Files
confidence-engine/docs/archive/experiments/decision-closure-integration/experiment-60b67.md
T

5.3 KiB
Raw Blame History

Experiment 60B.67 — Decision-Sufficiency Module Extraction

Branch: feature/decision-sufficiency-module-v0.44 Status: extraction complete, zero semantic change verified Date: 2026-08-14


Objective

Extract all decision-sufficiency evaluation logic from apply-proposal.js (4730 → 4480 lines) into a dedicated decision-sufficiency.js module (~233 lines), per the audit conclusions in experiment 60B.65. This narrows the boundary between evaluation (pure predicate) and mutation (orchestration), eliminating the ~130-line checkRemainingFactorsVirtual duplication described in 60B.65's LINE FOOTPRINT section.


Changes

New module: lib/graph/decision-sufficiency.js (~233 lines)

Exports (5 public, 1 shared constant):

  • isUserConfirmationOfNoRemainingUncertainty(answer) → boolean — pure text-predicate on raw user answer string
  • hasRemainingMaterialFactors(decisionNodeId, graph) → boolean — pure graph query (thin wrapper over count)
  • countRemainingMaterialFactors(decisionNodeId, graph, pendingResolvedIds?) → number — pure graph traversal across 4 routes; now accepts optional pendingResolvedIds parameter for same-turn virtual resolution semantics
  • shouldCloseDecision({ decisionNodeId, graph, answer, pendingResolvedIds? }) → booleannew combined predicate; eliminates the need for the caller to compose two checks
  • TERMINAL_STATUSES — internal constant (not exported; kept private)

Internal (private):

  • CONTRADICTION_PHRASES, CONFIRMATION_PHRASES, CONFIRMATION_PATTERNS — moved from apply-proposal.js constants
  • isUnresolvedUnknown(node) — pure graph query used as internal predicate

apply-proposal.js changes (~250 net lines removed)

  • Import statement added for countRemainingMaterialFactors, hasRemainingMaterialFactors, isUserConfirmationOfNoRemainingUncertainty, shouldCloseDecision
  • Re-export of hasRemainingMaterialFactors preserved for backward compatibility (existing tests import from apply-proposal.js)
  • Confirmation constants + function body removed (~57 lines)
  • Remaining-factor helpers + TERMINAL_STATUSES removed (~111 lines)
  • checkRemainingFactorsVirtual closure block replaced with single shouldCloseDecision() call (~130 lines eliminated as duplication)
  • Local TERMINAL_STATUSES constant added inside the closure iteration loop to avoid breaking the parentNode.status guard that already existed there

Tests: tests/graph/decision-sufficiency.test.js (~456 lines)

  • 15 tests for isUserConfirmationOfNoRemainingUncertainty — all phrase-family variants confirmed
  • 11 tests for hasRemainingMaterialFactors — identical assertions to 60B.61 in apply-proposal.test.js (route AD coverage, edge cases)
  • 6 tests for shouldCloseDecision — full predicate testing including pendingResolvedIds virtual resolution semantics

Tests: tests/graph/apply-proposal.test.js

  • Added import line for shouldCloseDecision, isUserConfirmationOfNoRemainingUncertainty from the new module (for future use)
  • Existing 60B.61 and 60B.64 test suites unchanged (zero semantic change verified)

Verification

npx vitest run tests/graph/decision-sufficiency.test.js \
             tests/graph/apply-proposal.test.js -t "60B.67|60B.64|60B.61"

# Result: 35 integration tests (apply-proposal) + 32 pure unit tests = 67 passing

All pre-existing test suites pass without modification — confirming zero semantic change.


Complexity Reduction

Metric Before After Delta
apply-proposal.js lines 4730 4480 250
Decision-sufficiency eval lines in file ~318 (scattered) 0 318
Closure integration orchestration lines ~150 ~40 110
CheckRemainingFactorsVirtual duplication ~90 lines (inline) Eliminated 90
New module size 233 +233
New test file size 456 +456

What Was NOT Moved (by design)

Per principle #4, graph mutation ownership stays in apply-proposal.js:

  • parentNode.status = "resolved" assignment
  • ensureResolvedUnknownId() calls
  • proposalSnapshot.updatedNodes manipulation
  • closureApplied flag propagation
  • Iteration loop over parent nodes

Only the evaluation predicate (shouldCloseDecision) was extracted. This keeps apply-proposal.js as the single source of graph truth for mutations while allowing the predicate to be independently testable and editable in a ~233-line file.


Why countRemainingMaterialFactors Gains a 3rd Parameter

The original checkRemainingFactorsVirtual accepted pendingResolvedIds because it was designed for same-turn resolutions where the graph hasn't yet been reconciled with the proposal snapshot. The extracted countRemainingMaterialFactors(decisionNodeId, graph) signature was deliberately extended to accept an optional pendingResolvedIds parameter so the pure function can serve both use cases:

  • Without the param: standard post-propagation evaluation (existing callers)
  • With the param: virtual resolution semantics during applyValidatedProposal

This preserves behavioral identity without requiring a separate "virtual" variant of the function.


Production code changed: YES (refactor only)

Tests changed: YES (new file + 1 import line in existing test)

Prompt changed: NO

Schema changed: NO

Ollama calls: 0

Live API calls: 0