Skip to content

refactor(core): give the token bucket its own function - #183

Merged
donislawdev merged 1 commit into
masterfrom
refactor/decide-complexity-headroom
Sep 6, 2026
Merged

refactor(core): give the token bucket its own function#183
donislawdev merged 1 commit into
masterfrom
refactor/decide-complexity-headroom

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

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 was decide, 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 takes decide from 27 to 28, ruff C901 fails, 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. decide goes 27 -> 24, max-complexity 27 -> 26 (now settings_summary), so decide has 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 < now clamp. That was correct only because step 11 had just run and left the bucket at or past now - 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-complexity so it is not re-derived optimistically: each moved body pays one if back 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 to decide - or to decide that a pipeline of N product features is allowed to score N.

DEPTHS_NEAR_CEILING fell 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 new rate: ones.

The paired decide benchmark 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 unchanged idle, udp and targeted-miss mixes 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 onto decide, giving away the room this change buys.

🤖 Generated with Claude Code

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>
@donislawdev
donislawdev merged commit 1fd053e into master Sep 6, 2026
14 checks passed
@donislawdev
donislawdev deleted the refactor/decide-complexity-headroom branch September 6, 2026 18:47
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