import { describe, expect, onTestFinished, test } from "bun:test" import { copyFileSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" import { recompute } from "./fact-derive.mjs" import { derive } from "./tests/derive-clock" import { LEDGER_FILENAME, addRow, emptyLedger, readLedger, validateLedger, writeLedger } from "./fact-ledger.mjs" // Generated by dev's own addRow (tests/fixtures/fidelity/gen-legacy-dev.mjs pins the dev commit): // no context/precision/compatibility columns, alias units, unreduced and zero-padded exacts. const FIXTURE = join(import.meta.dir, "tests", "fixtures", "fidelity", "ledger-legacy-dev.json") const SOURCE_IDS = ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "Q1", "F9", "F10", "F11", "F12", "F13", "F16", "F17", "F18"] type Row = Record type Ledger = { schemaVersion: number; rows: Row[]; events: unknown[] } function tempDir(): string { const dir = mkdtempSync(join(tmpdir(), "ulw-legacy-")) onTestFinished(() => rmSync(dir, { recursive: true, force: true })) return dir } const fixture = (): Ledger => JSON.parse(readFileSync(FIXTURE, "utf8")) const rowOf = (ledger: Ledger, id: string): Row => ledger.rows.find((row) => row.fact_id === id) as Row function loaded(): { dir: string; ledger: Ledger } { const dir = tempDir() copyFileSync(FIXTURE, join(dir, LEDGER_FILENAME)) return { dir, ledger: readLedger(dir) as Ledger } } describe("ledgers written by dev's writer (dimension 31 wins at read time)", () => { test("#given dev's generated ledger #when validated #then it has no errors", () => { expect(validateLedger(fixture())).toEqual([]) }) test("#given dev's ledger #when read #then exacts, decimals, and source units are normalized", () => { const { ledger } = loaded() expect(["F1", "F3", "F4", "F5", "F6", "F8", "F11"].map((id) => rowOf(ledger, id).unit)).toEqual(["km", "%", "%p", "person", "KRW-e3", "%", "km"]) const exacts = Object.fromEntries(["F10", "F11", "F12", "F13", "F14", "F15", "F18"].map((id) => [id, rowOf(ledger, id).exact])) expect(exacts).toEqual({ F10: { num: "1", den: "1" }, F11: { num: "5", den: "2" }, F12: { num: "7", den: "1" }, F13: { num: "-5", den: "2" }, F14: { num: "41", den: "2" }, F15: { num: "12000", den: "1" }, F18: { num: "3", den: "1", note: "from the brief" } }) expect(rowOf(ledger, "F7").value).toBe("7") for (const id of SOURCE_IDS) { for (const column of ["context", "precision", "compatibility"]) expect(rowOf(ledger, id)[column]).toBeNull() } for (const id of ["F14", "F15"]) expect(Object.hasOwn(rowOf(ledger, id), "precision")).toBe(false) expect(validateLedger(ledger)).toEqual([]) }) test("#given dev's ledger #when measured rows are summed #then the new row is canonical and recomputes", () => { for (const ledger of [fixture(), loaded().ledger]) { const result = derive(ledger, { op: "sum", operands: ["F1", "F2"], unit: "km" }) expect(result.error).toBeUndefined() expect(result.row).toMatchObject({ kind: "derived", unit: "km", value: "21", exact: { num: "41", den: "2" }, compatibility: "checked" }) const padded = derive(ledger, { op: "sum", operands: ["F1", "F7"], unit: "km" }) expect(padded.row).toMatchObject({ value: "19", exact: { num: "19", den: "1" } }) expect(validateLedger(ledger)).toEqual([]) expect(recompute(ledger, result.row).ok).toBe(true) } }) test("#given a legacy derived or converted row #when recomputed #then it reports a legacy derived row", () => { for (const ledger of [fixture(), loaded().ledger]) { for (const id of ["F14", "F15"]) { const result = recompute(ledger, rowOf(ledger, id)) expect(result.ok).toBe(false) expect(result.shape).toBe("legacy derived row") } } }) test("#given a legacy derived or converted row #when used as an operand #then derive refuses it", () => { const ledger = fixture() const before = ledger.rows.length for (const id of ["F14", "F15"]) { const result = derive(ledger, { op: "sum", operands: [id, "F2"], unit: "km" }) expect(result.error).toBe("incompatible_operands") expect(result.shape).toBe("legacy derived row") } expect(ledger.rows.length).toBe(before) }) test("#given a loadable row the strict dimensions reject #when used as an operand #then it is flagged by dimension", () => { const ledger = loaded().ledger const before = ledger.rows.length for (const [id, shape] of [["F16", "dimension 18"], ["F17", "dimension 1"]]) { const result = derive(ledger, { op: "sum", operands: [id, "F2"], unit: "km" }) expect(result.error).toBe("incompatible_operands") expect(result.shape).toBe(shape) } expect(ledger.rows.length).toBe(before) }) test("#given a new source row with a flaw dev accepted #when added #then this version's writer refuses it", () => { const ledger = fixture() const blank = { ...rowOf(ledger, "F16"), fact_id: undefined, label: "blank-again" } expect(() => addRow(ledger, blank)).toThrow("dimension 18") }) test("#given dev's ledger #when read and written twice #then the second pass changes nothing", () => { const { dir, ledger } = loaded() writeLedger(dir, ledger) const once = readFileSync(join(dir, LEDGER_FILENAME), "utf8") const written = JSON.parse(once) as Ledger expect(rowOf(written, "F11").exact).toEqual({ num: "5", den: "2" }) expect(Object.hasOwn(rowOf(written, "F14"), "precision")).toBe(false) writeLedger(dir, readLedger(dir)) expect(readFileSync(join(dir, LEDGER_FILENAME), "utf8")).toBe(once) expect(readLedger(dir)).toEqual(written) }) test("#given a current derived row with its later columns stripped #when validated #then it reads as legacy and never recomputes", () => { const ledger = fixture() const made = derive(ledger, { op: "sum", operands: ["F1", "F2"], unit: "km" }).row as Row const stripped = rowOf(ledger, made.fact_id as string) for (const column of ["context", "precision", "compatibility"]) delete stripped[column] stripped.unit = "kilometers" expect(validateLedger(ledger)).toEqual([]) expect(recompute(ledger, stripped).shape).toBe("legacy derived row") }) test("#given a ledger written by this version #when read and written #then it is unchanged", () => { const dir = tempDir() const ledger = emptyLedger() as Ledger const source = { kind: "measured", raw: "", quote: "", source_id: "S1", locator: { artifact: "sources/S1/text.txt", char_start: 0, char_end: 4 }, snapshot_sha256: "b".repeat(64), extracted_by: "fact add --find", observed_at: "2026-10-23T08:00:00Z" } addRow(ledger, { ...source, value: "3", unit: "kilometers", label: "a" }) addRow(ledger, { ...source, value: "4", unit: "km", label: "b" }) expect(derive(ledger, { op: "sum", operands: ["F1", "F2"], unit: "km" }).error).toBeUndefined() writeLedger(dir, ledger) const first = readFileSync(join(dir, LEDGER_FILENAME), "utf8") writeLedger(dir, readLedger(dir)) expect(readFileSync(join(dir, LEDGER_FILENAME), "utf8")).toBe(first) expect(readLedger(dir)).toEqual(JSON.parse(first)) }) test("#given a legacy exact with an unknown key #when read and written #then the key survives and the row is refused as an operand", () => { const { dir, ledger } = loaded() writeLedger(dir, ledger) const written = JSON.parse(readFileSync(join(dir, LEDGER_FILENAME), "utf8")) as Ledger expect(rowOf(written, "F18").exact).toEqual({ num: "3", den: "1", note: "from the brief" }) const result = derive(ledger, { op: "sum", operands: ["F18", "F2"], unit: "km" }) expect(result.error).toBe("incompatible_operands") expect(result.shape).toBe("dimension 7") expect(recompute(ledger, rowOf(ledger, "F18")).ok).toBe(false) }) test("#given an assumed row whose value and exact disagree #when read #then it loads flagged and a new row is refused (dimension 32)", () => { const dir = tempDir() const seed = fixture() const mismatched = { ...rowOf(seed, "F11"), fact_id: "F19", label: "mismatched-exact", unit: "km", value: "1.5", exact: { num: "3", den: "1" } } seed.rows.push(mismatched) writeFileSync(join(dir, LEDGER_FILENAME), JSON.stringify(seed)) const ledger = readLedger(dir) as Ledger expect(rowOf(ledger, "F19").value).toBe("1.5") const result = derive(ledger, { op: "sum", operands: ["F19", "F2"], unit: "km" }) expect(result.error).toBe("incompatible_operands") expect(result.shape).toBe("dimension 32") expect(() => addRow(ledger, { ...mismatched, fact_id: undefined, label: "mismatch-new" })).toThrow("dimension 32") }) })