tooling: expose closure metadata in live harness

This commit is contained in:
2026-08-14 09:30:55 +01:00
parent fa821a53dd
commit 831e395511
2 changed files with 86 additions and 1 deletions
@@ -343,6 +343,16 @@ async function runUpdateOnlyMode() {
}
}
// ── Capture explicit closure metadata from accepted update ─
const finalActiveUnknownNodeId =
updatedGraph?.activeUnknownNodeId === undefined
? null
: updatedGraph.activeUnknownNodeId;
console.log(
`finalActiveUnknownNodeId: ${JSON.stringify(finalActiveUnknownNodeId)}`,
);
console.log(`finalSelectedQuestion: ${JSON.stringify(sq)}`);
// ── Capture persistent graph after update ───────────────
const pNodes = updatedGraph?.nodes ?? [];
const pEdges = updatedGraph?.edges ?? [];
@@ -156,6 +156,8 @@ function runPreAnchoredSimulationWithFixture(cfg) {
proposal: proposal ? { addedNodes: proposal.addedNodes ?? [], addedEdges: proposal.addedEdges ?? [], updatedNodes: proposal.updatedNodes ?? [], resolvedUnknownNodeIds: proposal.resolvedUnknownNodeIds ?? [] } : null,
structuralActionRequired: sar,
selectedQuestion: sq && typeof sq === "object" ? { question: sq.question, nodeId: sq.nodeId } : null,
finalActiveUnknownNodeId: uj.updatedSituationGraph?.activeUnknownNodeId === undefined ? null : uj.updatedSituationGraph.activeUnknownNodeId,
finalSelectedQuestion: sq,
graph: { nodes: uj.updatedSituationGraph?.nodes ?? [], edges: uj.updatedSituationGraph?.edges ?? [] },
},
capturedUpdateBody,
@@ -477,6 +479,68 @@ describe("reproduce-multi-turn-investigation harness: one-shot semantics", () =>
expect(graph.edges[0].relationship).toBe("depends_on");
});
it("accepted Update exposes explicit null finalActiveUnknownNodeId", () => {
const r = runSimulationWithResponseShape({
scenario: "test_ok",
maxUpdates: 1,
answers: ["good answer"],
onResponseUpdate: () => ({
success: true,
stage: "update_applied",
updatedSituationGraph: { nodes: [], edges: [], activeUnknownNodeId: null },
selectedQuestion: { question: "q2", nodeId: "n_pending" },
updatedProposal: { addedNodes: [], addedEdges: [], updatedNodes: [], resolvedUnknownNodeIds: [] },
}),
});
expect(r.captured?.finalActiveUnknownNodeId).toBeNull();
});
it("accepted Update exposes explicit null finalSelectedQuestion", () => {
const r = runSimulationWithResponseShape({
scenario: "test_ok",
maxUpdates: 1,
answers: ["good answer"],
onResponseUpdate: () => ({
success: true,
stage: "update_applied",
updatedSituationGraph: { nodes: [], edges: [], activeUnknownNodeId: null },
selectedQuestion: null,
updatedProposal: { addedNodes: [], addedEdges: [], updatedNodes: [], resolvedUnknownNodeIds: [] },
}),
});
expect(r.captured?.finalSelectedQuestion).toBeNull();
});
it("accepted Update preserves populated closure metadata unchanged", () => {
const r = runSimulationWithResponseShape({
scenario: "test_ok",
maxUpdates: 1,
answers: ["good answer"],
onResponseUpdate: () => ({
success: true,
stage: "update_applied",
updatedSituationGraph: {
nodes: [],
edges: [],
activeUnknownNodeId: "n_example",
},
selectedQuestion: {
nodeId: "n_example",
question: "What evidence would clarify this example?",
},
updatedProposal: { addedNodes: [], addedEdges: [], updatedNodes: [], resolvedUnknownNodeIds: [] },
}),
});
expect(r.captured?.finalActiveUnknownNodeId).toBe("n_example");
expect(r.captured?.finalSelectedQuestion).toEqual({
nodeId: "n_example",
question: "What evidence would clarify this example?",
});
});
it("rejected Update still exposes rejectedProposalSnapshot", () => {
const r = runSimulation({ scenario: "test_ok", maxUpdates: 1, answers: ["answer_reject"] });
expect(r.startCalls).toBe(1);
@@ -1715,7 +1779,14 @@ function runSimulationWithResponseShape(cfg) {
const answers = cfg.answers || [];
const maxUpdates = typeof cfg.maxUpdates === "number" ? cfg.maxUpdates : 2;
const limit = Math.min(maxUpdates, answers.length);
let captured = { answerMeaning: null, proposal: null, selectedQuestion: null, graph: null };
let captured = {
answerMeaning: null,
proposal: null,
selectedQuestion: null,
finalActiveUnknownNodeId: null,
finalSelectedQuestion: null,
graph: null,
};
for (let i = 0; i < limit; i++) {
const upResp = api.post("/api/cases/update", { situationGraph: graph, previousQuestion: question, answer: answers[i] });
@@ -1747,6 +1818,10 @@ function runSimulationWithResponseShape(cfg) {
const sq = uj.selectedQuestion ?? null;
if (sq && typeof sq === "object") captured.selectedQuestion = { question: sq.question, nodeId: sq.nodeId };
captured.finalSelectedQuestion = sq;
captured.finalActiveUnknownNodeId =
graph?.activeUnknownNodeId === undefined ? null : graph.activeUnknownNodeId;
captured.graph = { nodes: graph?.nodes ?? [], edges: graph?.edges ?? [] };
}