/** * th1_composition.test.ts * * Vitest tests for the Lutar.Composition.TH1_Composition gate. * * Tests: * 1. 1000-input random doctrine preservation test * 2. Lattice order (labelLE) edge cases * 3. Compatible / incompatible system pairs * 4. composeList across all label combinations * 5. Receipt emission with mock signer * * Lean commit: c4d13795689601324fce0236351bfe0ade990a43 */ import { describe, it, expect } from "vitest"; import { labelLE, doctrinePredicate, isDoctrineLockedSystem, compatible, compose, composeList, verifyCompositionPreservesDoctrine, emitTH1CompositionReceipt, th1CompositionGate, type LutarSystem, type DoctrineLabel, type Signer, } from "../../src/gates/th1_composition"; // --------------------------------------------------------------------------- // Seeded LCG // --------------------------------------------------------------------------- function seedRandom(seed: number) { let s = seed >>> 0; return () => { s = (Math.imul(1664525, s) + 1013904223) >>> 0; return s / 0x100000000; }; } const LABELS: DoctrineLabel[] = ["Bot", "L1", "L2", "Top"]; function pickLabel(rand: () => number): DoctrineLabel { return LABELS[Math.floor(rand() * 4)]; } const mockSigner: Signer = (p: string) => `mock-sig::${Buffer.from(p).slice(0, 16).toString("hex")}`; // --------------------------------------------------------------------------- // 1. 1000-input random doctrine preservation test // --------------------------------------------------------------------------- describe("th1_composition: 1000-input random composition_preserves_doctrine", () => { it("composed system is doctrine-locked for all valid compatible pairs", () => { const rand = seedRandom(0xabcdef12); let failures = 0; for (let i = 0; i < 1000; i++) { const th = pickLabel(rand); // Build valid S1: threshold ≤ inputLabel ≤ outputLabel, threshold ≤ outputLabel const thRank = ["Bot", "L1", "L2", "Top"].indexOf(th); const validLabels = LABELS.slice(thRank); // all labels ≥ th if (validLabels.length < 2) continue; const inLabel1 = validLabels[Math.floor(rand() * validLabels.length)]; const inRank1 = LABELS.indexOf(inLabel1); const validOut1 = LABELS.slice(Math.max(inRank1, thRank)); const outLabel1 = validOut1[Math.floor(rand() * validOut1.length)]; // S2 must have inputLabel ≥ outLabel1 const s2MinInRank = LABELS.indexOf(outLabel1); const validIn2 = LABELS.slice(Math.max(s2MinInRank, thRank)); if (validIn2.length === 0) continue; const inLabel2 = validIn2[Math.floor(rand() * validIn2.length)]; const inRank2 = LABELS.indexOf(inLabel2); const validOut2 = LABELS.slice(Math.max(inRank2, thRank)); const outLabel2 = validOut2[Math.floor(rand() * validOut2.length)]; const s1: LutarSystem = { threshold: th, inputLabel: inLabel1, outputLabel: outLabel1 }; const s2: LutarSystem = { threshold: th, inputLabel: inLabel2, outputLabel: outLabel2 }; if (!verifyCompositionPreservesDoctrine(s1, s2)) failures++; } expect(failures).toBe(0); }); }); // --------------------------------------------------------------------------- // 2. Lattice order (labelLE) edge cases // --------------------------------------------------------------------------- describe("th1_composition: labelLE lattice order", () => { it("labelLE is reflexive", () => { for (const l of LABELS) { expect(labelLE(l, l)).toBe(true); } }); it("labelLE is transitive", () => { const pairs: [DoctrineLabel, DoctrineLabel, DoctrineLabel][] = [ ["Bot", "L1", "L2"], ["Bot", "L1", "Top"], ["Bot", "L2", "Top"], ["L1", "L2", "Top"], ]; for (const [a, b, c] of pairs) { expect(labelLE(a, b) && labelLE(b, c) && labelLE(a, c)).toBe(true); } }); it("Bot ≤ everything", () => { for (const l of LABELS) expect(labelLE("Bot", l)).toBe(true); }); it("everything ≤ Top", () => { for (const l of LABELS) expect(labelLE(l, "Top")).toBe(true); }); it("L2 > L1 (not L2 ≤ L1)", () => { expect(labelLE("L2", "L1")).toBe(false); }); it("Top > L2 (not Top ≤ L2)", () => { expect(labelLE("Top", "L2")).toBe(false); }); }); // --------------------------------------------------------------------------- // 3. Compatible / incompatible system pairs // --------------------------------------------------------------------------- describe("th1_composition: compatible and compose", () => { it("compatible: L1 output → L1 input → true", () => { const s1: LutarSystem = { threshold: "Bot", inputLabel: "Bot", outputLabel: "L1" }; const s2: LutarSystem = { threshold: "Bot", inputLabel: "L1", outputLabel: "L2" }; expect(compatible(s1, s2)).toBe(true); }); it("incompatible: L2 output → L1 input → false", () => { const s1: LutarSystem = { threshold: "Bot", inputLabel: "Bot", outputLabel: "L2" }; const s2: LutarSystem = { threshold: "Bot", inputLabel: "L1", outputLabel: "L2" }; expect(compatible(s1, s2)).toBe(false); }); it("compose produces correct input/output labels", () => { const s1: LutarSystem = { threshold: "L1", inputLabel: "L1", outputLabel: "L2" }; const s2: LutarSystem = { threshold: "L1", inputLabel: "L2", outputLabel: "Top" }; const result = compose(s1, s2); expect(result.inputLabel).toBe("L1"); expect(result.outputLabel).toBe("Top"); expect(result.threshold).toBe("L1"); }); it("compose throws on threshold mismatch", () => { const s1: LutarSystem = { threshold: "L1", inputLabel: "L1", outputLabel: "L2" }; const s2: LutarSystem = { threshold: "L2", inputLabel: "L2", outputLabel: "Top" }; expect(() => compose(s1, s2)).toThrow("threshold mismatch"); }); it("compose throws when incompatible (output > input)", () => { const s1: LutarSystem = { threshold: "Bot", inputLabel: "Bot", outputLabel: "L2" }; const s2: LutarSystem = { threshold: "Bot", inputLabel: "L1", outputLabel: "L2" }; expect(() => compose(s1, s2)).toThrow("incompatible"); }); }); // --------------------------------------------------------------------------- // 4. composeList // --------------------------------------------------------------------------- describe("th1_composition: composeList", () => { it("composeList of single system returns it unchanged", () => { const s: LutarSystem = { threshold: "L1", inputLabel: "L1", outputLabel: "L2" }; const result = composeList([s]); expect(result).toEqual(s); }); it("composeList of chain [Bot→L1] → [L1→L2] → [L2→Top]", () => { const chain: LutarSystem[] = [ { threshold: "Bot", inputLabel: "Bot", outputLabel: "L1" }, { threshold: "Bot", inputLabel: "L1", outputLabel: "L2" }, { threshold: "Bot", inputLabel: "L2", outputLabel: "Top" }, ]; const result = composeList(chain); expect(result.inputLabel).toBe("Bot"); expect(result.outputLabel).toBe("Top"); expect(isDoctrineLockedSystem(result)).toBe(true); }); it("composeList throws on empty list", () => { expect(() => composeList([])).toThrow("empty list"); }); }); // --------------------------------------------------------------------------- // 5. Receipt emission // --------------------------------------------------------------------------- describe("th1_composition: DSSE receipt emission", () => { const s1: LutarSystem = { threshold: "L1", inputLabel: "L1", outputLabel: "L2" }; const s2: LutarSystem = { threshold: "L1", inputLabel: "L2", outputLabel: "Top" }; it("emits receipt with correct theorem and commit SHA", () => { const receipt = emitTH1CompositionReceipt(s1, s2, mockSigner); expect(receipt.theorem).toBe( "Lutar.Composition.composition_preserves_doctrine" ); expect(receipt.lean_commit_sha).toBe("c4d13795689601324fce0236351bfe0ade990a43"); expect(receipt.output).toBe(true); expect(receipt.inputs_hash).toMatch(/^[0-9a-f]{64}$/); expect(receipt.sig).toContain("mock-sig::"); }); it("receipt output = false for incompatible systems", () => { const bad: LutarSystem = { threshold: "L1", inputLabel: "L1", outputLabel: "L1" }; const incompatible: LutarSystem = { threshold: "L1", inputLabel: "L2", outputLabel: "Top" }; const receipt = emitTH1CompositionReceipt(bad, incompatible, mockSigner); expect(receipt.output).toBe(false); }); it("gate returns composedSystem and doctrinePreserved=true", () => { const { composedSystem, doctrinePreserved, receipt } = th1CompositionGate(s1, s2, mockSigner); expect(composedSystem).not.toBeNull(); expect(composedSystem?.inputLabel).toBe("L1"); expect(composedSystem?.outputLabel).toBe("Top"); expect(doctrinePreserved).toBe(true); expect(receipt.output).toBe(true); }); it("deterministic inputs_hash", () => { const r1 = emitTH1CompositionReceipt(s1, s2, mockSigner); const r2 = emitTH1CompositionReceipt(s1, s2, mockSigner); expect(r1.inputs_hash).toBe(r2.inputs_hash); }); });