Reuse verified immutable plans and streamline cooperative hashing

Continue final hosted development reliability qualification.
This commit is contained in:
Timothy J. Aveni
2026-10-12 20:16:56 -07:00
parent 47b316180d
commit 62a09605cf
15 changed files with 214 additions and 57 deletions
+68 -31
View File
@@ -26,43 +26,80 @@ export const contentDigest = (value: unknown) =>
* event loop while verifying large immutable metadata. No validation is skipped.
* This is for asynchronous loading, not a different build/wire identity. */
export async function contentDigestAsync(value: unknown): Promise<string> {
function* parts(value: unknown): Generator<string> {
if (Array.isArray(value)) {
yield "[";
for (let index = 0; index < value.length; index++) {
if (index) yield ",";
// Match canonicalJson's Array.map behavior for sparse arrays too.
if (index in value) yield* parts(value[index]);
}
yield "]";
} else if (value !== null && typeof value === "object") {
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
throw new Error("Expected a plain JSON object");
yield "{";
let first = true;
for (const key of Object.keys(value).sort(compareText)) {
const entry = (value as Record<string, unknown>)[key];
if (entry === undefined) continue;
if (!first) yield ",";
first = false;
yield JSON.stringify(key) + ":";
yield* parts(entry);
}
yield "}";
} else yield canonicalJson(value);
}
const hash = createHash("sha256");
// An explicit stack visits each node once; recursive generator delegation
// otherwise forwards every token through every ancestor of a deep plan.
type Frame = { kind: "value" | "array" | "object"; value: unknown; index?: number; keys?: string[]; wrote?: boolean };
const hash = createHash("sha256"),
stack: Frame[] = [{ kind: "value", value }],
active = new WeakSet<object>();
let buffer = "",
sliceStarted = performance.now();
for (const part of parts(value)) {
sliceStarted = performance.now(),
steps = 0;
const append = (part: string) => {
buffer += part;
if (buffer.length >= 65536) {
hash.update(buffer);
buffer = "";
if (performance.now() - sliceStarted >= 8) {
await setImmediate();
sliceStarted = performance.now();
}
};
while (stack.length) {
const frame = stack[stack.length - 1]!;
if (frame.kind === "value") {
const value = frame.value;
if (value !== null && typeof value === "object") {
if (active.has(value)) throw new Error("Cannot hash cyclic JSON values");
active.add(value);
frame.index = 0;
if (Array.isArray(value)) {
frame.kind = "array";
append("[");
} else {
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
throw new Error("Expected a plain JSON object");
frame.kind = "object";
frame.keys = Object.keys(value).sort(compareText);
frame.wrote = false;
append("{");
}
} else {
append(canonicalJson(value));
stack.pop();
}
} else if (frame.kind === "array") {
const array = frame.value as unknown[];
if (frame.index === array.length) {
append("]");
active.delete(array);
stack.pop();
} else {
const index = frame.index!;
frame.index = index + 1;
if (index) append(",");
// Match canonicalJson's Array.map behavior for sparse arrays too.
if (index in array) stack.push({ kind: "value", value: array[index] });
}
} else {
const object = frame.value as Record<string, unknown>;
if (frame.index === frame.keys!.length) {
append("}");
active.delete(object);
stack.pop();
} else {
const index = frame.index!;
frame.index = index + 1;
const key = frame.keys![index]!,
entry = object[key];
if (entry !== undefined) {
if (frame.wrote) append(",");
frame.wrote = true;
append(JSON.stringify(key) + ":");
stack.push({ kind: "value", value: entry });
}
}
}
if (++steps % 512 === 0 && performance.now() - sliceStarted >= 8) {
await setImmediate();
sliceStarted = performance.now();
}
}
hash.update(buffer);