import { createHash } from "node:crypto"; import { canonicalEvidence, safeParseReportDocument, ReportDocumentValidationError, type EvidenceAppendix, type ReportDocument, type ReportDocumentParseResult, } from "./personal-report-contract.ts"; /** * Server hash core for ReportDocument v1/v2. * * Pure Node implementation (node:crypto) without the server-only marker so * tests can import it directly; the production entry * personal-report-contract.server.ts adds `import "server-only"` and * re-exports this module. Client bundles must never import this file. * * The canonical isomorphic parse runs first, then provenance.evidenceHash is * recomputed over the evidence appendix. Stored documents are never trusted to * self-report their evidence hash. The Python validator performs the same * recomputation for both schema versions. */ export function computeEvidenceHash(appendix: EvidenceAppendix): string { const canonical = canonicalEvidence(appendix); return createHash("sha256").update(JSON.stringify(canonical), "utf8").digest("hex"); } export function safeParseServerReportDocument(input: unknown): ReportDocumentParseResult { const parsed = safeParseReportDocument(input); if (!parsed.ok) return parsed; const recomputed = computeEvidenceHash(parsed.document.evidenceAppendix); if (parsed.document.provenance.evidenceHash !== recomputed) { return { ok: false, errors: [ { path: "provenance.evidenceHash", message: `does not match recomputed evidence hash ${recomputed}`, }, ], }; } return parsed; } export function parseServerReportDocument(input: unknown): ReportDocument { const result = safeParseServerReportDocument(input); if (!result.ok) throw new ReportDocumentValidationError(result.errors); return result.document; }