{"name":"Prooftask · Submit saved materials and read status","nodes":[{"id":"prooftask-0","name":"Run deliberately","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[0,0],"parameters":{}},{"id":"prooftask-1","name":"Input","type":"n8n-nodes-base.code","typeVersion":2,"position":[260,0],"parameters":{"jsCode":"// Replace this example with your actual input, or connect an upstream node. Never put credentials here.\nreturn [{json:{\n  \"projectId\": \"REPLACE_WITH_PROJECT_ID\",\n  \"envelope\": {\n    \"expectedEventHash\": \"FROM_PREPARE\",\n    \"automationRevision\": 0,\n    \"input\": {\n      \"requestId\": \"STABLE_UUID_FROM_PREPARE\"\n    }\n  }\n}}];"},"notes":"Load the exact saved prepare output. Do not regenerate its request ID or head on retries.","notesInFlow":true},{"id":"prooftask-2","name":"Validate input","type":"n8n-nodes-base.code","typeVersion":2,"position":[520,0],"parameters":{"jsCode":"if($input.all().length!==1)throw new Error('Run this template for exactly one project at a time.');\nconst validate=function savedSubmission(value     )                                       {\n  const id = value?.projectId;\n  if (typeof id !== 'string' || !/^delivery_[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(id)) throw new Error('Use an exact delivery project ID.');\n  const e = value?.envelope;\n  if (!e || typeof e !== 'object' || Array.isArray(e) || Object.keys(e).sort().join(',') !== 'automationRevision,expectedEventHash,input') throw new Error('Supply the entire previously saved envelope.');\n  if (typeof e.expectedEventHash !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(e.expectedEventHash) || !Number.isSafeInteger(e.automationRevision) || e.automationRevision < 0) throw new Error('Missing exact project head or automation revision.');\n  const i = e.input;\n  if (!i || typeof i !== 'object' || Array.isArray(i) || !/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(i.requestId || '')) throw new Error('Use the stable request ID saved before the first send.');\n  const fields = ['requestId','agreementDigest','previousSubmissionId','respondsToReviewId','summary','items'];\n  if (Object.keys(i).some(k => !fields.includes(k)) || typeof i.agreementDigest !== 'string' || !/^[a-f0-9]{64}$/.test(i.agreementDigest) || typeof i.summary !== 'string' || i.summary.length < 10 || i.summary.length > 4000 || !Array.isArray(i.items) || i.items.length < 1 || i.items.length > 20) throw new Error('Provide a complete submission, never an acceptance or payment instruction.');\n  if (JSON.stringify(value).length > 1_000_000) throw new Error('Submission is too large for this template.');\n  return { projectId: id, envelope: JSON.parse(JSON.stringify(e)) };\n}; return $input.all().map(i=>({json:validate(i.json)}));"}},{"id":"prooftask-3","name":"Submit saved envelope","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[780,0],"parameters":{"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{"allowUnauthorizedCerts":false,"redirect":{"redirect":{"followRedirects":false,"maxRedirects":0}},"response":{"response":{"responseFormat":"json","neverError":false}},"timeout":20000},"method":"POST","url":"={{ 'https://prooftask.com/api/agents/v1/projects/' + encodeURIComponent($json.projectId) + '/submit' }}","sendBody":true,"contentType":"json","specifyBody":"json","jsonBody":"={{ JSON.stringify($json.envelope) }}"},"notes":"Select a Header Auth credential: Authorization = Bearer <your project-scoped token>. Errors stop the flow; retry only the same saved input.","notesInFlow":true},{"id":"prooftask-4","name":"Read current decision","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1040,0],"parameters":{"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{"allowUnauthorizedCerts":false,"redirect":{"redirect":{"followRedirects":false,"maxRedirects":0}},"response":{"response":{"responseFormat":"json","neverError":false}},"timeout":20000},"method":"GET","url":"={{ 'https://prooftask.com/api/agents/v1/projects/' + encodeURIComponent($('Validate input').first().json.projectId) }}"},"notes":"Use the same scoped credential. A failed read must never be treated as acceptance.","notesInFlow":true},{"id":"prooftask-5","name":"Decision readback","type":"n8n-nodes-base.code","typeVersion":2,"position":[1300,0],"parameters":{"jsCode":"const read=function automationDecision(view     , projectId         ) {\n  const t = view?.project;\n  if (!t || t.taskId !== projectId || typeof t.state !== 'string' || typeof t.headEventHash !== 'string') throw new Error('The expected project state was not returned.');\n  const review = t.review;\n  return { projectId: t.taskId, agreementDigest:t.agreement?.digest, state:t.state, headEventHash:t.headEventHash,\n    submissionId:t.submission?.id ?? null, reviewId:review?.id ?? null, recordedReviewAction:review?.payload?.action ?? null,\n    automationMode:view.automation?.mode ?? 'UNKNOWN', availableActions:view.availableActions ?? [],\n    observedAt:new Date().toISOString(), humanUrl:view.humanUrl,\n    note:'Point-in-time API readback, not independent verification or payment. Earlier review actions may refer to earlier submissions; use the current state and exact IDs. Re-read before acting.' };\n};return [{json:read($input.first().json,$('Validate input').first().json.projectId)}];"},"notes":"Pass explicit state and exact IDs back to your existing workflow. Pending, rejected, superseded or unknown must not be treated as confirmed. This is not a payment instruction.","notesInFlow":true}],"connections":{"Run deliberately":{"main":[[{"node":"Input","type":"main","index":0}]]},"Input":{"main":[[{"node":"Validate input","type":"main","index":0}]]},"Validate input":{"main":[[{"node":"Submit saved envelope","type":"main","index":0}]]},"Submit saved envelope":{"main":[[{"node":"Read current decision","type":"main","index":0}]]},"Read current decision":{"main":[[{"node":"Decision readback","type":"main","index":0}]]}},"active":false,"settings":{"executionOrder":"v1","saveDataSuccessExecution":"none","saveDataErrorExecution":"none","saveManualExecutions":false},"pinData":{},"tags":[]}