test(confidence-engine): preserve focused investigation evidence seam

This commit is contained in:
2026-10-05 09:35:04 +01:00
parent e996a542b3
commit 8e3f4388d6
2 changed files with 77 additions and 6 deletions
@@ -311,8 +311,8 @@ assert(helperSrc.includes("resolvedNodeIds.add(targetNodeId)") || helperSrc.incl
// E5. Post-episode graph feeds CU synthesis: verified by composeDeepInvestigationCheckpoint's Step 2
assert(helperSrc.includes("synthesizeCurrentUnderstanding"), "helper invokes synthesizeCurrentUnderstanding for CU synthesis");
// E6. Prior + appended Findings accumulated exactly once for CU synthesis
assert(helperSrc.includes("postEpisodeFindings = [...priorFindings, ...accumulatedEpisodes]"), "findings accumulation: prior + appended concatenated exactly once");
// E6. Prior + derived from Contribution + appended Findings accumulated exactly once for CU synthesis
assert(helperSrc.includes("postEpisodeFindings = [...priorFindings, ...newFindingsFromContributions, ...accumulatedEpisodes]") || helperSrc.includes("postEpisodeFindings : [...priorFindings, ...newFindingsFromContributions, ...accumulatedEpisodes]"), "findings accumulation: prior + derived from Contribution + appended concatenated exactly once");
// E7. Next graph returned via updatedSituationGraph in checkpoint object
assert(helperSrc.includes("updatedSituationGraph: updatedGraph") || helperSrc.includes("updatedSituationGraph : updatedGraph"), "updatedSituationGraph returned in checkpoint output");
@@ -323,5 +323,39 @@ assert(helperSrc.includes("postEpisodeFindings: postEpisodeFindings"), "postEpis
// E9. Provider-call accounting preserved (existing behavior)
const extractResult = extractLiveExperimentCheckpoint(successResult(123, 456), successParams);
assert(extractResult.providerGenerateCalls === 1, "providerGenerateCalls preserved from extraction path");
// ---- CONTRIBUTION SEAM VERIFICATION (Iteration 4) ──────────────────────
console.log("\n=== CONTRIBUTION SEAM VERIFICATION ===\n");
// C1. composeDeepInvestigationCheckpoint accepts contributions parameter (defaults to [])
assert(composeDeepInvestigationCheckpoint.length >= 1, "composeDeepInvestigationCheckpoint accepts at least 1 param (contributions default)");
// C2. Production parity: helper derives Contribution from answer when not supplied
assert(helperSrc.includes("_deriveContributionFromAnswer"), "helper has _deriveContributionFromAnswer (mirrors production appendFocusedContribution)");
// C3. Contribution carries targetNodeId matching focused target
assert(helperSrc.includes("targetNodeId: targetNodeId") || helperSrc.includes("targetNodeId : targetNodeId"), "Contribution derives targetNodeId from focused target");
// C4. Contribution fields match production shape
assert(helperSrc.includes("observations:") && helperSrc.includes("answer:"), "Contribution includes observations and answer fields (production shape)");
// C5. deriveFindingsFromContributions is used to derive Findings from new Contribution (same production helper)
assert(helperSrc.includes("deriveFindingsFromContributions"), "helper reuses produce deriveFindingsFromContributions for Finding derivation");
// C6. Effective contributions passed to prepareCompletedEpisode (not empty array)
assert(helperSrc.includes("contributions: effectiveContributions") || helperSrc.includes("contributions : effectiveContributions"), "helper passes effectiveContributions to prepareCompletedEpisode (not empty)");
// C7. Post-episode graph feeds CU synthesis
assert(helperSrc.includes("synthesizeCurrentUnderstanding"), "CU synthesis receives updated graph for full accumulated findings");
// C8. postEpisodeFindings includes prior + derived from new Contribution + appended episodes (exactly-once)
assert(helperSrc.includes("newFindingsFromContributions"), "postEpisodeFindings includes newly derived Findings from Contribution");
// C9. next Contributions returned in checkpoint for longitudinal Episode N+1 state feeding
assert(helperSrc.includes("postEpisodeContributions: effectiveContributions") || helperSrc.includes("postEpisodeContributions : effectiveContributions"), "postEpisodeContributions returned in checkpoint");
// C10. Source code verifies contributions parameter default behavior (empty array)
assert(helperSrc.includes("contributions = []"), "contributions defaults to empty array when not supplied");
console.log("\n--- Total failures: " + failures + " ---\n");
process.exit(failures > 0 ? 1 : 0);
+41 -4
View File
@@ -18,6 +18,9 @@
const dotenv = require("dotenv");
dotenv.config({ path: ".env.local" });
// Re-export production helper for apparatus use — avoids fabricating Finding propositions.
const { deriveFindingsFromContributions } = require("../../lib/graph/finding-helpers.js");
function assertRequiredEnv(name) {
const value = process.env[name];
if (!value) {
@@ -268,7 +271,25 @@ async function runLiveExperiment({ graph, previousQuestion, answer, findings = [
* - currentUnderstanding from the synthesis result
* - findingsCheckpoint with accurate post-episode accumulated findings count
*/
async function composeDeepInvestigationCheckpoint({ situationGraph, previousQuestion, answer, findings = [], targetNodeId, episodeMode }) {
/**
* Deterministically derive a canonical Contribution from the fixed answer
* when the caller does not supply one. Mirrors production appendFocusedContribution
* shape: id, targetNodeId, question, answer, sequence, observations, etc.
*/
function _deriveContributionFromAnswer({ answer, previousQuestion, targetNodeId, sequence }) {
return {
id: `contrib-ep-${String(sequence).padStart(4, "0")}`,
targetNodeId: targetNodeId ?? null,
question: previousQuestion ?? "",
answer: answer ?? "",
sequence: sequence,
observations: [answer],
uncertainties: [],
possibleFollowUpQuestions: [],
};
}
async function composeDeepInvestigationCheckpoint({ situationGraph, previousQuestion, answer, findings = [], contributions = [], targetNodeId, episodeMode }) {
dotenv.config({ path: ".env.local" });
const baseUrl = assertRequiredEnv("OLLAMA_BASE_URL");
@@ -306,6 +327,19 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues
let episodeResult;
/* ── Production-equivalent focused-episode seam: targetNodeId triggers the same path as executeEpisodeDone → handleEpisodeMode ─── */
// Derive a canonical Contribution from the fixed answer when none supplied.
// Mirrors production appendFocusedContribution: user answer → canonical Contribution
const effectiveContributions = contributions.length > 0 ? [...contributions] : [];
let newFindingsFromContributions = [];
if (effectiveContributions.length === 0 && answer && targetNodeId) {
const contribSeq = effectiveContributions.length + 1;
const derivedContrib = _deriveContributionFromAnswer({ answer, previousQuestion, targetNodeId, sequence: contribSeq });
effectiveContributions.push(derivedContrib);
// Derive canonical Findings from the Contribution (production seam)
newFindingsFromContributions = deriveFindingsFromContributions([derivedContrib]).findings;
}
if (episodeMode === true && targetNodeId) {
// Mirrors produceEpisodeAwareGraph: prepareCompletedEpisode → reconsiderCompletedEpisode → applyValidatedProposal
const { prepareCompletedEpisode } = await import("../../lib/graph/episode-preparation.js");
@@ -314,7 +348,7 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues
const prepared = prepareCompletedEpisode({
situationGraph,
targetNodeId,
contributions: [], // no focused contributions in checkpoint helper (mirrors production when called without explicit contributions)
contributions: effectiveContributions,
findings,
});
@@ -365,10 +399,10 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues
/* ── Step 2: Current Understanding synthesis (mirrors production synthesizeFn) ─── */
const updatedGraph = episodeResult.updatedSituationGraph;
// Accumulate prior findings + newly appended Findings for CU synthesis
// Accumulate prior findings + newly derived Findings (from new Contribution) for CU synthesis
const priorFindings = Array.isArray(findings) ? [...findings] : [];
const accumulatedEpisodes = episodeResult.appendedFindings ?? [];
const postEpisodeFindings = [...priorFindings, ...accumulatedEpisodes];
const postEpisodeFindings = [...priorFindings, ...newFindingsFromContributions, ...accumulatedEpisodes];
let currentUnderstanding = null;
if (episodeResult.success && updatedGraph) {
@@ -405,6 +439,9 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues
/* ── updatedSituationGraph (NEW — for longitudinal Episode N+1 state feeding) ─── */
updatedSituationGraph: updatedGraph,
/* ── postEpisodeContributions (NEW — accumulated Contributions for longitudinal tracking) ─── */
postEpisodeContributions: effectiveContributions,
/* ── postEpisodeFindings (NEW — accumulated Findings for Episode N+1) ─── */
postEpisodeFindings: postEpisodeFindings,