import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const outputPath = path.join(root, "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json"); const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); const relative = (file) => path.relative(root, file).replaceAll(path.sep, "/"); const write = process.argv.includes("--write"); const entries = []; for (const task of fs.readdirSync(path.join(root, "tests/golden")).filter((name) => /^M12-/u.test(name)).sort()) { const manifestPath = path.join(root, "tests/golden", task, "manifest.json"); if (!fs.existsSync(manifestPath)) continue; const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); for (const [artifact, binding] of Object.entries(manifest.artifacts ?? {})) { if (!binding?.path || !binding.sha256) continue; const artifactPath = path.join(root, binding.path); if (!fs.existsSync(artifactPath)) continue; const observedSha256 = sha256(artifactPath); if (observedSha256 !== binding.sha256) { entries.push({ task, artifact, path: binding.path, originalSha256: binding.sha256, observedSha256, provenanceState: "BLOCKED_SOURCE_LOST", }); } } } const evidence = { schemaVersion: 1, operation: "M12_LEGACY_MANIFEST_DRIFT_REHYDRATION", historicalEvidencePreserved: true, immutableSourceSnapshotAvailable: false, provenanceState: "BLOCKED_SOURCE_LOST", entries, }; if (write) { fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, `${JSON.stringify(evidence, null, 2)}\n`); } else { assert.ok(fs.existsSync(outputPath), "M12 drift evidence is missing; run with --write"); const recorded = JSON.parse(fs.readFileSync(outputPath, "utf8")); assert.deepEqual(recorded, evidence, "M12 drift evidence is stale or current artifacts changed"); } process.stdout.write(`m12-manifest-drift-ok entries=${entries.length} paths=${new Set(entries.map((entry) => entry.path)).size} provenance=${evidence.provenanceState} write=${write}\n`);