Files
confidence-engine/docs/archive/experiments/decision-closure-integration/experiment-60b42.md
T

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 = known
  • status = resolved
  • status = 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:

  1. known node excluded when a genuine unresolved node exists
  2. known-only graph returns null
  3. resolved node excluded even when absent from supplied resolvedNodeIds
  4. contradicted node excluded even when absent from supplied resolvedNodeIds
  5. 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_value instead of downstream n_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.