{"record":{"id":"9447f951085ef25c","repo":"heygen-com/hyperframes","slug":"plan-v2-integrity-unrecoverable","errorCode":"PLAN_V2_INTEGRITY_UNRECOVERABLE","errorMessage":"[planV2] ${label} must be a lowercase SHA-256 digest","messagePattern":"\\[planV2\\] (.+?) must be a lowercase SHA-256 digest","errorType":"validation","errorClass":"PlanV2IntegrityError","httpStatus":null,"severity":"critical","filePath":"packages/aws-lambda/src/s3PlanV2Publisher.ts","lineNumber":27,"sourceCode":"  type PlanV2PublishBlob,\n} from \"@hyperframes/producer/distributed\";\nimport { parseS3Uri, uploadContentAddressedFileToS3 } from \"./s3Transport.js\";\n\nexport interface S3PlanV2ArtifactPublisherOptions {\n  readonly s3: S3Client;\n  /** Validated render output prefix from which all v2 object keys are derived. */\n  readonly planOutputS3Prefix: string;\n  /** Planner-local scratch parent for the small manifest upload file. */\n  readonly temporaryRoot?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction assertSha256(value: unknown, label: string): string {\n  if (typeof value !== \"string\" || !/^[a-f0-9]{64}$/.test(value)) {\n    throw new PlanV2IntegrityError(`${label} must be a lowercase SHA-256 digest`);\n  }\n  return value;\n}\n\nfunction manifestDigests(manifestBytes: string): ReadonlySet<string> {\n  let value: unknown;\n  try {\n    value = JSON.parse(manifestBytes);\n  } catch {\n    throw new PlanV2IntegrityError(\"S3 publisher received invalid manifest JSON\");\n  }\n  if (!isRecord(value) || !Array.isArray(value.artifacts)) {\n    throw new PlanV2IntegrityError(\"S3 publisher manifest requires an artifacts array\");\n  }\n  return new Set(\n    value.artifacts.map((artifact, index) => {\n      if (!isRecord(artifact)) {\n        throw new PlanV2IntegrityError(`S3 publisher artifacts[${index}] must be an object`);","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/aws-lambda/src/s3PlanV2Publisher.ts#L9-L45","documentation":"A `PlanV2IntegrityError` (code `PLAN_V2_INTEGRITY_UNRECOVERABLE`) thrown by `assertSha256` when a value expected to be a content-addressing digest is not a 64-char lowercase hex SHA-256. The publisher keys every v2 artifact under its digest, so a malformed digest breaks the CAS addressing scheme and is treated as unrecoverable.","triggerScenarios":"Calling `assertSha256(value, label)` (directly or via `putBlob`/`commitManifest`) where `value` is non-string, uppercase hex, too short/long, or contains non-hex characters.","commonSituations":"Producer computing digests with a non-normalized hash (base64, uppercase, truncated); a test fixture with a placeholder digest; manifest JSON whose `sha256` field was renamed or is the wrong field; a copy/paste digest typo.","solutions":["Produce digests with `crypto.createHash('sha256').update(data).digest('hex')` — always lowercase hex, 64 chars.","Validate the digest at the producer boundary before publishing (the same regex `/^[a-f0-9]{64}$/`).","Regenerate the manifest with correct digests and redeploy.","Check for field-name drift in the manifest schema (`sha256` vs `digest` vs `hash`)."],"exampleFix":"// before: base64 + uppercase digests\nconst digest = crypto.createHash('sha256').update(buf).digest('hex').toUpperCase();\n// after: lowercase 64-char hex\nconst digest = crypto.createHash('sha256').update(buf).digest('hex');","handlingStrategy":"validation","validationCode":"const SHA256_RE = /^[a-f0-9]{64}$/;\nfunction assertSha256(value: unknown, label: string): string {\n  if (typeof value !== \"string\" || !SHA256_RE.test(value)) {\n    throw new Error(`${label} must be a lowercase SHA-256 digest`);\n  }\n  return value;\n}","typeGuard":"function isLowercaseSha256(value: unknown): value is string {\n  return typeof value === \"string\" && /^[a-f0-9]{64}$/.test(value);\n}","tryCatchPattern":null,"preventionTips":["Always produce digests via `crypto.createHash('sha256').update(buf).digest('hex')` (lowercase).","Validate digests at the producer boundary with the same regex the publisher uses.","Add a schema test for the manifest that asserts every sha256 matches the regex."],"tags":["planv2","integrity","sha256","content-addressing","validation"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}