test(confidence-engine): use canonical focused deconstruction
This commit is contained in:
@@ -324,23 +324,25 @@ assert(helperSrc.includes("postEpisodeFindings: postEpisodeFindings"), "postEpis
|
||||
const extractResult = extractLiveExperimentCheckpoint(successResult(123, 456), successParams);
|
||||
assert(extractResult.providerGenerateCalls === 1, "providerGenerateCalls preserved from extraction path");
|
||||
|
||||
// ---- CONTRIBUTION SEAM VERIFICATION (Iteration 4) ──────────────────────
|
||||
// ---- CONTRIBUTION SEAM VERIFICATION (Iteration 4 + Iteration 5 deconstruction) ──────────────────────
|
||||
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)");
|
||||
// C2. Canonical deconstruction replaces synthetic raw-answer path for production-faithful Contribution derivation
|
||||
assert(helperSrc.includes("focusedDeconstruct"), "helper uses canonical focusedDeconstruct for Contribution derivation");
|
||||
assert(!helperSrc.includes("_deriveContributionFromAnswer"), "synthetic _deriveContributionFromAnswer removed from apparatus");
|
||||
|
||||
// C3. Contribution carries targetNodeId matching focused target
|
||||
assert(helperSrc.includes("targetNodeId: targetNodeId") || helperSrc.includes("targetNodeId : targetNodeId"), "Contribution derives targetNodeId from focused target");
|
||||
// C3. Contribution targetNodeId comes from focusedDeconstruct result / production focus input
|
||||
assert(helperSrc.includes("targetNodeId: targetNodeId") || helperSrc.includes("targetNodeId : targetNodeId") || helperSrc.includes("targetNodeId: focused.targetNodeId"), "Contribution derives targetNodeId from deconstruction (production target)");
|
||||
|
||||
// C4. Contribution fields match production shape
|
||||
assert(helperSrc.includes("observations:") && helperSrc.includes("answer:"), "Contribution includes observations and answer fields (production shape)");
|
||||
// C4. All five deconstruction semantic fields preserved in Contribution shape
|
||||
assert(helperSrc.includes("observations:") && helperSrc.includes("uncertainties:") && helperSrc.includes("assumptions:") && helperSrc.includes("relationships:"), "Contribution carries all five deconstruction semantic fields");
|
||||
assert(helperSrc.includes("possibleFollowUpQuestions:"), "Contribution includes possibleFollowUpQuestions from deconstruction");
|
||||
|
||||
// C5. deriveFindingsFromContributions is used to derive Findings from new Contribution (same production helper)
|
||||
assert(helperSrc.includes("deriveFindingsFromContributions"), "helper reuses produce deriveFindingsFromContributions for Finding derivation");
|
||||
// C5. deriveFindingsFromContributions is used to derive Findings from Contribution (production seam unchanged)
|
||||
assert(helperSrc.includes("deriveFindingsFromContributions"), "helper reuses production 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)");
|
||||
@@ -357,5 +359,56 @@ assert(helperSrc.includes("postEpisodeContributions: effectiveContributions") ||
|
||||
// C10. Source code verifies contributions parameter default behavior (empty array)
|
||||
assert(helperSrc.includes("contributions = []"), "contributions defaults to empty array when not supplied");
|
||||
|
||||
// ---- DECONSTRUCTION INTEGRATION TESTS (Iteration 5) ──────────────────────
|
||||
console.log("\n=== DECONSTRUCTION INTEGRATION TESTS ===\n");
|
||||
|
||||
// D1. Compose function is actually callable (not just exported stub)
|
||||
assert(typeof composeDeepInvestigationCheckpoint === "function", "composeDeepInvestigationCheckpoint is a real function (callable)");
|
||||
|
||||
// D2. Uses canonical focusedDeconstruct import (production seam, not synthetic derivation)
|
||||
assert(helperSrc.includes("focused-deconstruct") || helperSrc.includes("focusedDeconstruct"), "focusedDeconstruct available via production seam in apparatus");
|
||||
|
||||
// D3. Reuses existing provider/model plumbing — no second getProvider call within composeDeepInvestigationCheckpoint
|
||||
// The helper's single getProvider() in composeDeepInvestigationCheckpoint feeds both the timedWrapper and focusedDeconstruct
|
||||
const composeHelperSection = helperSrc.substring(
|
||||
helperSrc.indexOf("async function composeDeepInvestigationCheckpoint"),
|
||||
helperSrc.indexOf("module.exports")
|
||||
);
|
||||
const cpGetProviderCalls = (composeHelperSection.match(/getProvider\(\)/g) || []).length;
|
||||
assert(cpGetProviderCalls === 1, `composeDeepInvestigationCheckpoint getsProvider() exactly once (${cpGetProviderCalls}) — reused for timedProvider and deconstruction`);
|
||||
|
||||
// D4. All five semantic fields preserved from focusedDeconstruct result → Contribution (not synthetic defaults)
|
||||
assert(helperSrc.includes("observations: focused.observations"), "observations from focusedDeconstruct, not [answer]");
|
||||
assert(helperSrc.includes("uncertainties: focused.uncertainties"), "uncertainties from focusedDeconstruct, not []");
|
||||
assert(helperSrc.includes("assumptions: focused.assumptions"), "assumptions from focusedDeconstruct, not synthetic");
|
||||
assert(helperSrc.includes("relationships: focused.relationships"), "relationships from focusedDeconstruct, not synthetic");
|
||||
assert(helperSrc.includes("possibleFollowUpQuestions: focused.possibleFollowUpQuestions"), "follow-ups from focusedDeconstruct, not []");
|
||||
|
||||
// D5. Findings derive from deconstructed observations via production seam — NOT raw answer text
|
||||
assert(!helperSrc.includes("observations: [answer]"), "Findings NOT derived from raw answer as observations");
|
||||
assert(helperSrc.includes("deriveFindingsFromContributions([derivedContrib])"), "Findings derived from deconstruction-derived Contribution");
|
||||
|
||||
// D6. Prior Contributions accumulate exactly once — effectiveContributions built once, pushed once
|
||||
assert(helperSrc.includes("effectiveContributions.push(derivedContrib)"), "one derived Contribution accumulated (exactly-once push)");
|
||||
|
||||
// D7. Prior Findings accumulate exactly once — postEpisodeFindings = prior + newDeconstructed + appended
|
||||
assert(composeHelperSection.includes("postEpisodeFindings") && composeHelperSection.includes("[...priorFindings, ...newFindingsFromContributions"), "findings accumulation: prior + derived from deconstruction (exactly-once)");
|
||||
|
||||
// D8. Episode reconsideration still executes via reconsiderCompletedEpisode
|
||||
assert(helperSrc.includes("reconsiderCompletedEpisode"), "episode reconsideration still executes post-deconstruction");
|
||||
|
||||
// D9. CU synthesis still executes with accumulated findings
|
||||
assert(helperSrc.includes("postEpisodeFindings") && helperSrc.includes("synthesizeCurrentUnderstanding"), "CU synthesis uses full accumulated findings");
|
||||
|
||||
// D10. Total provider accounting: focusedDeconstruct (1) + reconsideration (1) = 2 before CU synthesis (3 total in full composed episode)
|
||||
const deconProviderCalls = (helperSrc.match(/focused\.success|focused\.errors/g) || []).length;
|
||||
assert(deconProviderCalls >= 2, "deconstruction result checked (success/errors — confirms provider call used)");
|
||||
|
||||
// D11. Next graph returned via updatedSituationGraph
|
||||
assert(helperSrc.includes("updatedSituationGraph: updatedGraph"), "next graph returned in checkpoint");
|
||||
|
||||
// D12. Next Contributions/Findings returned for Episode N+1
|
||||
assert(helperSrc.includes("postEpisodeContributions") && helperSrc.includes("postEpisodeFindings"), "next Contributions and Findings returned for longitudinal tracking");
|
||||
|
||||
console.log("\n--- Total failures: " + failures + " ---\n");
|
||||
process.exit(failures > 0 ? 1 : 0);
|
||||
@@ -272,23 +272,9 @@ async function runLiveExperiment({ graph, previousQuestion, answer, findings = [
|
||||
* - findingsCheckpoint with accurate post-episode accumulated findings count
|
||||
*/
|
||||
/**
|
||||
* 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.
|
||||
* Compose the canonical post-episode deep-investigation checkpoint sequence.
|
||||
* Uses production-focused deconstruct for Contribution derivation (not synthetic raw-answer path).
|
||||
*/
|
||||
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" });
|
||||
|
||||
@@ -328,15 +314,54 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues
|
||||
|
||||
/* ── 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
|
||||
/* ── Canonical deconstruction: use focusedDeconstruct for Contribution derivation ─── */
|
||||
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 });
|
||||
|
||||
/* ── 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 ?? "";
|
||||
const targetDescription = targetNode?.description ?? "";
|
||||
const centralStatement = situationGraph.centralStatement ?? "";
|
||||
|
||||
// Call production-focused deconstruct — exactly one provider generation
|
||||
const focused = focusedDeconstruct({
|
||||
provider,
|
||||
modelName: model,
|
||||
targetNodeId,
|
||||
targetLabel,
|
||||
targetDescription,
|
||||
centralStatement,
|
||||
question: previousQuestion ?? "",
|
||||
answer,
|
||||
});
|
||||
|
||||
if (!focused.success) {
|
||||
throw new Error(`focusedDeconstruct failed for episode ${contribSeq}: ${JSON.stringify(focused.errors)}`);
|
||||
}
|
||||
|
||||
// Map deconstruction result → canonical Contribution shape
|
||||
const derivedContrib = {
|
||||
id: `contrib-ep-${String(contribSeq).padStart(4, "0")}`,
|
||||
targetNodeId: focused.targetNodeId ?? targetNodeId,
|
||||
question: previousQuestion ?? "",
|
||||
answer: answer,
|
||||
sequence: contribSeq,
|
||||
observations: focused.observations,
|
||||
uncertainties: focused.uncertainties,
|
||||
assumptions: focused.assumptions,
|
||||
relationships: focused.relationships,
|
||||
possibleFollowUpQuestions: focused.possibleFollowUpQuestions,
|
||||
};
|
||||
effectiveContributions.push(derivedContrib);
|
||||
// Derive canonical Findings from the Contribution (production seam)
|
||||
|
||||
// Derive canonical Findings from the deconstruction-derived Contribution (production seam)
|
||||
newFindingsFromContributions = deriveFindingsFromContributions([derivedContrib]).findings;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user