refactor(core): give the token bucket its own function - #183
Merged
Conversation
decide() sat exactly on the complexity ceiling, and the ceiling is pinned to the measurement, so whatever stands at the top has zero headroom. What stood there was decide, whose complexity is the product's own feature list. That made the notes' recipe for adding an impairment block its own required check. Step 3 of that recipe is "handle it in core.decide() at the right pipeline step", and a 13th gate takes decide from 27 to 28: ruff C901 fails, which is a required check on every pull request. Measured by mutation before anything was changed. The token bucket moves to _charge, and step 11's use of it to _shape. decide goes 27 -> 24 and max-complexity 26, which is settings_summary - so decide now has room under somebody else's ceiling. The pipeline order is untouched: the twelve gates stay where they are, only two bodies moved, and the gate precedence property still passes. The harvest is smaller than a first count suggested, and the number is written next to max-complexity so nobody re-derives it optimistically. Extraction gives back less than it looks like: each moved body pays one `if` back for checking the helper's answer, so a gate with a single nested branch yields nothing. The whole remaining harvest in decide is about three points, which is two more impairments, not six. It also removed a real duplication rather than only branches. Steps 11 and 12 each carried their own copy of the bucket arithmetic, and the second copy left out the `b < now` clamp - correct only because step 11 had just run and left the bucket at or past now. That is a true fact about today's ordering and not something either step said, so it was one reordering away from being wrong in a way no counter would have shown. And that clamp turned out to be unguarded. Deleting it survived all 1405 tests. Without it a link that has been quiet spends its idleness as burst credit: the shaper adds no delay until the bucket catches up, and `queued` goes negative so the bounded buffer cannot tail-drop either. The line was always correct and is unchanged; what is new is a test written from that mutation rather than from the code, plus registry entries for it and for the duplicate's charge. DEPTHS_NEAR_CEILING fell 12 -> 11 on its own, which is the routine direction. Verified: 1406 passed, crash log empty, ruff and mypy clean, every mutation for the changed files caught. The paired decide benchmark could not produce a usable number - the canary moved between runs, so the machine did, and the rig says that makes two runs incomparable. What is solid from it: retained blocks and bytes are identical in every mix on both trees, and the default path gains no call at all, because both helpers sit inside gates that only an armed impairment enters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Point 2 of the codebase-health work, after #181 and #182.
The jam: the recipe blocked its own required check
decide()sat exactly on the complexity ceiling, and that ceiling is pinned to the measurement - so whatever stands at the top has zero headroom. What stood there wasdecide, whose complexity is the product's own feature list.Step 3 of the documented "how to add an impairment" recipe is handle it in
core.decide()at the right pipeline step. Measured by mutation before anything was touched: a 13th gate takesdecidefrom 27 to 28,ruff C901fails, and that is a required check on every pull request. The recipe could not be followed.What changed
The token bucket becomes
_charge, and step 11's use of it becomes_shape.decidegoes 27 -> 24,max-complexity27 -> 26 (nowsettings_summary), sodecidehas room under somebody else's ceiling.The pipeline order is untouched. The twelve gates stay exactly where they are - only two bodies moved - and the gate-precedence property still passes.
It also removes a real duplication rather than only branches: steps 11 and 12 each carried their own copy of the bucket arithmetic, and the second copy left out the
b < nowclamp. That was correct only because step 11 had just run and left the bucket at or pastnow- a true fact about today's ordering, and not something either step stated.A defect the extraction surfaced: an unguarded line
That clamp turned out to have no guard at all. Deleting it survives all 1405 tests.
The bucket holds a virtual finish time, not a token count, so a link that has been quiet leaves it in the past. Without the clamp two things break at once: the shaper adds no delay until the bucket catches up to the present, and
queued(b - now) goes negative, so the bounded-buffer tail-drop cannot fire either. The longer the pause, the bigger the burst that walks through a link with a speed limit on it.The line was always correct and is unchanged. What is new is
test_an_idle_shaped_link_does_not_bank_burst_credit, written from that mutation rather than from the code, plus registry entries for it and for the duplicate's charge.The harvest is smaller than it looks
An earlier estimate said seven complexity points were available here. The real number is about three, and the reason is written next to
max-complexityso it is not re-derived optimistically: each moved body pays oneifback for checking the helper's answer, so a gate with a single nested branch yields nothing at all.That is two more impairments of room, not six. This axis cannot absorb a twelve-step pipeline growing indefinitely, and when it jams again the honest options are to lower
settings_summary- which hands the ceiling straight back todecide- or to decide that a pipeline of N product features is allowed to score N.DEPTHS_NEAR_CEILINGfell 12 -> 11 on its own, which is the routine direction.Verification
Full suite 1406 passed, crash log empty, ruff and mypy clean. Every mutation for the changed files caught, including the four
core:entries and both newrate:ones.The paired
decidebenchmark could not produce a usable number, and that is reported rather than rounded away: the canary moved between runs (50 -> 86 in the same units), so the machine moved, and the rig's own rule is that two runs then cannot be compared. What is solid from it: retained blocks and bytes are identical in every mix on both trees (13/608, 15/776, 15/656), and the default path gains no call at all - both helpers sit inside gates that only an armed impairment enters, which the unchangedidle,udpandtargeted-missmixes agree with.Not done
settings_summary(26) has the same shape - 20 top-level branches, one per setting, while the upload half twenty lines above it already uses a table for exactly this and says why in its docstring. It is deliberately left alone here: lowering it takes the ceiling off it and drops the band back ontodecide, giving away the room this change buys.🤖 Generated with Claude Code