{"record":{"id":"333772a240710f5b","repo":"ruvnet/ruflo","slug":"label-must-be-a-canonical-sha256-digest","errorCode":null,"errorMessage":"${label} must be a canonical sha256 digest","messagePattern":"(.+?) must be a canonical sha256 digest","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/build-evidence.ts","lineNumber":60,"sourceCode":"  assurance: 'declared-unsigned';\n  sourceStateId: string;\n  buildInputs: readonly DeclaredBuildInput[];\n  toolchains: readonly DeclaredToolchain[];\n  evidenceDigest: string;\n}\n\nfunction compare(left: string, right: string): number {\n  return left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction requireText(value: string, label: string): string {\n  const result = value.trim();\n  if (!result) throw new Error(`${label} must be non-empty`);\n  return result;\n}\n\nfunction requireDigest(value: string, label: string): string {\n  if (!DIGEST.test(value)) throw new Error(`${label} must be a canonical sha256 digest`);\n  return value;\n}\n\nfunction normalizePath(value: string): string {\n  const path = requireText(value, 'build input path');\n  if (\n    path.includes('\\\\')\n    || path.startsWith('/')\n    || path.startsWith('-')\n    || path !== path.normalize('NFC')\n    || path.split('/').some((part) => !part || part === '.' || part === '..')\n  ) {\n    throw new Error(`unsafe build input path: ${value}`);\n  }\n  return path;\n}\n\nfunction sha256(value: string): string {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/build-evidence.ts#L42-L78","documentation":"Digest fields in build evidence (build input digest, toolchain digest, and source state id) must match the canonical form enforced by the regex /^sha256:[0-9a-f]{64}$/ — the literal prefix 'sha256:' followed by exactly 64 lowercase hex characters. Uppercase hex, a missing prefix, a different algorithm (sha512, a git object id), or a wrong-length string all throw, with the failing field named in the label.","triggerScenarios":"Passing bare 64-char hex from sha256sum without the 'sha256:' prefix; uppercase hex; a SHA-1 git SHA; truncated or double-prefixed digests ('sha256:sha256:...').","commonSituations":"Piping sha256sum output straight into declarations; mixing git commit/blob SHAs with content digests; reformatting evidence produced by another hasher or registry (which often use uppercase or sha512).","solutions":["Format every digest as `sha256:${hex}` with lowercase hex, exactly 64 characters","Compute with node:crypto: `sha256:${createHash('sha256').update(bytes).digest('hex')}`","When carrying evidence produced by captureBuildEvidence, reuse the returned digests verbatim instead of reformatting them"],"exampleFix":"// before\nconst digest = '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08';\n\n// after\nimport { createHash } from 'node:crypto';\nconst digest = `sha256:${createHash('sha256').update(content).digest('hex')}`;","handlingStrategy":"validation","validationCode":"const CANONICAL_SHA256 = /^sha256:[0-9a-f]{64}$/;\nfunction isCanonicalSha256(value: string): boolean {\n  return CANONICAL_SHA256.test(value);\n}\nfunction toCanonicalSha256(bytes: Buffer | string): string {\n  return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;\n}","typeGuard":"function isCanonicalSha256(value: unknown): value is string {\n  return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);\n}","tryCatchPattern":null,"preventionTips":["Always produce digests with the same helper that prepends 'sha256:' and lowercases hex","Reject digests from external tools at ingest with the regex before they reach evidence code","Never mix git object ids into digest fields — they are different namespaces"],"tags":["build-evidence","sha256","checksum","validation","harness"],"backgroundTag":"invalid-checksum-format","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}