177 lines
7.5 KiB
JavaScript
177 lines
7.5 KiB
JavaScript
/**
|
|
* RTO.A4 — Real captured fixture apparatus verification.
|
|
*
|
|
* Proves the bounded projection boundary:
|
|
* 1. source fixture is the real captured JSON
|
|
* 2. projected situationGraph is the captured updatedSituationGraph
|
|
* 3. exactly 3 unresolved unknowns are derived from real graph state
|
|
* 4. their IDs/labels match the captured nodes
|
|
* 5. recommended item derives from selectedQuestion.nodeId
|
|
* 6. diagnostics recommendation agrees for this fixture
|
|
* 7. no synthetic availableThreads/recommendedThreadId data is required
|
|
* 8. any presentation selection remains outside situationGraph
|
|
* 9. active/reasoning ownership fields are not mutated
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import rawFixture from "../fixtures/live-product-launch-update-response.json";
|
|
|
|
import { buildScenarioFixture } from "@/lib/mocks/scenarios.js";
|
|
|
|
const projected = buildScenarioFixture("rto-real-product-launch", 0);
|
|
|
|
describe("RTO.A4 real captured fixture apparatus", () => {
|
|
/* ── 1. Source fixture is the real captured JSON ─────────── */
|
|
|
|
it("uses the live-product-launch-update-response.json as source", () => {
|
|
expect(projected._sourceFixturePath).toBe("../../tests/fixtures/live-product-launch-update-response.json");
|
|
});
|
|
|
|
/* ── 2. Projected situationGraph is the captured updatedSituationGraph ─── */
|
|
|
|
it("projects situationGraph from fixture.updatedSituationGraph", () => {
|
|
// projected.situationGraph is the SAME object as projected.updatedSituationGraph (alias)
|
|
expect(projected.situationGraph).toBe(projected.updatedSituationGraph);
|
|
});
|
|
|
|
it("preserves centralStatement from captured graph", () => {
|
|
expect(projected.situationGraph.centralStatement.length).toBeGreaterThan(100);
|
|
});
|
|
|
|
/* ── 3. Exactly 3 unresolved unknowns derived from real graph state ─── */
|
|
|
|
it("derives exactly 3 unresolved unknowns", () => {
|
|
const unresolved = projected.situationGraph.nodes.filter(
|
|
(n) => n.kind === "unknown" && n.status !== "resolved"
|
|
);
|
|
expect(unresolved.length).toBe(3);
|
|
});
|
|
|
|
/* ── 4. IDs/labels match the captured nodes ──────────────── */
|
|
|
|
it("resolves to the three known unresolved node IDs", () => {
|
|
const resolved = JSON.stringify(rawFixture.updatedSituationGraph.nodes
|
|
.filter(n => n.kind === "unknown" && n.status !== "resolved")
|
|
.map(n => n.id)
|
|
.sort());
|
|
expect(resolved).toBe(JSON.stringify(["nR4vL9w", "ntpt9ki", "nxmeiab"].sort()));
|
|
});
|
|
|
|
it("labels come directly from captured nodes, not synthesized", () => {
|
|
const unresolved = projected.situationGraph.nodes.filter(
|
|
(n) => n.kind === "unknown" && n.status !== "resolved"
|
|
);
|
|
const labels = unresolved.map((n) => n.label);
|
|
expect(labels).toContain(
|
|
"Probability or current status of the large enterprise customer signing their contract before launch or within the year"
|
|
);
|
|
expect(labels).toContain(
|
|
"Whether competitors are actively developing similar products and how soon they might release them"
|
|
);
|
|
expect(labels).toContain(
|
|
"Sufficiency of committed or highly probable revenue from other customers to cover £300k cost and yield acceptable return"
|
|
);
|
|
});
|
|
|
|
/* ── 5. Recommended item derives from selectedQuestion.nodeId ─── */
|
|
|
|
it("derives recommendation from selectedQuestion.nodeId", () => {
|
|
expect(projected.selectedQuestion.nodeId).toBe("nR4vL9w");
|
|
});
|
|
|
|
it("recommendation matches one of the unresolved graph nodes", () => {
|
|
const resolved = projected.situationGraph.nodes.find(
|
|
(n) => n.id === projected.selectedQuestion.nodeId && n.kind === "unknown"
|
|
);
|
|
expect(resolved).toBeDefined();
|
|
expect(resolved.label).toContain("Sufficiency of committed or highly probable revenue");
|
|
});
|
|
|
|
/* ── 6. Diagnostics recommendation agrees for this fixture ── */
|
|
|
|
it("diagnostics.unknownSelectionExplanation.selectedNodeId agrees with selectedQuestion", () => {
|
|
expect(projected.diagnostics.unknownSelectionExplanation.selectedNodeId).toBe("nR4vL9w");
|
|
expect(projected.selectedQuestion.nodeId).toBe(
|
|
projected.diagnostics.unknownSelectionExplanation.selectedNodeId
|
|
);
|
|
});
|
|
|
|
/* ── 7. No synthetic availableThreads/recommendedThreadId required ─── */
|
|
|
|
it("does not require synthetic reasoning fields to establish the open set", () => {
|
|
// The open set comes from situationGraph.nodes, not from synthetic data
|
|
const graphUnknowns = projected.situationGraph.nodes.filter(
|
|
(n) => n.kind === "unknown" && n.status !== "resolved"
|
|
);
|
|
expect(graphUnknowns.length).toBe(3);
|
|
// No _experimental or synthetic thread fields on situationGraph
|
|
expect(projected.situationGraph._experimental).toBeUndefined();
|
|
});
|
|
|
|
it("does not introduce synthetic recommendationOrder, questionFrame, thread confidence, or priority", () => {
|
|
const unresolved = projected.situationGraph.nodes.filter(
|
|
(n) => n.kind === "unknown" && n.status !== "resolved"
|
|
);
|
|
for (const node of unresolved) {
|
|
expect(node).not.toHaveProperty("recommendationOrder");
|
|
expect(node).not.toHaveProperty("questionFrame");
|
|
}
|
|
});
|
|
|
|
/* ── 8. Presentation selection remains outside situationGraph ── */
|
|
|
|
it("keeps presentation-only state separate from situationGraph", () => {
|
|
expect(projected.situationGraph.selectedPresentationItemId).toBeUndefined();
|
|
});
|
|
|
|
it("activeUnknownNodeId is not mutated by the projection", () => {
|
|
expect(projected.situationGraph.activeUnknownNodeId).toBe(
|
|
rawFixture.updatedSituationGraph.activeUnknownNodeId
|
|
);
|
|
});
|
|
|
|
it("selectedQuestion is preserved as captured, not rewritten", () => {
|
|
// selectedQuestion comes directly from the fixture — check key fields
|
|
expect(projected.selectedQuestion.nodeId).toBe(rawFixture.selectedQuestion.nodeId);
|
|
expect(projected.selectedQuestion.question).toBe(rawFixture.selectedQuestion.question);
|
|
});
|
|
|
|
/* ── 9. No production reasoning or ownership mutation ─────── */
|
|
|
|
it("does not modify activeUnknownNodeId for presentation selection", () => {
|
|
const resolved = projected.situationGraph.resolvedNodeIds || [];
|
|
expect(resolved).toEqual([]);
|
|
});
|
|
|
|
it("resolvedNodeIds matches captured state (empty)", () => {
|
|
const resolved = projected.situationGraph.resolvedNodeIds || [];
|
|
expect(resolved.length).toBe(0);
|
|
});
|
|
|
|
/* ── Additional boundary checks ─────────────────────────── */
|
|
|
|
it("preserves success and stage from fixture", () => {
|
|
expect(projected.success).toBe(true);
|
|
expect(projected.stage).toBe("update_applied");
|
|
});
|
|
|
|
it("newlySurfacedNodeIds derives from proposal.addedNodes (unknown kind)", () => {
|
|
const expected = rawFixture.proposal.addedNodes
|
|
.filter(n => n.kind === "unknown")
|
|
.map(n => n.id);
|
|
expect(projected.newlySurfacedNodeIds).toEqual(expected);
|
|
});
|
|
|
|
it("selectedQuestion matches captured field exactly", () => {
|
|
expect(projected.selectedQuestion.nodeId).toBe(rawFixture.selectedQuestion.nodeId);
|
|
expect(projected.selectedQuestion.question).toBe(rawFixture.selectedQuestion.question);
|
|
});
|
|
|
|
it("diagnostics object matches captured diagnostics key fields", () => {
|
|
expect(projected.diagnostics.promptVersion).toBe(rawFixture.diagnostics.promptVersion);
|
|
expect(projected.diagnostics.modelName).toBe(rawFixture.diagnostics.modelName);
|
|
expect(projected.diagnostics.validationStatus).toBe(rawFixture.diagnostics.validationStatus);
|
|
expect(projected.diagnostics.nodeCount).toBe(rawFixture.diagnostics.nodeCount);
|
|
});
|
|
});
|