107 lines
5.3 KiB
Markdown
107 lines
5.3 KiB
Markdown
# 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 A–D 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
|