/*
RFC-0221: detect and surface derived-file edits as decision records. When a derived
path in the bundle manifest has a hash mismatch (edited after packing), absorb raises a
decision record instead of silently regenerating, so hand edits are not lost without warning.
- Do not modify files — pure detection.
- RFC-0221: derived-edit detection for decision records.
*/
import path from "node:path";
import type { HandoffManifest } from "@warpgogol/werkstatt-engine/schemas";
import { sha256OfBytes } from "./bundle-io.ts";
import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
export interface DerivedEditMismatch {
path: string;
expectedHash: string;
actualHash: string | null;
}
/**
* Check manifest derived entries against actual bundle file contents. Returns mismatches
* where a derived file's hash does not match the stored hash (indicating post-pack edits).
* Returns empty if no derived entries or all hashes match.
*/
export async function reportDerivedEdits(
bundleDir: string,
manifest: HandoffManifest,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
const mismatches: DerivedEditMismatch[] = [];
for (const entry of manifest.entries) {
if (entry.kind !== "derived") continue;
const abs = path.join(bundleDir, entry.path);
let actualHash: string | null = null;
try {
actualHash = sha256OfBytes(await io.readFile(abs));
} catch {
// File absent (could have been deleted) — report as mismatch.
actualHash = null;
}
if (actualHash !== entry.hash) {
mismatches.push({
path: entry.path,
expectedHash: entry.hash,
actualHash,
});
}
}
return mismatches.sort((a, b) => a.path.localeCompare(b.path));
}