/** * Progressed investigation fixture apparatus verification. * * Proves that the canonical progressed-investigation fixture: * 1. loads successfully as valid JSON * 2. contains >= 1 admitted known/supported evidence item * 3. contains >= 1 unresolved Open Question (kind=unknown, status=unknown) * 4. the unresolved Open Question is not admitted evidence * 5. at least one eligible Finding exists (null disposition) * 6. unresolved Open Question is not represented as an eligible Finding * 7. resolvedNodeIds does NOT include any unknown node id */ import { describe, it, expect } from "vitest"; import fixture from "../fixtures/progressed-investigation-manufacturing.json"; describe("Progressed investigation fixture apparatus", () => { /* ── 1. Fixture loads successfully ─────────────────── */ it("loads as valid JSON with required top-level keys", () => { expect(fixture).toHaveProperty("success", true); expect(fixture).toHaveProperty("stage", "update_applied"); expect(fixture).toHaveProperty("situationGraph"); expect(fixture.situationGraph).toHaveProperty("nodes"); expect(fixture.situationGraph).toHaveProperty("edges"); expect(fixture.situationGraph).toHaveProperty("activeUnknownNodeId"); expect(fixture.situationGraph).toHaveProperty("resolvedNodeIds"); expect(fixture.situationGraph).toHaveProperty("centralStatement"); }); /* ── 2. Contains admitted evidence (>= 1 supported/known node) ─── */ it("contains at least one admitted known or supported evidence node", () => { const admitted = fixture.situationGraph.nodes.filter( (n) => n.status === "supported" || n.status === "known" ); expect(admitted.length).toBeGreaterThan(0); }); it("admitted nodes are not unknown kind", () => { const admitted = fixture.situationGraph.nodes.filter( (n) => ["supported", "known"].includes(n.status) ); for (const node of admitted) { expect(node.kind).not.toBe("unknown"); } }); /* ── 3. Contains unresolved Open Question (>= 1) ─────── */ it("contains at least one unresolved Open Question node", () => { const unresolved = fixture.situationGraph.nodes.filter( (n) => n.kind === "unknown" && n.status === "unknown" ); expect(unresolved.length).toBeGreaterThan(0); }); /* ── 4. Unresolved Open Question is not admitted evidence ─── */ it("no node is simultaneously unresolved unknown AND admitted evidence", () => { const allNodes = fixture.situationGraph.nodes; for (const node of allNodes) { if (node.kind === "unknown" && node.status === "unknown") { // An unresolved unknown must not have admitted status expect(["supported", "known"]).not.toContain(node.status); } } }); it("unresolved and admitted node ID sets are disjoint", () => { const unresolvedIds = new Set( fixture.situationGraph.nodes .filter((n) => n.kind === "unknown" && n.status === "unknown") .map((n) => n.id) ); const admittedIds = new Set( fixture.situationGraph.nodes .filter((n) => ["supported", "known"].includes(n.status)) .map((n) => n.id) ); for (const id of unresolvedIds) { expect(admittedIds.has(id)).toBe(false); } }); it("unresolved unknown nodes are distinct from admitted evidence", () => { const unresolvedIds = new Set( fixture.situationGraph.nodes .filter((n) => n.kind === "unknown" && n.status === "unknown") .map((n) => n.id) ); const admittedIds = new Set( fixture.situationGraph.nodes .filter((n) => ["supported", "known"].includes(n.status)) .map((n) => n.id) ); for (const id of unresolvedIds) { expect(admittedIds.has(id)).toBe(false); } }); /* ── 5. Unresolved question excluded from resolvedNodeIds ─── */ it("resolvedNodeIds does not contain any unresolved unknown node id", () => { const unresolvedIds = fixture.situationGraph.nodes .filter((n) => n.kind === "unknown" && n.status === "unknown") .map((n) => n.id); for (const nodeId of unresolvedIds) { expect(fixture.situationGraph.resolvedNodeIds).not.toContain(nodeId); } }); /* ── 6. Eligible Finding exists ─────────────────────── */ it("findings array exists and has eligible items", () => { expect(fixture.findings).toBeDefined(); expect(Array.isArray(fixture.findings)).toBe(true); const eligible = fixture.findings.filter( (f) => f.status === "provisional" && f.userDisposition === null ); expect(eligible.length).toBeGreaterThan(0); }); /* ── 7. Unresolved Open Question is not represented as a Finding ─── */ it("unresolved unknown nodes are not mapped to findings propositions", () => { const unresolvedNodeIds = new Set( fixture.situationGraph.nodes .filter((n) => n.kind === "unknown" && n.status === "unknown") .map((n) => n.id) ); // originTargetNodeId links findings to unknown nodes // A finding whose originatingTargetNodeId is an unresolved unknown is fine // but the proposition should not BE the same as the node label const eligibleFindings = fixture.findings.filter( (f) => f.status === "provisional" && f.userDisposition === null ); const unresolvedNodes = fixture.situationGraph.nodes.filter( (n) => n.kind === "unknown" && n.status === "unknown" ); for (const finding of eligibleFindings) { // The proposition is an admitted evidence item, not the unknown itself expect(finding.proposition).not.toContain("Whether"); // Verify the originating node is NOT resolved — it remains open const targetNode = unresolvedNodes.find( (n) => n.id === finding.originatingTargetNodeId ); if (targetNode) { expect(targetNode.status).toBe("unknown"); expect(targetNode.kind).toBe("unknown"); } } }); /* ── 8. Canonical shape integrity ──────────────────── */ it("activeUnknownNodeId references a valid unresolved unknown node", () => { const unresolvedIds = fixture.situationGraph.nodes .filter((n) => n.kind === "unknown" && n.status === "unknown") .map((n) => n.id); expect(unresolvedIds).toContain(fixture.situationGraph.activeUnknownNodeId); }); it("all nodes pass the canonical situationNode schema invariants", () => { for (const node of fixture.situationGraph.nodes) { expect(node).toHaveProperty("id"); expect(node).toHaveProperty("label"); expect(node).toHaveProperty("description"); expect(["observation","reported_claim","metric","state","transition","relationship","assumption","unknown","conclusion","option"]).toContain(node.kind); expect(["known","unknown","provisional","supported","weakened","contradicted","resolved"]).toContain(node.status); expect(["low","medium","high"]).toContain(node.confidence); expect(Array.isArray(node.evidenceIds)).toBe(true); expect(Array.isArray(node.dependsOn)).toBe(true); expect(Array.isArray(node.affects)).toBe(true); } }); it("all edges reference valid nodes", () => { const nodeIds = new Set(fixture.situationGraph.nodes.map((n) => n.id)); for (const edge of fixture.situationGraph.edges) { expect(edge).toHaveProperty("id"); expect(nodeIds.has(edge.fromNodeId)).toBe(true); expect(nodeIds.has(edge.toNodeId)).toBe(true); expect(edge).toHaveProperty("relationship"); expect(edge).toHaveProperty("confidence"); } }); });