Skip to content

guards(reach): MIN_GUARDS_EXAMINED must sit in a window, and a stale pin fails as a puzzle instead of an instruction #17818

Description

@mrveiss

Finding

guard_reach_meta_test.py's MIN_GUARDS_EXAMINED does two jobs, and only one of them is visible when it goes stale.

line assertion what it checks
:257 len(guards) >= MIN_GUARDS_EXAMINED the sweep reached a real population — the reach floor
:355 len(narrowed) < MIN_GUARDS_EXAMINED the floor at :257 is tight enough to notice if .glob( were dropped from the enumerator

:355 is the non-vacuity check on :257. It is the reason a regression that silently narrowed the enumerator would fail loudly instead of reporting clean over a fifth fewer guards, which is the blind spot #16147 was filed for.

The consequence nobody wrote down: the constant must sit in a window.

narrowed  <  MIN_GUARDS_EXAMINED  <=  full

Above the narrowed reach or :355 cannot fire; at or below the full reach or :257 fails. That window moves as the tree grows, so the pin needs periodic re-measurement — and when it goes stale the failure surfaces on whichever innocent PR happens to add the guard that closes the gap.

Measured, 2026-09-29

full reach (matches _ENUMERATOR)            165
narrowed reach (without `.glob(`)           132
MIN_GUARDS_EXAMINED before this measurement 132   <- equal to narrowed: window violated

Ten days of ordinary guard growth carried the narrowed reach from 104 to 131. Two unrelated PRs then each added an rglob-based guard, and each became "the 132nd":

Both went red on python-suite shard 8/12 with assert 132 < 132, and neither PR was defective. Fixed in #17817 by re-pinning to 165 (top of the window, which maximises time to the next re-pin — the same choice the 2026-09-11 and 2026-09-19 re-pins made). #17799 carries a witness comment rather than a second patch.

Why this is worth a change and not just another re-pin

The re-pin is correct and the treadmill is inherent — the window moves, so the pin must be re-measured. What is wrong is that a stale pin fails as a puzzle rather than an instruction. Today's message says the floor "cannot detect the loss ... raise it or the mutation is unguarded", which tells the reader something is wrong but not what value is legal. Diagnosing it took reading the docstring, re-deriving both reach numbers by hand, and reconstructing the window from two assertions in different tests.

Proposed change

Assert the window as one invariant, with a message that names the legal range:

narrowed < MIN_GUARDS_EXAMINED <= full

so a stale pin fails with "MIN_GUARDS_EXAMINED is 132; it must be in (132, 165] — re-pin to 165" instead of leaving the reader to derive it.

Explicitly rejected: deriving the floor from the tree. It is the first thing anyone proposes when a pin goes stale, and it destroys the check — a floor computed by the same enumerator it guards always agrees with itself and can never fail. The hand-pinned number is the whole mechanism. Recorded in the constant's comment on #17817 so the next reader does not re-derive it as an improvement.

Also rejected: replacing :355 with narrowed < full. That looks like the durable form of the same property and is not: :354 already asserts narrowed < full ("the .glob( term is load-bearing"). :355 asserts something :354 cannot — that the floor would notice. Dropping it would remove the only check on the floor's tightness, which is the non-vacuity property this umbrella exists for.

Acceptance criteria

  • guard_reach_meta_test.py asserts the window narrowed < MIN_GUARDS_EXAMINED <= full in one place, and the failure message names the legal range and the value to re-pin to.
  • A negative control proves the new assertion can fail: with the floor set to the narrowed reach, the test goes red (the 132 == 132 state reproduced deliberately), and with it set above the full reach it also goes red. Per tech-debt(guards): 79 of 133 tree-scanning guards can report clean having examined nothing #15826, a check that cannot fail is the defect this issue is about.
  • :354 (narrowed < full) is retained, not merged into the new invariant — it is a different property.
  • The comment records the measured pair and the date, as the three prior re-pins did.
  • No derived floor: a guard or comment states that computing the floor from the enumerator it guards is rejected, with the reason.

Notes for the owner

Scope is one constant and one assertion in one file — deliberately small, and separate from #17817 which only re-pinned the value.

A class observation, not a filing: a reach floor saturating against its own population and reddening innocent PRs has at least four known instances with no umbrella owning the class — #17517 (docstring-restatement floor, main at exactly zero headroom, also shard 8/12), #16183 (hooksPath floor down to 14 files of headroom), #17159 (tests-excluded floor counting 729 test files), #17142 (hooks-path-override floor tripping on ordinary changes), plus this one. Whether that deserves a class umbrella rather than five sibling issues is an owner call; this issue does not assume one.

Refs #16147 (the mechanism and the blind spot it was built for), #17517 (sibling instance), #17817 (where this instance was fixed), #17799 (second witness).

Activity

  1. added this to the v0.12.0 milestone on Sep 29, 2026
  2. mrveiss commented on Oct 2, 2026

    @mrveiss
    OwnerAuthor

    This issue now owns the saturated-reach-floor class

    Promoted to class owner rather than filing a sixth issue above the instances. The reason for not opening a new umbrella: this issue is already a sub-issue of #15826 alongside #17400 and #17819, and it already proposes the mechanism fix. A new umbrella in the same tree would duplicate its content and add a number for a defect that already had several.

    Instances now wired as sub-issues:

    Instance Guard How it presented
    #17517 docstring-restatement reach floor main sits exactly at the cap, so any PR adding a public symbol goes red
    #17142 hooks-path-override reach floor re-pinned three times on 2026-09-19/20 (6412→6421→6473→6481); skips=1 + growth=400 leaves room for under ~1 net file
    this issue MIN_GUARDS_EXAMINED double duty as a reach floor and as the check that the floor is tight enough to notice a dropped .glob( term, forcing a narrowed < FLOOR <= full window that moves as the tree grows

    Milestone moved v0.12.0 → v0.9.5 - CI and guards, where both instances already sit. A class owner scheduled later than its own children cannot be worked in order.

    Two corrections to the set as it was originally described (five siblings):

    So the class is three guards, not five.

    The constraint the fix must not violate

    Recorded here because it is the obvious shortcut and it produces a guard that can never fail:

    Do not derive the floor from the tree. A floor computed by the same enumerator it guards always agrees with itself.

    The property to assert is the window — narrowed < FLOOR <= full — failing with a message that names the legal range, so a stale pin reads as an instruction rather than a puzzle. That is this issue's existing proposal and it is what makes the class fixable once instead of re-pinned per instance.

    Acceptance shape for whoever takes it: the window assertion lives in the reach framework, not in each guard; a negative control proves a floor derived from the tree fails the new assertion; and each of the three instances is verified under the new mechanism rather than re-pinned.

  3. mrveiss commented on Oct 4, 2026

    @mrveiss
    OwnerAuthor

    Closure evidence — all 5 criteria met, AC1 with a location deviation

    Verified against merged origin/main (PR #17951, merge commit 12e1a56).

    AC verdict evidence in merged code
    1 — the window asserted in one place, message names range and re-pin value MET in substance, different file _guard_reach_census.py:166-194 window_complaint, message at :187-188: "MIN_GUARDS_EXAMINED is {floor}; it must be in ({narrowed}, {full}] -- re-pin to {full}.", asserted by guard_reach_floor_window_17818_test.py:42-51. The criterion names guard_reach_meta_test.py; the constant and invariant moved to the census module when that file was split at the 600-line ceiling (_guard_reach_census.py:5-13). One place, as required — just not the place the criterion named
    2 — negative control both directions MET guard_reach_floor_window_17818_test.py:54-66 parametrized offset [0, -1] reproduces the 132 == 132 state; :69-73 asserts full + 1 refused and full accepted. Drives window_complaint with wrong floors rather than mutating the live constant, so it cannot go vacuous
    3 — narrowed < full retained separately MET guard_reach_meta_test.py:450, with :446-449 stating it is retained and is not the window, citing this issue
    4 — comment records the measured pair and date MET _guard_reach_census.py:104-107: RE-MEASURED 2026-10-04 (#17870, #17818), 172 matched after exemptions, 140 without the glob alternative, legal window (140, 172]
    5 — no derived floor, with the reason MET _guard_reach_census.py:119-122: a floor computed by the same enumerator it guards "always agrees with itself and can never fail"; repeated at guard_reach_floor_window_17818_test.py:30-33

    Independent re-measurement on current origin/main (applying the merged _ENUMERATOR and _EXEMPT_NON_SCANNING to git ls-tree blobs, not the working tree): 361 tracked repo_tests/*_test.py, full = 176, narrowed = 142, MIN_GUARDS_EXAMINED = 170 (_guard_reach_census.py:126). 142 < 170 <= 176 — the window holds, 6 below full.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions