{"record":{"id":"2a3f7ca3478f1130","repo":"ruvnet/ruflo","slug":"run-command-and-evidence-digests-must-be-canonical","errorCode":null,"errorMessage":"run command and evidence digests must be canonical sha256 values","messagePattern":"run command and evidence digests must be canonical sha256 values","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/in-memory-run-receipt-reference.ts","lineNumber":27,"sourceCode":"}\n\nfunction copy<T>(value: T): T {\n  return structuredClone(value);\n}\n\nfunction timestamp(value: string, label: string): number {\n  const parsed = Date.parse(value);\n  if (!Number.isFinite(parsed)) throw new Error(`${label} must be an ISO timestamp`);\n  return parsed;\n}\n\nfunction validateRun(run: RunEvidence): void {\n  if (!run.executionId.trim() || !run.sessionId.trim() || !run.workloadId.trim()) {\n    throw new Error('run execution, session, and workload identity are required');\n  }\n  if (!DIGEST.test(run.sourceState.sourceStateId)) throw new Error('run sourceStateId is invalid');\n  if (!DIGEST.test(run.commandDigest) || !DIGEST.test(run.evidenceDigest)) {\n    throw new Error('run command and evidence digests must be canonical sha256 values');\n  }\n  if (!Number.isSafeInteger(run.exitCode)) throw new Error('run exitCode must be a safe integer');\n  const started = timestamp(run.startedAt, 'startedAt');\n  const completed = timestamp(run.completedAt, 'completedAt');\n  if (completed < started) throw new Error('run completedAt precedes startedAt');\n  if (\n    run.buildEvidence !== undefined\n    && run.buildEvidence.sourceStateId !== run.sourceState.sourceStateId\n  ) {\n    throw new Error('build evidence belongs to a different source state');\n  }\n}\n\n/**\n * Unsigned, non-durable, content-addressed in-memory conformance ledger.\n *\n * Exact retries converge on one receipt. Reusing an execution ID with changed\n * evidence is refused rather than rewriting history. These receipts are local","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/in-memory-run-receipt-reference.ts#L9-L45","documentation":"Both run.commandDigest and run.evidenceDigest must match ^sha256:[0-9a-f]{64}$ (the same DIGEST regex as sourceStateId). validateRun checks the two fields in one condition and throws this single message when either fails. The digests anchor the receipt's content address, so non-canonical forms (no prefix, wrong case, wrong algorithm) are refused.","triggerScenarios":"recordRun with commandDigest: sha256 hex without the 'sha256:' prefix, an uppercase-hex digest, a base64 digest, a SHA-1/SHA-512 value, or the two fields accidentally swapped with non-digest content.","commonSituations":"Producing digests with a different utility that returns bare or base64 hex; migrating from a tool that uses 'sha1:' style prefixes; unit tests stubbing digests as 'abc123'.","solutions":["Compute both with one shared helper: `const sha256 = (v: string) => \\`sha256:\\${createHash('sha256').update(v).digest('hex')}\\``","Derive evidenceDigest from canonicalJson(evidence) so the format matches the ledger's own convention","Pre-validate both fields against /^sha256:[0-9a-f]{64}$/ before recordRun"],"exampleFix":"// before\n{ commandDigest: hashHex(cmd), evidenceDigest: hashHexB64(evidence) }\n\n// after\nconst sha256 = (v: string) => `sha256:${createHash('sha256').update(v).digest('hex')}`;\n{ commandDigest: sha256(cmd), evidenceDigest: sha256(canonicalJson(evidence)) }","handlingStrategy":"type-guard","validationCode":"const SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;\nfunction assertDigests(run: { commandDigest: string; evidenceDigest: string }): void {\n  for (const [label, value] of [['commandDigest', run.commandDigest], ['evidenceDigest', run.evidenceDigest]] as const) {\n    if (!SHA256_DIGEST.test(value)) throw new TypeError(`${label} is not canonical sha256: ${value}`);\n  }\n}","typeGuard":"function isCanonicalSha256(value: unknown): value is string {\n  return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);\n}","tryCatchPattern":"try {\n  ledger.recordRun(run);\n} catch (error) {\n  if (error instanceof Error && error.message === 'run command and evidence digests must be canonical sha256 values') {\n    run.commandDigest = sha256(run.command);\n    run.evidenceDigest = sha256(canonicalJson(run.evidence));\n    ledger.recordRun(run);\n  } else throw error;\n}","preventionTips":["Compute digests with a single shared sha256() helper returning the canonical form","Derive evidenceDigest from canonicalJson so it matches ledger expectations byte-for-byte","Reject uppercase or unprefixed digests in code review of evidence-building code"],"tags":["validation","digest","sha256","run-receipt"],"backgroundTag":"invalid-content-digest","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}