Files
confidence-engine/docs/experiment-60b70.md
T

281 lines
17 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.70 — Remaining apply-proposal.js Boundary Map
**Date:** 2026-08-14
**Branch:** `feature/decision-sufficiency-module-v0.44`
**Type:** Read-only structural audit (no code changes)
---
## Objective
Identify the highest-value zero-semantic-change extraction boundary in `apply-proposal.js` (4,480 lines) that would materially reduce edit/context risk without obscuring lifecycle orchestration.
---
## Git Pre-check
```
branch = feature/decision-sufficiency-module-v0.44
working tree = clean
HEAD includes: 36b4f47, 1ca5026, 6cb9109 ✓
```
---
## Responsibility Map
### Proposal reconciliation (lines 320409)
- **Approx line count:** 90 lines
- **Primary responsibility:** Normalize `resolvedUnknownNodeIds``updatedNodes` symmetry; auto-add synthetic updatedNode when proposal lists a resolved ID without corresponding update; nuke selectedQuestion if its node was resolved by the same proposal
- **Pure / impure / mixed:** Mixed (pure on proposal object, reads graph state only for existence checks)
- **Depends heavily on applyValidatedProposal locals:** NO — takes `graph` + `proposal` as parameters; returns `{proposal, errors}`
- **Existing focused tests:** STRONG (60B.49 suite: 6 integration tests across reconciliation, staleness, dedup, non-unknown guard)
### Proposal compatibility / selected-question validation (lines ~35813648)
- **Approx line count:** 130 lines of inline validation logic within applyValidatedProposal
- **Primary responsibility:** Pre-mutation graph integrity — added edge duplicates, edge reference validity (from/to node existence, cross-boundary edges), removed edge existence, combined node deduplication, semantic duplicate unknown detection, added-unknown support validation, selectedQuestion node validity, answer-meaning compatibility with raw answer, answer-meaning alignment, question-selection requirement
- **Pure / impure / mixed:** Mixed — calls helpers that read graph + proposal; mutates no state
- **Depends heavily on applyValidatedProposal locals:** PARTIAL — operates on `validatedProposal` (local) and `situationGraph` (param); calls imported `validateGraphUpdate`
- **Existing focused tests:** STRONG — 60B.61/64 suites, structured-fidelity suite (8 tests), boundary overlap tests (3), regression A/B/C/D suites, add-unknown support tests (7 cases)
### Resolution propagation (lines 9021261; `propagateResolvedChildEvidence`)
- **Approx line count:** 360 lines (exported function)
- **Primary responsibility:** Post-mutation parent progress state computation; child branch evidence aggregation; ancestor chain confidence propagation; confidence cap logic; comparison vs independent evidence distinction
- **Pure / impure / mixed:** Mixed — reads graph, computes derived metrics, returns rich result object
- **Depends heavily on applyValidatedProposal locals:** NO — already extracted as standalone export
- **Existing focused tests:** MEDIUM (covered by 60B.43/64 integration; no dedicated unit suite)
### Active unknown / target selection (lines ~25563488 + inlined orchestration at 39074121)
- **Approx line count:** 933 lines (exported `determineGraphBackedQuestion`) + ~215 lines inlined within applyValidatedProposal
- **Primary responsibility:** Unknown candidate eligibility filtering; reasoning pattern compatibility scoring; decomposition child selection; reseat-after-rejection; model-selected target preference via depends_on prerequisite check; sibling ordering tiebreakers
- **Pure / impure / mixed:** Mixed — reads graph, returns selection result (no mutations)
- **Depons heavily on applyValidatedProposal locals:** NO — the exported `determineGraphBackedQuestion` is fully self-contained. The inlined 257 lines at 38604121 are orchestration glue that depends on decompositionResult/propagationResult locals.
- **Existing focused tests:** STRONG (60B.42 active selector guard: 5 tests; 60B.11 prerequisite-aware targeting: 9 tests; selectedQuestion lifecycle in 60B.43/64)
### Answer semantic validation (lines ~32673454)
- **Approx line count:** 297 lines (validateAnswerMeaningCompatibilityWithRawAnswer + validateAnswerMeaningAlignment helpers)
- **Primary responsibility:** Raw answer → userSupportedMeaning alignment verification; unclassified meaning support detection; hard constraint boundary language analysis; conditional qualification preservation
- **Pure / impure / mixed:** Mostly pure — reads answer + proposal, returns errors array
- **Depends heavily on applyValidatedProposal locals:** NO — operates on `answer` + `proposal` only
- **Existing focused tests:** MEDIUM (regression A/B/C suites test the path end-to-end but don't isolate the helpers)
### Graph mutation (applyGraphUpdate import from utils.js)
- **Approx line count:** ~0 in apply-proposal.js (imported)
- **Primary responsibility:** The single mutation point — applies node updates, resolves nodes, adds/removes edges
- **Pure / impure / mixed:** Pure mutation function
### Final selectedQuestion lifecycle (lines ~41524312 within applyValidatedProposal)
- **Approx line count:** ~160 lines of inlined orchestration
- **Primary responsibility:** Compose finalSelectedQuestion from deterministicSelection + formulatedQuestion; repeated-question rejection + reseat; effectiveSelectedQuestion composition
- **Pure / impure / mixed:** Mixed — reads multiple locals, returns selected question or null
- **Depends heavily on applyValidatedProposal locals:** YES — tight coupling with deterministicSelection, proposedNode, decompositionResult
### Supporting pure helpers (lines 38569)
- **Approx line count:** ~530 lines
- **Primary responsibility:** JSON cloning, Zod error formatting, edge duplicate detection, text normalization, node lookup, compound question detection, confidence assessment, branch conflict signature computation, token overlap utilities
- **Pure / impure / mixed:** All pure — no side effects
- **Depends heavily on applyValidatedProposal locals:** NO
---
## Orchestration vs Extractable Logic
```text
proposal reconciliation: GOOD EXTRACTION CANDIDATE (pure on proposal+graph)
proposal compatibility val: GOOD EXTRACTION CANDIDATE (complex but stateless)
answer semantic validation: POSSIBLE LATER (good candidate but lower priority)
resolution propagation: ALREADY EXTRACTED (standalone export)
active unknown / target sel: ALREADY PARTIALLY EXTRACTED (determineGraphBackedQuestion is standalone; inlined orchestration stays)
final selectedQuestion: SHOULD STAY IN apply-proposal.js (tightly coupled to deterministicSelection lifecycle)
graph mutation: MUST STAY IN apply-proposal.js (single ownership point)
supporting pure helpers: POSSIBLE LATER (large cluster, but low edit frequency)
```
---
## Candidate Assessment
### Candidate A — Proposal Reconciliation (`reconcileResolutionSemantics`)
- **Approx removable lines:** ~90 (lines 320409)
- **Semantic-change risk:** LOW — pure function on proposal object; existing tests cover all paths
- **Coupling:** LOW — takes graph + proposal; returns {proposal, errors}
- **Test coverage:** STRONG — 6 dedicated integration tests in 60B.49
- **Context reduction:** MEDIUM — removes 90 lines of self-contained logic
- **Future edit-frequency:** LOW — stable reconciliation rules unlikely to change
- **Lifecycle clarity after extraction:** BETTER — apply-proposal.js pre-validation flow becomes a clear sequence of named steps
- **Principal risk:** Must verify every edge case (selectedQuestion nuke on resolution, bidirectional update-node/ID consistency) is captured in the new module's tests
### Candidate B — Proposal Compatibility Validation
- **Approx removable lines:** ~130 (lines 35813648 inline within applyValidatedProposal)
- **Semantic-change risk:** LOW — stateless validation logic; all callers pass through same helpers
- **Coupling:** MEDIUM — imports `validateGraphUpdate` from utils.js and calls other internal helpers
- **Test coverage:** STRONG — 25+ tests across multiple suites exercise every validation path
- **Context reduction:** HIGH — removes the largest single block of inline logic from applyValidatedProposal, splitting it into a named pre-check step
- **Future edit-frequency:** MEDIUM — schema-driven, may need updates when graph schema evolves
- **Lifecycle clarity after extraction:** BETTER — `validateProposalCompatibility()` becomes a single readable call replacing 7+ individual validation pushes
- **Principal risk:** Must preserve exact error aggregation order and deduplication semantics across the extracted validator
### Candidate C — Resolution Propagation
- **Already extracted as standalone export (lines 9021261)**
- **No remaining inline logic to extract**
### Candidate D — Active Unknown / Target Selection
- **Approx removable lines:** ~215 (inlined orchestration at 38704121 within applyValidatedProposal)
- **Semantic-change risk:** MEDIUM — the inlined block has many local-variable side effects and interacts with decompositionResult/propagationResult state
- **Coupling:** HIGH — deeply reads locals from applyValidatedProposal; recomputes deterministicSelection multiple times
- **Test coverage:** STRONG (exported function); but inlined orchestration has MEDIUM test coverage
- **Context reduction:** MEDIUM
- **Future edit-frequency:** LOW-MEDIUM
- **Lifecycle clarity after extraction:** WORSE — would separate the "post-propagation reselection decision" from its governing state variables across function boundary
- **Principal risk:** Extracting the inlined orchestration block would scatter the candidate selection logic across multiple function boundaries, making it harder to trace the active unknown lifecycle
### Candidate E — Answer Semantic Validation
- **Approx removable lines:** ~297 (validateAnswerMeaningCompatibilityWithRawAnswer + validateAnswerMeaningAlignment at lines 32673454)
- **Semantic-change risk:** LOW — mostly pure text analysis
- **Coupling:** LOW — operates on answer + proposal only
- **Test coverage:** MEDIUM — tested end-to-end but not as isolated unit tests for the helpers
- **Context reduction:** MEDIUM
- **Future edit-frequency:** MEDIUM — answer semantics may evolve with prompt changes
- **Lifecycle clarity after extraction:** BETTER
- **Principal risk:** Answer semantics is tightly coupled to prompt contract; extraction alone doesn't reduce orchestration complexity in applyValidatedProposal
---
## Mutation Ownership
```text
Can mutation ownership remain central while extracting candidate modules: YES
applyGraphUpdate(...) invocation — MUST stay (single mutation entry point)
proposalSnapshot lifecycle — MUST stay (built up locally, passed to mutation)
updatedSituationGraph lifecycle — MUST stay (accumulates mutation state across pipeline stages)
resolvedUnknownNodeIds bookkeeping — MUST stay (derived from proposalSnapshot.resolvedUnknownNodeIds)
activeUnknownNodeId mutation — MUST stay (tied to post-mutation candidate reselection lifecycle)
selectedQuestion finalisation — MUST stay (composed from deterministicSelection + formulatedQuestion in same scope)
```
The key insight: all mutations flow through `applyGraphUpdate(graphSnapshot, proposalSnapshot)`. Once extracted modules return their outputs, the mutation remains a single point of truth. Extraction of validation/reconciliation doesn't fragment mutation ownership because these are pre-mutation checks that operate on copies/clones.
---
## Ranking
1. **Candidate B — Proposal compatibility validation** (highest context reduction, strongest tests, LOW semantic risk, removes largest inline logic block)
2. **Candidate A — Proposal reconciliation** (LOW risk, STRONG tests, self-contained, but fewer lines than B)
3. **Candidate E — Answer semantic validation** (pure text analysis, good candidate but lower priority)
4. **Candidate D — Active unknown / target selection** (HIGH coupling to applyValidatedProposal locals makes it a weak extraction candidate despite strong tests)
5. **Supporting pure helpers** (LOW edit frequency; not worth the abstraction cost)
---
## Strategy Assessment
### Strategy A — ONE EXTRACTION ONLY
Extract Candidate B (validation), verify, stop.
```text
Risk: LOW
Expected line reduction: ~130 lines from apply-proposal.js (now ~4,350)
Expected context reduction: HIGH — removes the largest single inline logic block
Semantic-drift risk: LOW — stateless validation functions are easy to extract correctly
```
### Strategy B — TWO SMALL EXTRACTIONS
Extract A + B in separate commits. Both are independent pure-checking modules with STRONG test coverage.
```text
Risk: LOW (two independent, low-risk extractions)
Expected line reduction: ~220 lines total (~4,260 remaining)
Expected context reduction: HIGH — two clear named pre-validation steps replace inline logic
Semantic-drift risk: LOW (both have STRONG test coverage and pure/mixed character)
```
### Strategy C — LARGE APPLY-PROPOSAL DECOMPOSITION
Break apply-proposal.js into several lifecycle modules now.
```text
Risk: MEDIUM-HIGH — too many extraction points to verify in one pass; risk of scattering orchestration awareness across modules
Expected line reduction: ~600+ lines (aggressive)
Semantic-drift risk: MEDIUM — more boundaries to cross during verification
```
### Strategy D — STOP REFACTORING
Current structure is good enough.
```text
Risk: LOW (no risk)
But 4,480 lines still has one ~990-line function with 15+ phases of inline logic
Context reduction: NONE
Semantic-drift risk: NONE
Future Claude/Codex context cost: HIGH — every session loads all 4,480 lines
```
**Chosen: Strategy B — TWO SMALL EXTRACTIONS in separate commits**
Rationale: Candidates A and B are independent pure-checking modules with STRONG test coverage. Extracting both gives ~220 lines of reduction for minimal risk. Candidate D is excluded because its HIGH coupling to orchestration locals makes it a weak extraction candidate despite strong tests.
---
## File Size Estimates
```text
Current apply-proposal.js lines: 4,480
After reconciliation extraction (A): ~4,390 (-90)
After compatibility validation extraction (B): ~4,260 (-220 total)
Reasonable medium-term target: ~4,250-4,300
Why not lower? Because apply-proposal.js must retain:
- Lifecycle ordering visibility (~150 lines of orchestration scaffolding)
- Graph mutation ownership (applyGraphUpdate invocation + proposalSnapshot buildup)
- Active target reselection lifecycle (~260 lines, partially extracted already)
- Final selectedQuestion composition (~160 lines)
Target of ~4,250-4,300 reflects a clear orchestration file — not tiny wrapper-only, not giant mixed-responsibility.
```
---
## Critical Distinction
**Choice: B — validation should be next**
Why: Candidate B removes the largest single inline logic block (~130 lines) that currently scatters 7+ validation calls across applyValidatedProposal's pre-mutation phase. Extracting `validateProposalCompatibility()` into its own module gives the highest context reduction per line extracted. Both A and B are equally justified as clean extractions, but B has higher priority because:
1. It removes more lines (130 vs 90)
2. The validation block in applyValidatedProposal is visually dominant — it obscures the post-validation lifecycle
3. STRONG test coverage across multiple independent suites (60B.49/61/64/structured-fidelity/boundary/edge-case)
4. No new tests needed for extraction — existing integration tests provide sufficient boundary coverage
---
## Minimum Next Refactor Boundary
**Choice: B — one new validation module**
Why: Extract `validateProposalCompatibility(graph, proposal)` as a single function that encapsulates all 8 pre-mutation validations currently scattered across applyValidatedProposal. The extracted function takes the same inputs (`graph`, `proposal`) and returns `{valid, errors}`. This matches the existing pattern established by decision-sufficiency.js extraction (pure logic out, mutation stays).
---
## Refactor Timing
**Choice: B — RETURN TO REASONING WORK FIRST**
Why: Experiment 60B.70 is a read-only audit with no implementation directive. The highest-value next action is completing this documentation and returning to active reasoning work. A future session can implement the validation extraction when there's a natural editing context (e.g., when schema changes require touching that validation layer anyway). Forcing an extraction without a natural editing trigger increases semantic drift risk because there's no external pressure ensuring the extraction serves a real need.
---
## Verification
- Production code changed: NO
- Tests changed: NO
- Prompt changed: NO
- Schema changed: NO
- Ollama calls: 0
- Live API calls: 0
- Vitest run: NO
- Jest run: NO
- Watchman used: NO