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

107 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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? }) → boolean`**new** 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
```bash
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