/* RFC-0364, RFC-0849: Lightweight hashing primitives — byte hashing, stable JSON hashing, and Sha256Digest opaque type. No parser dependencies. Do not implement file-type normalizers — those live in normalizers/. Do not implement tree fingerprinting — that lives in fingerprint.ts. Do not implement canonical JSON — that lives in canonical-json.ts. RFC-0849: add Sha256Digest branded type, isSha256Digest guard, and narrower byteHash/byteHashFile returns. Existing string consumers remain compatible. RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. RFC-1149: migrate ambient filesystem and process IO to the WorkspaceIO port across engine and shared */ import { createHash } from "node:crypto"; import { createDefaultIO, type WorkspaceIO } from "../kernel/workspace-io.ts"; declare const sha256DigestBrand: unique symbol; export type Sha256Digest = `sha256:${string}` & { readonly [sha256DigestBrand]: true; }; const SHA256_DIGEST_RE = /^sha256:[0-9a-f]{64}$/; export function isSha256Digest(value: unknown): value is Sha256Digest { return typeof value === "string" && SHA256_DIGEST_RE.test(value); } export function byteHash(bytes: Uint8Array | string): Sha256Digest { const input = typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes; return `sha256:${createHash("sha256").update(input).digest("hex")}` as Sha256Digest; } export async function byteHashFile( absPath: string, io: WorkspaceIO = createDefaultIO().io, ): Promise { const bytes = await io.readFileBytes(absPath); return byteHash(bytes); } function sortForJson(value: unknown): unknown { if (Array.isArray(value)) return value.map(sortForJson); if (value && typeof value === "object") { return Object.fromEntries( Object.entries(value as Record) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, nested]) => [key, sortForJson(nested)]), ); } return value; } export function stableStringify(value: unknown): string { return JSON.stringify(sortForJson(value)); } export function stableJsonHash(value: unknown): string { return byteHash(stableStringify(value)); }