{"record":{"id":"485a077216bcc929","repo":"paperclipai/paperclip","slug":"unsupported-protocol-eval-history-schema","errorCode":null,"errorMessage":"Unsupported protocol eval history schema","messagePattern":"Unsupported protocol eval history schema","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs","lineNumber":307,"sourceCode":"export function protocolEvalHistoryRecord(campaign, publicRoot) {\n  return {\n    campaignId: campaign.campaignId,\n    generatedAt: campaign.generatedAt,\n    publicUrl: `${publicRoot}/campaigns/${encodeURIComponent(campaign.campaignId)}/`,\n    complete: campaign.complete === true,\n    allPassed: campaign.allPassed === true,\n    totals: campaign.totals,\n    rosters: campaign.rosters,\n    source: campaign.source,\n    ...(campaign.reportRevision\n      ? { reportRevision: campaign.reportRevision }\n      : {}),\n  };\n}\n\nexport function mergeProtocolEvalHistory(history, record) {\n  if (history.schema !== \"paperclip.runner-protocol-eval.history/v1\") {\n    throw new Error(\"Unsupported protocol eval history schema\");\n  }\n  const existing = history.campaigns.find(\n    (item) => item.campaignId === record.campaignId,\n  );\n  if (existing && JSON.stringify(existing) !== JSON.stringify(record)) {\n    throw new Error(\n      `Immutable campaign history changed for ${record.campaignId}`,\n    );\n  }\n  const campaigns = existing\n    ? [...history.campaigns]\n    : [...history.campaigns, record];\n  const activityAt = (campaign) =>\n    campaign.reportRevision?.renderedAt ?? campaign.generatedAt;\n  const activityOrder = (left, right) =>\n    activityAt(right).localeCompare(activityAt(left));\n  campaigns.sort(activityOrder);\n  // Report revisions are discoverable history entries, never qualification runs.","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs#L289-L325","documentation":"mergeProtocolEvalHistory only accepts an existing history document with schema \"paperclip.runner-protocol-eval.history/v1\". When merging a freshly published campaign into the S3 history.json, an unknown or older schema version is rejected so the immutable history semantics are not applied to an incompatible document shape.","triggerScenarios":"Calling mergeProtocolEvalHistory(history, record) where history came from an old deployment with a different schema string; downloading a history.json that was hand-edited; passing a campaign document (or other object) instead of a history document; constructing a history object without the schema field.","commonSituations":"A history.json published by a previous major version of the script still living in the S3 bucket; manual JSON editing of the remote history; a bug writing an empty or wrong-typed file to the history key.","solutions":["Inspect the remote s3://<bucket>/<prefix>/history.json and check its schema field","If it is a legacy version, migrate or delete the history object so a fresh v1 history is seeded via emptyProtocolEvalHistory()","Ensure the object passed to mergeProtocolEvalHistory is a history document (schema, campaigns array), not a campaign or bundle manifest","Re-run with the current version of this script, which reads and writes v1"],"exampleFix":"// before\nconst history = JSON.parse(await readFile(\"history.json\", \"utf8\")); // schema: history/v0\nmergeProtocolEvalHistory(history, record);\n// after\nif (history.schema !== \"paperclip.runner-protocol-eval.history/v1\") {\n  history = emptyProtocolEvalHistory(); // or migrate v0 -> v1\n}\nmergeProtocolEvalHistory(history, record);","handlingStrategy":"type-guard","validationCode":"const history = remote ?? emptyProtocolEvalHistory();\nif (history.schema !== \"paperclip.runner-protocol-eval.history/v1\") throw new Error(`unsupported history schema: ${history.schema}`);","typeGuard":"const isHistoryV1 = (h) =>\n  !!h && typeof h === \"object\" &&\n  h.schema === \"paperclip.runner-protocol-eval.history/v1\" &&\n  Array.isArray(h.campaigns);","tryCatchPattern":"try {\n  merged = mergeProtocolEvalHistory(history, record);\n} catch (e) {\n  if (String(e.message).includes(\"Unsupported protocol eval history schema\")) {\n    console.error(\"history.json has an incompatible schema; migrate or reset it\");\n  }\n  throw e;\n}","preventionTips":["Check the remote history.json schema before merging; seed with emptyProtocolEvalHistory() when absent","Do not hand-edit history.json in S3","Version-bump and migrate deliberately if the history schema ever changes"],"tags":["schema","migration","versioning"],"backgroundTag":"schema-validation-failed","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}