test(experiment): ground RTO apparatus in real fixture
This commit is contained in:
+42
-1
@@ -1,10 +1,41 @@
|
||||
/**
|
||||
* Expanded mock scenario library for the Confidence Engine workspace.
|
||||
* Each scenario produces a complete investigation journey through turns.
|
||||
*
|
||||
*
|
||||
* UI-only development work — no reasoning engine changes.
|
||||
*/
|
||||
|
||||
/* ── RTO.A4 real captured fixture (RTO.A1 superseded) ──────────── */
|
||||
|
||||
import fixtureData from "../../tests/fixtures/live-product-launch-update-response.json";
|
||||
|
||||
/**
|
||||
* Thin projection of a real captured Engine update response into the
|
||||
* shape already expected by the workspace/mock client.
|
||||
*
|
||||
* The real captured fixture remains the source of truth — no reasoning
|
||||
* content is recreated or synthesized here.
|
||||
*/
|
||||
function projectRealUpdateFixture(fixture) {
|
||||
return {
|
||||
success: fixture.success,
|
||||
stage: fixture.stage,
|
||||
situationGraph: fixture.updatedSituationGraph,
|
||||
updatedSituationGraph: fixture.updatedSituationGraph,
|
||||
selectedQuestion: fixture.selectedQuestion,
|
||||
newlySurfacedNodeIds: (fixture.proposal?.addedNodes || [])
|
||||
.filter(node => node.kind === "unknown")
|
||||
.map(node => node.id),
|
||||
diagnostics: fixture.diagnostics,
|
||||
noQuestionReason: null,
|
||||
// Mark as experimental so buildScenarioFixture routes through the scenario branch
|
||||
_experimental: { apparatusMode: "rto-real-fixture" },
|
||||
_sourceFixturePath: "../../tests/fixtures/live-product-launch-update-response.json",
|
||||
};
|
||||
}
|
||||
|
||||
const __rtoRealScenario = projectRealUpdateFixture(fixtureData);
|
||||
|
||||
/* ── Node / Edge factories ─────────────────────────────── */
|
||||
|
||||
export function mkN(id, label, opts) {
|
||||
@@ -389,6 +420,11 @@ export var MULTI_THREAD_ITEMS = multiThreadItems;
|
||||
/* ── Registry ─────────────────────────────────────── */
|
||||
|
||||
var SCENARIOS = {
|
||||
"rto-real-product-launch": {
|
||||
scenario: __rtoRealScenario,
|
||||
label: "[RTO.A4] Real captured product-launch update",
|
||||
centralStatement: __rtoRealScenario?.updatedSituationGraph?.centralStatement || "",
|
||||
},
|
||||
"default": { turns: comparisonTurns, label: "Comparison (product ratings)", centralStatement: comparisonTurns[0].centralStatement },
|
||||
"comparison": { turns: comparisonTurns, label: "Comparison (product ratings)", centralStatement: comparisonTurns[0].centralStatement },
|
||||
"contradictory": { turns: contradictoryTurns, label: "Contradictory evidence", centralStatement: contradictoryTurns[0].centralStatement },
|
||||
@@ -409,6 +445,11 @@ export function buildScenarioFixture(scenarioName, turnIdx) {
|
||||
var s = SCENARIOS[scenarioName];
|
||||
if (!s) return null;
|
||||
|
||||
/* RTO.A4 real fixture — returned as-is (already projected) */
|
||||
if (s.scenario && s.scenario._sourceFixturePath) {
|
||||
return s.scenario;
|
||||
}
|
||||
|
||||
/* Experimental multi-thread — does not follow the turns model */
|
||||
if (s.scenario && s.scenario._experimental) {
|
||||
var mt = s.scenario;
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user