heygen-com/hyperframes · critical · PlanV2IntegrityError
PLAN_V2_INTEGRITY_UNRECOVERABLE
PLAN_V2_INTEGRITY_UNRECOVERABLE
Error message
[planV2] ${label} must be a lowercase SHA-256 digest What it means
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.
Source
Thrown at packages/aws-lambda/src/s3PlanV2Publisher.ts:27
type PlanV2PublishBlob,
} from "@hyperframes/producer/distributed";
import { parseS3Uri, uploadContentAddressedFileToS3 } from "./s3Transport.js";
export interface S3PlanV2ArtifactPublisherOptions {
readonly s3: S3Client;
/** Validated render output prefix from which all v2 object keys are derived. */
readonly planOutputS3Prefix: string;
/** Planner-local scratch parent for the small manifest upload file. */
readonly temporaryRoot?: string;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function assertSha256(value: unknown, label: string): string {
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) {
throw new PlanV2IntegrityError(`${label} must be a lowercase SHA-256 digest`);
}
return value;
}
function manifestDigests(manifestBytes: string): ReadonlySet<string> {
let value: unknown;
try {
value = JSON.parse(manifestBytes);
} catch {
throw new PlanV2IntegrityError("S3 publisher received invalid manifest JSON");
}
if (!isRecord(value) || !Array.isArray(value.artifacts)) {
throw new PlanV2IntegrityError("S3 publisher manifest requires an artifacts array");
}
return new Set(
value.artifacts.map((artifact, index) => {
if (!isRecord(artifact)) {
throw new PlanV2IntegrityError(`S3 publisher artifacts[${index}] must be an object`);View on GitHub (pinned to c2996c8626)
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`).
Example fix
// before: base64 + uppercase digests
const digest = crypto.createHash('sha256').update(buf).digest('hex').toUpperCase();
// after: lowercase 64-char hex
const digest = crypto.createHash('sha256').update(buf).digest('hex'); Defensive patterns
Strategy: validation
Validate before calling
const SHA256_RE = /^[a-f0-9]{64}$/;
function assertSha256(value: unknown, label: string): string {
if (typeof value !== "string" || !SHA256_RE.test(value)) {
throw new Error(`${label} must be a lowercase SHA-256 digest`);
}
return value;
} Type guard
function isLowercaseSha256(value: unknown): value is string {
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
} Prevention
- 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.
When it happens
Trigger: Calling `assertSha256(value, label)` (directly or via `putBlob`/`commitManifest`) where `value` is non-string, uppercase hex, too short/long, or contains non-hex characters.
Common situations: 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.
Related errors
- [s3Transport] expected lowercase SHA-256 digest, got ${JSON.
- [handler] chunk URI at index ${i} is empty
- [s3Transport] expected s3:// URI, got: ${JSON.stringify(uri)
- [s3Transport] missing key in s3 URI: ${JSON.stringify(uri)}
- [s3Transport] empty bucket or key in s3 URI: ${JSON.stringif
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/9447f951085ef25c.
Report an issue: GitHub.