123 lines
3.5 KiB
Markdown
123 lines
3.5 KiB
Markdown
# Experiment 60B.43 — Terminal post-mutation eligibility
|
|
|
|
**Date:** 2026-08-14
|
|
**Branch:** `feature/post-mutation-terminal-eligibility-v0.38`
|
|
|
|
## Purpose
|
|
|
|
Extend the 60B.42 selector guard to the remaining post-mutation question-target eligibility checks so terminal-status unknown nodes cannot remain active or become the final selected question after mutation.
|
|
|
|
## Starting point from 60B.42
|
|
|
|
60B.42 fixed `selectActiveUnknownCandidate(...)` so it no longer scores or returns unknown-kind nodes whose status is terminal:
|
|
|
|
- `known`
|
|
- `resolved`
|
|
- `contradicted`
|
|
|
|
That closed one fallback source, but several independent post-mutation checks in `apply-proposal.js` still used weaker eligibility rules and could keep terminal nodes alive through other paths.
|
|
|
|
## Remaining post-mutation eligibility changes
|
|
|
|
This experiment changed post-mutation eligibility only in `lib/graph/apply-proposal.js`.
|
|
|
|
Updated paths:
|
|
|
|
- `isSelectableUnresolvedUnknown(...)`
|
|
- `remainingUnknownExists`
|
|
- `carriedActiveUnknownStillUnresolved`
|
|
- `listUnresolvedUnknownCandidates(...)`
|
|
- `listEligibleUnknownCandidates(...)`
|
|
- `selectPatternCompatibleUnknownCandidate(...)`
|
|
|
|
### Terminal-status rule applied post-mutation
|
|
|
|
A selectable unresolved post-mutation target now requires:
|
|
|
|
```text
|
|
kind === unknown
|
|
status NOT IN [known, resolved, contradicted]
|
|
not in resolvedNodeIds
|
|
```
|
|
|
|
### Important boundary preserved
|
|
|
|
`validateSelectedQuestion(...)` was **not** changed.
|
|
|
|
The closure proposal remains valid pre-mutation even when:
|
|
|
|
- `selectedQuestion.nodeId = n_product_launch_decision`
|
|
- the same proposal updates `n_product_launch_decision -> known`
|
|
|
|
Only after mutation is that now-terminal target discarded.
|
|
|
|
## 60B.37 deterministic regression
|
|
|
|
Added focused regression:
|
|
|
|
- `60B.43 — terminal post-mutation target is cleared after valid decision closure`
|
|
|
|
Reproduced the 60B.37-shaped closure:
|
|
|
|
- existing active unknown: `n_enterprise_customer_signing`
|
|
- proposal resolves `n_enterprise_customer_signing`
|
|
- proposal updates `n_product_launch_decision -> known`
|
|
- proposal still selects `n_product_launch_decision`
|
|
- no added nodes or edges
|
|
|
|
### Result
|
|
|
|
- proposal applied successfully
|
|
- customer factor resolved in place
|
|
- decision became known in place
|
|
- both options preserved unchanged
|
|
- `activeUnknownNodeId = null`
|
|
- final `selectedQuestion = null`
|
|
|
|
## Fallback-to-real-unknown result
|
|
|
|
Added a second focused case where:
|
|
|
|
- proposal-selected target becomes `known`
|
|
- another genuine unresolved unknown remains after mutation
|
|
|
|
Result:
|
|
|
|
- terminal known target discarded
|
|
- remaining genuine unresolved candidate selected
|
|
|
|
## Known-only post-mutation result
|
|
|
|
Added a valid mutated case where the final remaining unknown becomes `known` in the same mutation.
|
|
|
|
Result:
|
|
|
|
- `activeUnknownNodeId = null`
|
|
- `selectedQuestion = null`
|
|
|
|
## Preservation checks
|
|
|
|
Focused run also preserved:
|
|
|
|
- 60B.11 preferred-target behaviour
|
|
- pricing prerequisite-first behaviour
|
|
- resolved exclusion
|
|
- contradicted exclusion
|
|
|
|
## Validation
|
|
|
|
Command run:
|
|
|
|
```bash
|
|
npx vitest run tests/graph/apply-proposal.test.js -t "60B.43|60B.11|replaces downstream pricing"
|
|
```
|
|
|
|
Result:
|
|
|
|
- PASS — `14 passed | 76 skipped`
|
|
|
|
## What remains unproven until live rerun
|
|
|
|
This deterministic bounded fix now clears the exact 60B.37-shaped stale target through the post-mutation production path under test.
|
|
|
|
What remains unproven until a live rerun is whether the full runtime/orchestration path with the real customer-signing closure answer produces the same null-question closure end state under live conditions. |