This commit is contained in:
parent
8e71537d57
commit
fcc18ddcc8
96 changed files with 8074 additions and 214 deletions
42
pipeline/canonical-json.ts
Normal file
42
pipeline/canonical-json.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Deterministic JSON serialization — sorted keys recursively, no whitespace.
|
||||
* Required for manifest SHA-256 over exact bytes (SPIKE-04, ARCH 10.5).
|
||||
* Trace: SPIKE-04 §2 manifest, IMPLEMENTATION-CONTRACT.md §15
|
||||
*/
|
||||
|
||||
export function canonicalJson(value: unknown): string {
|
||||
return stringify(value);
|
||||
}
|
||||
|
||||
function stringify(value: unknown): string {
|
||||
if (value === null) return "null";
|
||||
if (value === undefined) return "null";
|
||||
const t = typeof value;
|
||||
if (t === "string") return JSON.stringify(value);
|
||||
if (t === "number") {
|
||||
if (!Number.isFinite(value as number)) throw new Error("non-finite number in canonical JSON");
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (t === "boolean") return value ? "true" : "false";
|
||||
if (Array.isArray(value)) {
|
||||
const items = (value as unknown[]).map((v) => stringify(v));
|
||||
return `[${items.join(",")}]`;
|
||||
}
|
||||
if (t === "object") {
|
||||
const obj = value as Record<string, unknown>;
|
||||
const keys = Object.keys(obj).sort();
|
||||
const parts: string[] = [];
|
||||
for (const k of keys) {
|
||||
const v = obj[k];
|
||||
if (v === undefined) continue; // omit undefined like JSON.stringify
|
||||
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
||||
}
|
||||
return `{${parts.join(",")}}`;
|
||||
}
|
||||
throw new Error(`unsupported canonical json type ${t}`);
|
||||
}
|
||||
|
||||
export function canonicalBytes(value: unknown): Uint8Array {
|
||||
const str = canonicalJson(value);
|
||||
return new TextEncoder().encode(str);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue