2.9 KiB
Experiment 60B.42 — Active selector terminal-status guard
Date: 2026-08-14
Branch: feature/active-selector-terminal-guard-v0.37
Purpose
Implement the narrow selector-only fix established by 60B.41 so selectActiveUnknownCandidate(...) never scores or returns terminal-status unknown nodes.
60B.41 diagnosis
60B.41 confirmed that selectActiveUnknownCandidate(graph, resolvedNodeIds) filtered candidates using only:
kind === "unknown"!resolvedNodeIds.includes(node.id)
It applied no status filter at all. Because scoreUnknownCandidate(...) also ignores node status, nodes with:
status = knownstatus = resolvedstatus = contradicted
could enter scoring whenever their IDs were absent from resolvedNodeIds.
That meant the active-selector fallback path could still select terminal nodes, including the exact known-decision stale-target risk seen in the 60B.37 lifecycle.
Exact selector filter change
Changed only the candidate filter inside selectActiveUnknownCandidate(...) in lib/graph/utils.js.
Before:
(n) => n.kind === "unknown" && !resolvedNodeIds.includes(n.id)
After:
(n) =>
n.kind === "unknown" &&
!["known", "resolved", "contradicted"].includes(n.status) &&
!resolvedNodeIds.includes(n.id)
No scoring weights, ordering rules, prerequisite logic, or other eligibility predicates were changed.
Terminal-state tests
Added focused tests in tests/graph/apply-proposal.test.js under:
60B.42 — active selector terminal-status guard
Covered cases:
- known node excluded when a genuine unresolved node exists
- known-only graph returns
null - resolved node excluded even when absent from supplied
resolvedNodeIds - contradicted node excluded even when absent from supplied
resolvedNodeIds - unresolved ranking remains unchanged when both candidates are genuinely unresolved
Unresolved-ranking preservation
The selector still chooses the same higher-priority unresolved candidate when both candidates remain valid (status = unknown).
This confirms the change acts only as a pre-scoring terminal-state gate and does not alter ranking semantics.
60B.11 / pricing preservation
The same focused run preserved:
- 60B.11 preferred-target behaviour
- prerequisite-first behaviour
- pricing regression selecting
n_commercial_valueinstead of downstreamn_pricing
Validation
Command run:
npx vitest run tests/graph/apply-proposal.test.js -t "60B.42|60B.11|replaces downstream pricing"
Result:
- PASS —
16 passed | 71 skipped
Remaining boundary
This experiment does not solve the broader duplicated eligibility problem.
Other post-mutation eligibility checks still exist elsewhere and remain unchanged in this task. This selector guard closes one specific fallback risk, but the broader shared-eligibility cleanup still remains to be handled separately before declaring the 60B.37 stale-question lifecycle fully fixed.