281 lines
17 KiB
Markdown
281 lines
17 KiB
Markdown
# 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 320–409)
|
||
- **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 ~3581–3648)
|
||
- **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 902–1261; `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 ~2556–3488 + inlined orchestration at 3907–4121)
|
||
- **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 3860–4121 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 ~3267–3454)
|
||
- **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 ~4152–4312 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 38–569)
|
||
- **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 320–409)
|
||
- **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 3581–3648 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 902–1261)**
|
||
- **No remaining inline logic to extract**
|
||
|
||
### Candidate D — Active Unknown / Target Selection
|
||
- **Approx removable lines:** ~215 (inlined orchestration at 3870–4121 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 3267–3454)
|
||
- **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
|