fix(quota): gate a family model on the higher of its own bucket and the shared weekly - #191
Open
ak2k wants to merge 1 commit into
Open
fix(quota): gate a family model on the higher of its own bucket and the shared weekly#191ak2k wants to merge 1 commit into
ak2k wants to merge 1 commit into
Conversation
…he shared weekly Diagnosis, measurement and the proposed rule are johnnymo87's in issue KarpelesLab#175. This implements it. RELEASE NOTE. An account past its shared weekly cap is now unavailable for Fable and Sonnet traffic, where before it kept serving them. Operators will see such an account drop out of the pool sooner, and see fewer accounts reading above 1.00 on the shared weekly bucket. Family spend meters twice, once in the family bucket and once in the shared one, so the two are not independent. Reading the family bucket alone let an account at unified7d 1.00 with unified7dFable 0.20 keep taking Fable, and each such request pushed the shared bucket further past its cap. Once the shared bucket is spent, family requests are the only ones still admitted, which makes it a one-way ratchet rather than a bounded overshoot. KarpelesLab#175 measured the coupling directly: per-request increment on the shared bucket [+1.14e-4, +5.21e-4] against Fable-only traffic, P about 0.999. The rule is the maximum of the two REPORTED buckets. null keeps meaning unreported and never becomes zero: a max invites flooring an absent bucket at 0, and 0 reads as empty, which is the opposite of unknown and errs toward keeping the account in service. If one side is absent the answer is the other; if both are, the answer is null and the gate leaves the dimension alone. The change is confined to the VALUE. _governingWeeklyReset still keys off the governing bucket, so value and reset can now name different buckets. Nothing here pairs them: both readers of the reset, _pickBestAvailable and _pickLeastLoaded, use it as a ranking tiebreak among accounts that already passed _isAvailable, and neither divides a headroom by it. Maxing the reset would pair one bucket's level with another bucket's clock. _modelWeeklyExhausted stays family-only, deliberately. It answers whether an account can serve a family at all, not whether it is near a cap. Folding the shared bucket in would skip accounts for probes they could have served, and a probe is how a stale cached utilization is corrected, so it would harden the state it exists to escape. THE DISPLAYS FOLLOW THE GATE. KarpelesLab#175 names the status Models row: KarpelesLab#172 fixed its blocklist half and left the shared-weekly half reading the family bucket alone, so the row printed Fable OK on an account the router had already refused. The TUI's blocked tag (issue KarpelesLab#85) had the same defect. Both now read gatingUtilization rather than deriving the rule a second time. Where a mark and a recovery time appear together, the time is the LATEST reset among the two WEEKLY buckets currently over the threshold. That half of the mark comes from a maximum, so it clears only once both weekly blockers have rolled; showing the family reset beside a mark the shared weekly produced told an operator that a week-long block clears in an hour. An unreported reset among the blockers leaves the time unknown and the cell says nothing, since a known-but-earlier one would understate it. The shared 5h bucket stays outside that set, which leaves a narrow pre-existing gap: it can raise the mark while contributing no reset, so an account blocked longer by 5h than by its weekly buckets still shows the weekly clearing time. With unified5h 0.99 resetting in 10h and unified7dFable 0.99 resetting in 1h, both stock and this branch render `Fable X 1h` for a block that lasts 10h. Closing it means restructuring the cell, since the time is gated on the weekly condition and a 5h-only block suppresses it entirely, so it is left as it is and ledgered rather than fixed here. Tests: test/weekly-gate.test.js, 19 cases. The matrix KarpelesLab#175 proposed, one per row (shared-over/family-under, the reverse with gating still model-scoped, both-low, family-reported-with-shared-null, both-null, and a model whose governing bucket already is unified7d), a reported 0 against an absent bucket, the null rule over the whole 4x4 cross product, Sonnet on both arms, the probe ranker inheriting the fix, _modelWeeklyExhausted not inheriting it, the reset staying with the governing window, and six display cases driven through the real renderer and the real TUI row.
This was referenced Aug 25, 2026
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.
The diagnosis, measurement, and proposed rule here are johnnymo87's, from #175. This implements their offer; read #175 first. Refs #175. If they would rather land their own patch, say so and this closes without hard feelings.
The bug
_governingWeeklyreturns the family bucket alone whenever it is reported, so a Fable request on an account atunified7d = 1.00withunified7dFable = 0.20keeps being selected. #175 measured that family spend meters into both buckets, so this is a one-way ratchet: once the shared weekly caps, family requests are the only ones still admitted and each pushes it further past 1.0. We then watched a production account do exactly this at 99% shared utilization.The fix
The governing weekly for a family request is the higher of the reported family bucket and the reported shared bucket.
nullstill means unreported, never zero; behavior is unchanged when the governing key already isunified7d;_modelWeeklyExhausteddeliberately stays family-only, since "can this account serve this family at all" is a different question from "is it near any cap". The statusModelsrow and the TUI tag read the same gate, so the display agrees with routing once the gate is fixed; on stock they only agreed because both shared the defect.The recovery time beside a blocked mark is the latest blocking weekly bucket's reset: a block produced by a maximum clears only when every blocking bucket has rolled.
Behavior change
Deliberate, and worth a release note: an account near its shared weekly cap becomes unavailable for family models sooner. That is the bug fix, not a regression, per #175's own framing.
Measured against stock: 5,000 seeded scenarios (2 to 5 accounts, quota levels drawn across the threshold, fixed 40-request mixed schedules), walking both trees in lockstep. 405 diverge, 4,595 identical, zero cases where this branch served a request stock refused. Every divergence falls into two classes: the stricter gate barred the account stock chose (358), or every account is now barred and the one revalidation probe was already spent (47). On the weekly buckets the family gate is now at least as strict as the shared one: over all 81 combinations of the two bucket levels, zero cases where an account can serve Fable but not Opus.
Tests
525 before, 544 after, 0 fail;
npm run lintclean. #175's six-row matrix is covered one test per row, plus the null cross-product, the probe ranker inheriting the fix,_modelWeeklyExhaustednot inheriting it, the reset staying with the governing window, and six display cases through the real renderer and TUI row. With the fix reverted and the new tests in place: 536 pass, 8 fail. Upstream's own suite does not cover this path: the same reversion with the new file parked passes 525/525.Limitations
_modelWeeklyExhaustedstays family-scoped).This collides with #190 and the #192 stack above it in
src/account-manager.jsandsrc/model.js(one import name and one adjacent export each); whichever lands second rebases the same day.