Skip to content

guard(reach): pin every declared floor to its population (#15928) - #16048

Merged
mrveiss merged 8 commits into
Dev_new_guifrom
issue-15928-floor-guard
Sep 8, 2026
Merged

mrveiss merged 8 commits into
Dev_new_guifrom
issue-15928-floor-guard

Conversation

@mrveiss

@mrveiss mrveiss commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Thinking Path

repo_tests/_reach.py gives a guard a reach floor and proves it fires
(reach_declarations_test drives every declaration against an empty tree). It
does not prove the floor is tight, and that is the half that was missing.

The evidence is in the two guards that had already adopted it:

Guard Floor Live population
audio-extension-allowlist 1000 5,599 18%
env-var-bare-cast 500 3,152 16%

Both fire correctly against an empty tree. Both tolerate losing four-fifths of a
real one. Adoption was never the missing part — the number was, and the same
is true of every floor examined in review during #15896 (five of them), #15901
and #15913.

A floor far below its population catches only total collapse. Total collapse is
not the failure that happens; partial loss is — a narrowed glob, a moved
directory, a change propagated to some call sites and not others. Measured on
the file-size guard: a floor of 3,000 against 5,241 files detects only the loss
of the one tree holding 75% of them. The other five can vanish silently.

What Changed

  • Reach.growth (_reach.py) — how far the live population may sit above
    the floor before the ratchet demands it be raised. growth=0 (equality) is
    the default, because a floor at its population turns shrinkage into a
    deliberate line in a diff, which is the correct price for shrinking a guarded
    population.
  • test_every_declared_floor_is_pinned_to_its_population
    (reach_declarations_test.py) — asserts the live count sits in
    [floor, floor + growth], and names both numbers plus the remedy when it
    fails.
  • Both existing floors ratcheted, with the band's derivation written beside
    them rather than chosen by feel.

Validated in the meta-test rather than in declare(): declaring would walk the
tree once per imported guard, while the meta-test already enumerates every
declaration — same guarantee, cost paid once. The issue's AC said "declare()
rejects"; that was the wrong place and the PR says so.

Verification

The pre-push hook rejected the first version, and the rejection is the second
finding.

ReachFloorError: [audio-extension-allowlist] completed 5337 tracked python
files; floor is 5500.

One floor serves two populations. examined() bounds what discover
returned (5,599); completed() bounds what the guard actually finished (5,337)
— 262 files are skipped as unreadable or unparseable. A floor pinned to the
discovered count clears the first check and fails the second.

So the new meta-test is necessary and not sufficient: it can only see the
higher population, and completed() remains the binding constraint. Both bands
are 500 rather than the 250 ordinary churn alone would need, precisely to
absorb that gap. The limitation is stated in the test rather than left for the
next author to rediscover by the same hook failure. Giving the two populations
separate floors is the fuller fix and is deliberately not this change.

Mutation-checked: restoring the old floor of 1000 yields slack 4599 against
a band of 500, so the new test fails on exactly the defect it exists for.

Populations measured independently, not by running the guards' own
discover callables: git ls-files '*.py' plus each guard's own filter,
replicated. The 5,337 figure is the hook's, which is the one that binds.

Both guards pass the full pre-push suite at 715b5eb1.

Model Used

Opus 5 (1M context)

Closes #15928

Single-issue rationale: this is one mechanism in one module. Its siblings under
#15923 are #15926 (one tracked_files()) and #15927 (an exclusion registry),
which touch different files and carry different risk — #15927 is a
policy-visible change and this is not. Batching them would put three
independent migrations behind one review pass.

A floor far below what its own discovery returns fires only against total
collapse, and total collapse is not the failure that happens. Partial loss is:
a narrowed glob, a moved directory, a change propagated to some call sites and
not others. Every floor examined in review during #15896, #15901 and #15913
fired correctly against an empty tree while tolerating the loss of most of a
real one.

Adoption did not fix this. Both guards that had already adopted _reach declared
a number chosen by feel -- 1000 against 5,599 files and 500 against 3,152, 18%
and 16% of their own populations. The mechanism was never the missing part.

Adds Reach.growth and a meta-test asserting the live population sits within
[floor, floor + growth]. growth=0 -- equality -- is the default because a floor
at its population turns shrinkage into a deliberate line in a diff. A
population ordinary work grows states a band instead, where a reviewer sees it,
rather than being pinned low and quietly meaning nothing.

Validated in the meta-test rather than in declare(): declaring would walk the
tree once per imported guard, and the meta-test already enumerates every
declaration, so the guarantee is the same and the cost is paid once.

Both existing floors ratcheted to a band of 250, which absorbs ordinary churn
in both directions while staying far below the size of any subtree whose loss
the floor exists to catch.
… discovered (#15928)

The pre-push hook rejected the first version and the rejection is the finding:
one floor serves two populations. `examined()` bounds what `discover`
returned -- 5,599 tracked python files -- while `completed()` bounds what the
guard actually finished, 5,337, because 262 are skipped as unreadable or
unparseable. A floor pinned to the discovered count clears the first check and
fails the second.

So the meta-test added here is necessary and not sufficient: it can only see
the higher population, and `completed()` remains the binding constraint. Both
declarations carry a band of 500 rather than the 250 ordinary churn alone would
need, precisely to absorb that gap. Separating the two floors is the fuller fix
and is deliberately not this change.

Stating the limitation in the test rather than leaving it for the next author
to rediscover by the same hook failure.
@coderabbitai

coderabbitai Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: d8ade208-50b8-418c-978a-7213fcc4ad5c


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No new hardcoded values of either class — ssot and other both block.

Known backlog in pipeline-scripts/hardcoded_values_baseline.txt is suppressed and tracked in #14371.

@mrveiss

mrveiss commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

Review: the ratchet is the right mechanism and the docstrings are unusually honest about its limits. Two findings, one of which is in the failure message rather than the logic.

I read this as the necessary complement to test_no_guard_can_succeed_against_an_empty_tree — that one proves a floor fires, this one proves it is tight, and the gap between them is real: every floor in review during #15896/#15901/#15913 fired correctly against an empty tree while tolerating the loss of most of a real one. growth=0 as the default is the right call, and stating the deciding question as "does normal work move this number?" rather than "how much slack feels safe" is what stops the field becoming the problem it replaces.

I also want to note the docstring saying this is necessary and not sufficient, that completed() remains the binding constraint, and that separate floors for the two populations is the fuller fix and not this change. Scoping that explicitly rather than half-doing it is the right boundary.

1. The failure message tells the reader to defeat the guard

reach_declarations_test.py, the first assertion:

assert slack >= 0, (
    f"[{reach.name}] floor {reach.floor} exceeds the live population of "
    f"{count} {reach.what}. The guard cannot pass; lower the floor to "
    f"{count} only if the population genuinely shrank."
)

Consider when this fires. count has fallen below the floor — a glob narrowed, a directory moved, a tree stopped being reached. That is the event this guard exists to catch, and the message's actionable instruction is lower the floor to {count}, which makes the failure go away and locks in the collapse.

The only if the population genuinely shrank caveat carries the whole load, and a caveat is not what people act on — the number in the imperative is. A ratchet whose collapse message hands you the release lever, pre-filled with the value that disarms it, will be disarmed.

The second assertion has the same shape (Either ratchet the floor to {count}) but there it is correct: slack positive means the population grew, and ratcheting up is the right action.

Suggestion: on the negative branch, lead with the diagnosis and make the reader do the work — the sweep is reaching N fewer items than when this floor was set; find out what stopped being reached before touching the floor — and drop the pre-computed replacement value. Right or wrong, the number should not be sitting there ready to paste.

2. _REPO_ROOT = _REPO_TESTS.parent is a hand-rolled root the #15925 guard cannot see

_REPO_TESTS = Path(__file__).resolve().parent
_REPO_ROOT = _REPO_TESTS.parent     # new in this PR

This should be repo_root() from repo_tests._paths — that module exists precisely so this question has one spelling, and #15925 removed 161 bindings to get there.

What makes it worth more than a style note is that the guard does not fire on it, so nothing else will tell you. hand_rolled_roots_in bails before hop-counting when the assignment's own value contains no __file__:

if not any(isinstance(n, ast.Name) and n.id == "__file__" for n in ast.walk(node.value)):
    continue

The value here is _REPO_TESTS.parent — no __file__ in it — so _parent_hops and the semantic membership check never run. One hop of aliasing, and the 38th spelling is back.

I have filed the general hole as #16055; it is a different scope from this PR. For this PR the fix is the one-line import, and it removes the instance.

Not a finding, but the field name will decay

growth is currently carrying two different quantities. The docstring is explicit that both declarations use 500 rather than the ~250 that churn alone needs, because the band also has to absorb the examined()/completed() gap (262 of 5,599 for audio-extension-allowlist). So the number means churn plus skip-gap, while the name and the ratchet instruction both say growth.

The practical consequence: someone ratcheting after ordinary growth cannot tell how much of the 500 was never theirs to spend, and if the skip-gap widens the floor stops being tight in a way this field cannot express. You have already named the real fix — separate floors for examined and completed — so this is only an argument for making the split sooner rather than a request to do it here.

Verdict

Fix 2 before merge — it is one line, and leaving it in adds an instance of the thing #15925 closed, in a file whose whole subject is guards that quietly stop reaching. I would take 1 as well, since the message is the interface at exactly the moment it matters most. Neither changes the design, which I think is right.

@mrveiss

mrveiss commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

The reach_declarations_test[audio-extension-allowlist] failure is an off-by-one, and it is the growth concern from my review arriving within a day.

Measured against current base:

declare("audio-extension-allowlist", floor=5100, growth=500)
git ls-files "*.py" | wc -l   ->  5601

slack = 5601 - 5100 = 501     growth = 500     501 <= 500  ->  False

It fails by one.

The immediate fix is the one your own assertion names, and here it is the correct branch of it — slack is positive, so the population genuinely grew and ratcheting the floor to 5601 is right. (This is the case I said the second assertion handles correctly; only the negative-slack message worries me.)

One correction to a diagnosis in circulation: this is not the EmptyEnumeration collision I described on #16057. On this branch _tracked_python_files still calls raw subprocess.run(..., check=False), and that guard is one of the two I deliberately excluded from the migration, so it stays on raw subprocess on every branch. The helper cannot raise here.

Worth more than the fix. I flagged in review that growth=500 carries two quantities — ordinary churn plus the examined()/completed() gap of 262 — leaving roughly 250 for churn. That was consumed in under 24 hours by a repo that gains a few Python files a day. Your own docstring called it:

A population that grows with almost every commit (tracked files, dict literals) cannot be equality-pinned without failing unrelated PRs, and states a band instead.

*.py tracked files is that population, and a 500-band on 5,601 files is a pin with extra steps. Raising to 5601 buys a day or two before the next unrelated PR goes red for a reason its author cannot act on — which is the specific failure mode the band exists to prevent.

So I would not just ratchet. Either widen the band substantially with the reasoning stated where a reviewer meets it, or take the separate examined/completed floors you already named as the fuller fix. This declaration looks like the case growth cannot serve in its current shape.

Caveat on my evidence: the CI logs for that run have expired — --log-failed returns nothing at job or run level — so I did not read the failure text. The arithmetic above is from current base and the declaration on this branch. If the log said something else, I am wrong and would want to know.

@mrveiss

mrveiss commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

Do not merge — the band in this PR is wrong, not unlucky

Marking this explicitly because the failure is subtle enough to read as flaky,
and merging would pin a wrong number as correct.

The red on shard 12/12 is mine and the test is behaving correctly

FAILED reach_declarations_test.py::test_every_declared_floor_is_pinned_to_its_population[audio-extension-allowlist]
tracked .py files : 5601
declared          : floor=5100  growth=500   -> band covers 5100..5600
slack             : 501   -> fails by one file

The tree grew by two files between my measurement and CI. The meta-test did
exactly what it was added to do.

Why the number was unchoosable, which is the actual defect

One floor has to satisfy both populations at once:

  • at or below completed — 5,337, what the guard finishes after skipping
    262 unreadable or unparseable files;
  • within growth of discover — 5,601, what enumeration returns.

That forces growth >= 264 before a single file of real headroom. So the
band was never 500 wide for growth; it was ~236, and the repository consumed it
within the hour.

A number that silently spends most of itself on a different quantity cannot be
chosen well.
Raising it to 800 would buy a few hours and re-teach exactly the
habit this PR exists to end — a floor set by feel rather than to its population.

The fix, and why it is not in this push

Split the two jobs: growth means ordinary growth before a deliberate
ratchet
, and a separate declared allowance means discovered items this guard
legitimately cannot complete
. The meta-test then checks
discover - floor <= skips + growth, with both quantities named where a
reviewer sees them, and the floor pins to the population that binds.

That change lands in _reach.py, and #16057 is currently changing the same
file
— teaching examined() to treat EmptyEnumeration as the collapse it
already tests for. Two compatible changes, one module, and they collide
textually. Agreed ordering with the coordinating session: #16057 lands first
and this rebases onto it.
An unpassable test outranks a wrong number.

Not the same root as #16057, for the record

That collision fires in test_no_guard_can_succeed_against_an_empty_tree, which
drives discovery against a deliberately empty tree. My meta-test calls
reach.discover(_REPO_ROOT) against the real root, which is non-empty, so
EmptyEnumeration cannot fire on that path. Two tests, two causes, one file —
worth stating because the filename alone merged them once already.

Shard 8/12 is not mine

Stale base. The same two assertions in glob_declared_reads_15900_test.py are
red on #15978, #15980 and #16052, and #16012 fixed the record they check.
Merging base in clears it. One cause, four symptoms.


The part I would keep from this if the rest changes: the mechanism caught its
own author.
This PR argues that floors get set by feel and then pass while
meaning nothing, and its own declaration was set by feel and failed within the
hour. That is the guard working, and it is a better argument for the change than
the two floors it was written to fix.

…15928)

The previous push failed by one file, and the failure is the argument.

One floor serves two populations: `examined()` bounds what `discover`
returned (5,601 tracked python files), `completed()` bounds what the guard
finished (5,337, with 262 skipped as unreadable or unparseable). The floor has
to clear the lower one while the meta-test measures the higher, so a single
band of 500 spent 264 of itself on the skip gap before buying any growth
headroom. The remainder lasted an hour.

Raising it to 800 would have been the fix this change exists to prevent: a
number chosen by feel. Splitting it is the fix -- `skips` for what a guard
cannot complete, `growth` for what it may grow before a deliberate ratchet.
Each is then chosen against one quantity and a reviewer can see which.

audio-extension-allowlist declares skips=300, measured from the hook's own
`completed 5337` against 5,599. env-var-bare-cast declares skips=200 and says
in the comment that it is a guess, because that guard's `completed` count has
never failed and the gap is unknown -- recorded as unmeasured rather than
presented as measured.

The failure message now names both numbers and says which to raise, since the
whole defect was one number silently doing two jobs.

`examined()` is deliberately untouched: #16057 documents an EmptyEnumeration
collision in that function and the fix belongs to whoever takes it.
@mrveiss

mrveiss commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

Pushed c1a8b714 — the band is split, not widened. Hold lifted.

The previous version failed by one file and the failure was the argument.

What changed

growth was doing two jobs. One floor serves two populations — examined()
bounds what discover returned (5,601), completed() bounds what the guard
finished (5,337, with 262 skipped as unreadable or unparseable) — so a single
band of 500 spent 264 of itself on the skip gap before buying any growth
headroom. The remainder lasted an hour.

Reach.skips now carries that gap under its own name:

before after
growth 500, silently part skip-gap 400, meaning only growth
skips — 300 (audio) / 200 (env-var)

The meta-test checks slack <= skips + growth and its failure message now names
both, and says which to raise:

skips=  items this guard cannot COMPLETE (unreadable, unparseable).
        Measure it from a `completed` failure; do not estimate it.
growth= ordinary growth tolerated before a deliberate ratchet.
If neither is short, the floor is stale: ratchet it toward N.

Why not just raise the band to 800

That is the habit this PR exists to end. A number chosen because the last one
was too small is the same number chosen by feel, one iteration later. A
quantity that silently spends most of itself on a different quantity cannot be
chosen well
— the fix is two names, not a bigger one.

The two declarations, and which numbers are measured

  • audio-extension-allowlist: skips=300 is measured — the pre-push hook
    reported completed 5337 against discover 5599, a gap of 262, rounded up
    for churn. growth=400 is a maintenance interval and is the only number here
    chosen by judgement.
  • env-var-bare-cast: skips=200 is a guess and the comment says so. That
    guard's completed count has never failed, so its gap is unknown. Recorded as
    unmeasured rather than presented as measured; the next completed failure
    gives the real number and whoever sees it should replace this.

Current slack: audio 501 against an allowance of 700; env-var 302 against 600.

examined() is deliberately untouched

#16057 documents an EmptyEnumeration collision in that function — tracked_paths
raises where test_no_guard_can_succeed_against_an_empty_tree needs an empty
result as evidence. Both mechanisms are right and the resolution belongs to
whoever takes it. Verified untouched: the diff to _reach.py adds a dataclass
field and a parameter, and changes no function body.

Status

Shard 12/12 was the red this fixes. Shard 8/12 was stale base — the same two
assertions in glob_declared_reads_15900_test.py were red on #15978, #15980 and
#16052, and #16012 fixed the record they check; merging base in clears it.

Green on the pre-push suite at c1a8b714. No reds on the PR; 27 checks pending.

@mrveiss mrveiss left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. The split is the right abstraction, and I took up your invitation to push back on env-var's unmeasured number — it is measurable, and the measurement says 0.

The split

growth was carrying two quantities, so a 500 band bought ~236 of actual headroom and the repo ate it in an hour. skips naming the discover/completed gap separately is right, and the failure message naming both and saying which to raise is what makes the next choice a decision rather than a guess. Raising 500 → 800 would have been the same act one iteration later, and you said so.

examined() is untouched — confirmed in the diff, a dataclass field and a parameter, no function body changed. 46's EmptyEnumeration resolution stays available.

The pushback you asked for: EXPECTED_SKIPS = 200 should be 0

I replicated the guard's own population and parse loop rather than reasoning about it — git ls-files "*.py", the _EXCLUDED_PATH_FRAGMENTS filter, _is_scanned, then ast.parse catching the same (SyntaxError, UnicodeDecodeError, OSError) triple that measurement() catches at :233:

discovered : 3152
completed  : 3152
GAP        : 0

Every file this guard discovers, it parses. So 200 is not a conservative guess — it is 200 units of slack buying nothing, and it widens the band by 200 for a population that has no gap at all.

That matters more than the arithmetic: a number chosen by feel is exactly what this PR exists to end, and an unmeasured skips reintroduces one under a name that now looks measured because its sibling is. The comment saying it is a guess is honest, and honesty about a wrong number is weaker than the right number.

Recommend EXPECTED_SKIPS = 0 with the derivation and the command recorded, so the next person re-derives rather than re-guesses. If a completed failure ever appears there, that is the signal to raise it — with the number the failure hands you, which is precisely the mechanism you built.

Contrast worth keeping in the comment: audio's 262 is real because that guard's population contains files it genuinely cannot finish. Two guards, two answers, and only measurement distinguishes them — which is the argument for the field existing.

Not blocking on

  • growth=400 as a maintenance-interval judgement. It is judgement, it is labelled as judgement, and safety comes from the floor rather than the band.
  • MIN_FILES_SCANNED 500 → 2850 on the env-var guard: a floor at 500 against a 3,152 population tolerated losing four-fifths of the tree, which is the same defect this PR is about, one declaration over.

Verdict: approve with one change. Set EXPECTED_SKIPS = 0 and record the measurement; everything else stands. I am not the ideal reviewer for whether the abstraction is right — I endorsed the ordering that produced it — so a second pass from 46 is still worth having, since they hit the adjacent half of this tension from the other side.

It is 0. That guard discovers 3,152 files and parses 3,152 -- every file it
enumerates, it completes -- so the 200 it shipped with was slack buying
nothing.

Worse than useless: it sat beside audio's measured 300, where it inherited an
authority it had not earned. A number chosen by feel, under the name this
change added to stop numbers being chosen by feel. Declaring it honestly as a
guess was weaker than measuring it, which is the pushback I asked for and got.

With no gap the floor can pin near the real population rather than below a
phantom one, so MIN_FILES_SCANNED goes 2850 -> 3000: slack 152 against a
growth band of 400, and the floor still clears completed by 152.

The derivation and the command to repeat it are recorded beside the constant,
so the next person re-derives rather than re-guesses. If a completed failure
ever appears there, that is the moment to raise it -- with the number the
failure hands you, which is the mechanism this PR builds.
@mrveiss
mrveiss merged commit 6954ab1 into Dev_new_gui Sep 8, 2026
68 checks passed
@mrveiss
mrveiss deleted the issue-15928-floor-guard branch September 8, 2026 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant