import { createHash } from "node:crypto"; import { canonicalEvidence, safeParseReportDocument, ReportDocumentValidationError, type EvidenceAppendix, type ReportDocumentParseResult, type ReportDocumentV1, } from "./personal-report-contract.ts"; /** * Server hash core for ReportDocument v1. * * 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: besides * the marker on the production entry, node:crypto fails Next.js client builds. * * Flow per the architecture ruling: canonical isomorphic parse first, then * verify provenance.evidenceHash against the recomputed hash. The hash is * never trusted as a model self-report. The Python validator * (scripts/personal_report_contract.py) performs the same recomputation. */ 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): ReportDocumentV1 { const result = safeParseServerReportDocument(input); if (!result.ok) throw new ReportDocumentValidationError(result.errors); return result.document; }