{"record":{"id":"ac37e5b67161f6d6","repo":"paperclipai/paperclip","slug":"public-replay-contains-unprojected-evidence","errorCode":null,"errorMessage":"Public replay contains unprojected evidence","messagePattern":"Public replay contains unprojected evidence","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/public-eval-viewer.mjs","lineNumber":181,"sourceCode":"        visit(child, name);\n      }\n    }\n  };\n  visit(payload);\n  for (const section of [\n    \"tools\",\n    \"authorization\",\n    \"control_plane\",\n    \"runner\",\n    \"state\",\n    \"traceability\",\n    \"parity\",\n  ]) {\n    if (\n      !Array.isArray(payload.view.evidence?.[section]) ||\n      payload.view.evidence[section].length\n    )\n      throw new Error(\"Public replay contains unprojected evidence\");\n  }\n  for (const call of payload.view.evidence.calls) {\n    fields(\n      call,\n      \"id turnId operationId version providerRequest dispatchedCommand outcome result redactions threadAnchorId\",\n    );\n    fields(call.result, \"outcome detail\");\n    if (call.result.detail !== \"Tool payload withheld from public replay.\")\n      throw new Error(\"Public replay contains raw call evidence\");\n  }\n  for (const turn of payload.view.turns ?? []) {\n    fields(turn, \"id ordinal mode toolCallCount at stoppedByUser items\");\n    for (const item of turn.items ?? []) {\n      if (\n        ![\n          \"user_message\",\n          \"agent_message\",\n          \"tool_activity\",","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/public-eval-viewer.mjs#L163-L199","documentation":"Thrown by validatePublicChatPayload in the public eval viewer script. The public replay payload is supposed to be a projected view where the 'parity' evidence section (and similar sections) are either absent or empty arrays, since raw evidence must not leak into a public artifact. If a projected section still contains items, the projection step was skipped or buggy, so the validator throws.","triggerScenarios":"Calling validatePublicChatPayload with a payload whose payload.view.evidence.parity (or another iterated section) is a non-empty array; also triggered when the section is not an array at all.","commonSituations":"Regenerating a public replay without running the evidence-projection/redaction step; hand-editing the JSON and pasting internal evidence; a code change adding a new evidence section that the projector does not clear.","solutions":["Run the evidence projection step so evidence.parity (and all projected sections) are emitted as empty arrays before validation.","Check the code that builds payload.view.evidence to ensure every section iterated here is cleared for public output.","If the payload was edited by hand, remove the leaked parity evidence entries."],"exampleFix":"// before\nview.evidence.parity = internalParityResults;\n// after\nview.evidence.parity = []; // projected: public replay carries no parity evidence","handlingStrategy":"validation","validationCode":"const projected = ['parity'].every(s => Array.isArray(view.evidence?.[s]) && view.evidence[s].length === 0);\nif (!projected) throw new Error('evidence sections must be empty in public payload');","typeGuard":"function isProjectedEvidence(e) { return Array.isArray(e?.parity) && e.parity.length === 0; }","tryCatchPattern":null,"preventionTips":["Always run the projection/redaction step before validating a public payload.","Add a unit test asserting all projected evidence sections are empty arrays.","Never hand-edit public replay JSON."],"tags":["validation","security","replay"],"backgroundTag":"unexpected-response-shape","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}