Skip to content

[finding] bare-root-worklist prints REACHABLE beside a REFUSE-* reason when a gate takes the escape, and --self-test stays green — the contradiction lands silently #12064

Description

@yinlianghui

Measured on PR #12061 while implementing #11935. Filed unassigned — out of that card's declared file surface (scripts/check-ratchet-remedy-authority.mjs), and it is a defect of the worklist rather than of the gate.

This is not the same defect as #11277. That one is about a why string stating a wrong measurement, and correctly observes that a prose assertion cannot be mechanised. This one is a state/verdict contradiction, which is mechanisable — the two fields disagree structurally, not in prose.

The shape

scripts/pm/bare-root-worklist.mjs prints, per row, a state derived from the live tree beside a recorded verdict from its TRIAGE map. When a gate that carries a REFUSE-* verdict later takes the watch-hint escape, the state flips to REACHABLE while the recorded refusal reason stays put. The row then asserts both that the population is now reachable and that it is unspellable.

Reproduced by declaring ROOT_DIR_WATCH_HINTS = ['scripts/*'] on scripts/check-ratchet-remedy-authority.mjs (the change on PR #12061):

# unmodified tree (1e79aa4f8)
REFUSE-UNSPELLABLE check:ratchet-remedy-authority SCRIPTS_DIR scripts
reads the TOP LEVEL of the root only, and only two extensions — 115 of 226 (51%).
The idiom has no non-recursive spelling: a subtree hint claims every nested directory too
# with the declaration (36c15e682)
REACHABLE check:ratchet-remedy-authority SCRIPTS_DIR scripts
reads the TOP LEVEL of the root only, and only two extensions — 115 of 226 (51%).
The idiom has no non-recursive spelling: a subtree hint claims every nested directory too

node scripts/pm/bare-root-worklist.mjs --self-test exits 0 on both trees (OK self-test: 41 live row(s), 34 unreachable as spelled, 35 recorded verdict(s) — none stale, none missing). The production run exits 0 on both as well.

Why the STALE assertion does not fire

The self-test audits TRIAGE keys against all sweep rows in both directions — a key with no row is STALE, a row with no key is missing. A row that is still present but whose state has moved out of the refusal classes satisfies both halves, so nothing fires. The map already has a verdict for a gate that took the escape — DECLARED-NARROWER — so the vocabulary to express the correct post-escape state exists; nothing checks that a row actually moved to it.

This was predicted in #11155's dev report ("its self-test will NOT catch that, because the STALE assertion tests TRIAGE keys against ALL sweep rows, not just uncovered ones, so the contradiction would land silently"). It is now measured rather than predicted, which is why it is worth its own card.

Why it matters

The worklist is a governance record whose value is that its rows are trustworthy — its own triage docblock insists every percentage was measured rather than estimated. A row that reads REACHABLE … has no non-recursive spelling is worse than a wrong measurement: it is self-refuting, and the next reader cannot tell which half is stale. It also silently converts a deliberate, adjudicated refusal into something that reads like an oversight.

The trigger is not hypothetical or specific to this gate: any row whose gate later declares a watch hint lands it, and taking the escape is exactly what the ledger exists to encourage.

Suggested remedy (not applied here)

Assert, in --self-test, that a row's live state and its recorded verdict class are consistent — a REFUSE-* verdict must not sit on a row the sweep now finds REACHABLE; such a row should either move to DECLARED-NARROWER or fail naming itself. That is a mechanical pairing of two fields the tool already computes, and it fails at authoring time rather than landing quietly.

Refs

#11155 (where it was predicted) · #11277 (the adjacent, different why-accuracy defect) · #11935 / PR #12061 (where it was measured) · scripts/pm/bare-root-worklist.mjsTRIAGE

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions