{"name":"Prooftask · Prepare a material submission","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  \"input\": {\n    \"requestId\": \"CREATE_AND_PERSIST_A_UUID_V4\",\n    \"agreementDigest\": \"FROM_CURRENT_AGREEMENT\",\n    \"previousSubmissionId\": null,\n    \"respondsToReviewId\": null,\n    \"summary\": \"Describe the materials actually delivered.\",\n    \"items\": []\n  }\n}}];"},"notes":"Use an exact authorized project. This template is inactive and has no schedule.","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 id=function automationProjectId(value         )         {\n  if (typeof value !== '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(value)) throw new Error('Use an exact delivery project ID.');\n  return value;\n}; return $input.all().map(i=>({json:{...i.json,projectId:id(i.json.projectId)}}));"}},{"id":"prooftask-3","name":"Read current project","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":"GET","url":"={{ 'https://prooftask.com/api/agents/v1/projects/' + encodeURIComponent($json.projectId) }}"},"notes":"Select a Header Auth credential. This call does not accept terms, submit or confirm completion.","notesInFlow":true},{"id":"prooftask-4","name":"Prepare only - persist output","type":"n8n-nodes-base.code","typeVersion":2,"position":[1040,0],"parameters":{"jsCode":"const prepare=function prepareSubmission(value     , view     )                                       {\n  const t = view?.project;\n  if (!t || t.taskId !== value?.projectId || !view.automation || view.automation.mode !== 'AGENT' || !view.availableActions?.includes('SUBMIT_MATERIALS')) throw new Error('This project does not currently allow this agent to submit.');\n  if (!value.input || value.input.agreementDigest !== t.agreement?.digest) throw new Error('Review the current agreement before preparing materials.');\n  if ((value.input.previousSubmissionId ?? null) !== (t.submission?.id ?? null)) throw new Error('Bind the exact preceding submission.');\n  const previousReview = ['REQUEST_CHANGES','REQUEST_EVIDENCE'].includes(t.review?.payload?.action) ? t.review.id : null;\n  if ((value.input.respondsToReviewId ?? null) !== previousReview) throw new Error('Bind the correction request being answered.');\n  const ids = value.input.items?.map((i     ) => i.criterionId);\n  if (!Array.isArray(ids) || new Set(ids).size !== ids.length || ids.length !== t.agreement.criteria.length || t.agreement.criteria.some((c     ) => !ids.includes(c.id))) throw new Error('Map materials to every agreed criterion exactly once.');\n  return { projectId: t.taskId, envelope: {expectedEventHash:t.headEventHash,automationRevision:view.automation.revision,input:JSON.parse(JSON.stringify(value.input))} };\n},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};const input=$('Validate input').first().json;return [{json:validate(prepare(input,$input.first().json))}];"},"notes":"STOP here. Persist this entire result in your existing durable job store before sending. Use the Submit template with that exact saved object. Preparation is not submission.","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":"Read current project","type":"main","index":0}]]},"Read current project":{"main":[[{"node":"Prepare only - persist output","type":"main","index":0}]]}},"active":false,"settings":{"executionOrder":"v1","saveDataSuccessExecution":"none","saveDataErrorExecution":"none","saveManualExecutions":false},"pinData":{},"tags":[]}