test(confidence-engine): extract focused target input for deterministic verification

This commit is contained in:
2026-10-09 16:44:09 +01:00
parent acf0665f23
commit a54b36a2b2
4 changed files with 140 additions and 5 deletions
@@ -891,6 +891,114 @@ console.log("\n=== SAVE FOCUSED INVESTIGATION EVIDENCE ===\n");
console.log("\n=== SAVE EVIDENCE NEGATIVE TESTS COMPLETE — ALL ASSERTIONS PASSED ===\n");
}
// ---- OBSERVATION: targetDescription initialization boundary (Iteration 9) ───
// Verify that focusedInvestigationInput.targetDescription assignment is scoped inside
// the deconstruction block and cannot escape to the return object independently.
console.log("\n=== TARGET DESCRIPTION BOUNDARY ===\n");
{
const { writeFileSync, readFileSync, unlinkSync } = require("fs");
const os = require("os");
const path = require("path");
const helperSrc2 = readFileSync("./tests/graph/live-update-experiment-helper.cjs", "utf8");
// T1. targetDescription is declared with let (not const) — must be assigned later
assert(helperSrc2.includes("let targetDescription") || helperSrc2.includes("let targetDescription "),
"T1: targetDescription declared as mutable let");
// T2. Initialize to empty string before any deconstruction block
const initIdx = helperSrc2.indexOf('targetDescription = ""');
assert(initIdx >= 0, "T2: targetDescription initialized to \"\" before deconstruction block");
// T3. Target node lookup uses situationGraph.nodes.find — requires graph.nodes array with .id property
assert(helperSrc2.includes("situationGraph.nodes.find") && helperSrc2.includes("n.id === targetNodeId"),
"T3: target node lookup from situationGraph.nodes by id");
// T4. Assignment to targetDescription is ONLY inside the deconstruction condition block
const descBlockStart = helperSrc2.indexOf('if (effectiveContributionsForTarget.length === 0 && answer && targetNodeId)');
assert(descBlockStart >= 0, "T4: deconstruction guard condition present");
const nextElseIdx = helperSrc2.indexOf("} else {", descBlockStart);
const blockEnd = helperSrc2.indexOf("\n\n ", descBlockStart + 5);
const blockContent = helperSrc2.substring(descBlockStart, Math.min(nextElseIdx > descBlockStart ? nextElseIdx : blockEnd, helperSrc2.length));
assert(blockContent.includes("targetDescription = targetNode?.description"),
"T4: assignment to targetDescription inside deconstruction block only");
// T5. Return value reads targetDescription ?? "" — never the source node directly
assert(helperSrc2.includes("targetDescription: targetDescription ?? \"\"") || helperSrc2.includes('targetDescription: targetDescription ?? ""'),
"T5: return reads from variable (not direct lookup)");
// T6. When answer is falsy, deconstruction block DOES NOT fire — targetDescription remains ""
const guardCondition = "effectiveContributionsForTarget.length === 0 && answer && targetNodeId";
assert(helperSrc2.includes(guardCondition), "T6: guard requires truthy answer for block entry");
// T7. When targetNodeId is null/undefined, deconstruction block DOES NOT fire — targetDescription remains ""
assert(helperSrc2.includes("targetNodeId"), "T7: guard includes targetNodeId check");
// T8. effectiveContributionsForTarget = [] when no contributions (empty path triggers deconstruct)
const effInitLine = helperSrc2.indexOf("effectiveContributionsForTarget = effectiveContributions");
assert(helperSrc2.substring(effInitLine, effInitLine + 200).includes("effectiveContributions"),
"T8: effectiveContributionsForTarget defaults to contributions (empty when no contributions)");
console.log("\n=== TARGET DESCRIPTION BOUNDARY OBSERVATION COMPLETE — ALL ASSERTIONS PASSED ===\n");
}
// ---- EXPERIMENT 3: extractFocusedTargetInput deterministic test fixture ────
// Deterministic pure-function test using graphUnknownNodes values from HS-03C-result.json.
// This is a deterministic test fixture, NOT a reconstruction of the original Experiment 9 runtime graph.
console.log("\n=== EXTRACT FOCUSED TARGET INPUT — DETERMINISTIC TEST ===\n");
{
const { extractFocusedTargetInput } = require("./live-update-experiment-helper.cjs");
// Test graph constructed from known HS-03C graphUnknownNodes values (deterministic fixture)
const testGraph = {
nodes: [
{ id: "n2yzwlv", label: "Timeline and feasibility of recruiting two specialists within the nine-month window", description: "Timeline and feasibility of recruiting two specialists within the nine-month window", status: "unknown" },
{ id: "n16fjvw", label: "Timeline for designing, testing, and deploying the software integration without exceeding the nine-month window", description: "Timeline for designing, testing, and deploying the software integration without exceeding the nine-month window", status: "unknown" },
{ id: "n2bmvqz", label: "Volume and activation timeline of customer commitments needed to cover operating costs at launch", description: "Volume and activation timeline of customer commitments needed to cover operating costs at launch", status: "unknown" },
{ id: "nb4o17s", label: "Nature and magnitude of dependencies between the three requirements that could cause cascading timeline delays", description: "Nature and magnitude of dependencies between the three requirements that could cause cascading timeline delays", status: "unknown" },
],
edges: [],
};
const CANONICAL_DESCRIPTION = "Timeline and feasibility of recruiting two specialists within the nine-month window";
const CANONICAL_LABEL = "Timeline and feasibility of recruiting two specialists within the nine-month window";
// 1. The selected node returns the exact canonical description
const result = extractFocusedTargetInput(testGraph, "n2yzwlv");
assert(result.targetDescription === CANONICAL_DESCRIPTION, "Assert 1: known node returns exact canonical description");
assert(result.targetLabel === CANONICAL_LABEL, "Assert 2: known node label preserved exactly");
// 3. An unknown node ID returns the existing empty-string fallbacks
const unknownResult = extractFocusedTargetInput(testGraph, "nonexistent-node-id");
assert(unknownResult.targetDescription === "", "Assert 3a: unknown node description is empty string fallback");
assert(unknownResult.targetLabel === "", "Assert 3b: unknown node label is empty string fallback");
// 4. The graph is not mutated by the extraction
const nodesBefore = testGraph.nodes.map(n => n.id);
extractFocusedTargetInput(testGraph, "n2yzwlv");
extractFocusedTargetInput(testGraph, "nonexistent-node-id");
const nodesAfter = testGraph.nodes.map(n => n.id);
assert(JSON.stringify(nodesBefore) === JSON.stringify(nodesAfter),
"Assert 4: graph not mutated by extraction (node ids unchanged)");
// 5. The focused-deconstruction branch uses the extracted function (source-pattern assertion, preserved from T3)
const helperSrc3 = readFileSync("./tests/graph/live-update-experiment-helper.cjs", "utf8");
assert(helperSrc3.includes("extractFocusedTargetInput(situationGraph, targetNodeId)"),
"Assert 5: focused-deconstruction path calls extractFocusedTargetInput (extraction integration verified)");
// 6. Existing question/answer handling remains unchanged (source-pattern assertion from existing tests preserved)
assert(helperSrc3.includes("previousQuestion ?? \"\"") || helperSrc3.includes("previousQuestion ?? ''"),
"Assert 6: question handling with previousQuestion ?? fallback still present");
assert(helperSrc3.includes('focusedInvestigationInput:') && helperSrc3.includes('answer'),
"Assert 6b: focusedInvestigationInput field still carries answer in returned checkpoint");
console.log("\n=== EXTRACT FOCUSED TARGET INPUT — DETERMINISTIC TEST COMPLETE — ALL ASSERTIONS PASSED ===\n");
}
// ---- SYNTHETIC TESTS WITH FAKE PROVIDER (Batch 3 Iteration 1) ────────────
// Tests synthesizeCurrentUnderstanding directly with DI-injected fake providers.
// Zero live calls — provider.generateReconstruction returns synchronously.
+26 -5
View File
@@ -331,10 +331,9 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues
/* ── Load production deconstruction seam (dynamic import to resolve ESM path aliases) ─── */
const { focusedDeconstruct } = await import("../../lib/graph/focused-investigation.js");
// Extract node context for deconstruction prompt
const targetNode = situationGraph.nodes.find(n => n.id === targetNodeId);
const targetLabel = targetNode?.label ?? "";
targetDescription = targetNode?.description ?? "";
// Extract node context for deconstruction prompt (reuses pure extraction function)
const { targetLabel, targetDescription: _td } = extractFocusedTargetInput(situationGraph, targetNodeId);
targetDescription = _td;
const centralStatement = situationGraph.centralStatement ?? "";
// Call production-focused deconstruct — exactly one provider generation
@@ -677,4 +676,26 @@ function saveFocusedInvestigationEvidence(checkpoint, identity, metadata, output
return { output_path: outputPath, verifiedEvidence: recovered };
}
module.exports = { runLiveExperiment, extractLiveExperimentCheckpoint, _extractReasoningState, composeDeepInvestigationCheckpoint, serializeFocusedInvestigationEvidence, saveFocusedInvestigationEvidence };
/**
* Extract the focused-investigation target-node input (label and description)
* from a situationGraph by node id.
*
* Pure function — no side effects, no I/O, no model calls.
* Mirrors exactly the lookup and fallback semantics used inside the
* guarded focused-deconstruction path of composeDeepInvestigationCheckpoint:
* - situationGraph.nodes.find(n => n.id === targetNodeId)
* - ?? "" (empty-string fallback when node not found)
*
* @param {object} situationGraph — SituationGraph input containing { nodes, centralStatement? }
* @param {string} targetNodeId — id of the focused-investigation target node
* @returns {{ targetLabel: string, targetDescription: string }}
*/
function extractFocusedTargetInput(situationGraph, targetNodeId) {
const targetNode = situationGraph.nodes.find(n => n.id === targetNodeId);
return {
targetLabel: targetNode?.label ?? "",
targetDescription: targetNode?.description ?? "",
};
}
module.exports = { runLiveExperiment, extractLiveExperimentCheckpoint, _extractReasoningState, composeDeepInvestigationCheckpoint, serializeFocusedInvestigationEvidence, saveFocusedInvestigationEvidence, extractFocusedTargetInput };