fix(subscriptions): read the utilization header as a fraction, past 1.0 too (#2419) - #2864
Conversation
….0 too (#2419) The provider's anthropic-ratelimit-unified-{5h,7d}-utilization header is a fraction of the cap. _parse_utilization multiplied by 100 only up to 1.0 and passed anything larger through as an "already-percent" value, so an overage reading of 1.2 came out as 1.2%: the ent#434 weekly alert filed an exhausted subscription as has_headroom and the #2409 ranker rated it the best destination. The parser now always scales, matching the provider's own client; non-finite or negative results are None (the guard sits on the scaled value, since a finite 1e307 overflows only after the multiply), and -0.0 is normalised. A window reading past 100% logs the raw header strings at INFO, the capture path for the first real overage sample. Residuals: cached Redis snapshots self-heal within one refresh interval; pre-fix history rows cannot be repaired (1.2 from "0.012" and from "1.2" are identical); the first post-deploy sweep may raise critical items for every overage subscription; a negative reading now classifies as unassessable rather than has_headroom. Fixes #2419 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…llow-up, #2865 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Follow-up for the part deliberately left out of this PR — the status predicates never read |
|
Full unit island on this branch (
The 14 subscription suites this change reaches ( |
Description
_parse_utilizationconverted the provider'santhropic-ratelimit-unified-{5h,7d}-utilizationheader withround(v * 100, 1) if v <= 1.0 else round(v, 1). The header is a fraction of the cap, so an overage reading of1.2(120%) came out as 1.2%: Settings and the dashboard tile showed a nearly-empty subscription, the ent#434 weekly alert classified ithas_headroomand never fired, and the #2409 auto-switch ranker rated the exhausted subscription the best destination in the fleet.Number(value)behindNumber.isFinite, no<= 1branch, no clamp on 5h/7d, displaysMath.round(u * 100)).None. The guard sits on the scaled value: a finite1e307overflows toinfonly after the multiply, andround(inf, 1)returnsinfrather than raising.-0.0is normalised so it never renders as-0%._probelogs the raw header strings when a window reads past 100% — the only path by which the issue's "capture a real overage header set" criterion can ever close. Standing query:SELECT * FROM subscription_headroom_history WHERE five_hour_utilization_pct > 100 OR seven_day_utilization_pct > 100.db_models.py# 0..100, the alert threshold ceiling note) and the module docstring updated; the feature flow gains two clauses; one learnings-ledger entry.subscriptionPressureTile.js::barWidthPctalready clamps the bar and reports the unclamped number (spec has a 137% case); the Settings panel prints the raw value. No migration: both DB tracks store the figure as REAL/Float with no bound; no pydanticle=100.Evidence, stated honestly. "The header is a fraction" is proven (the 2026-08-19 spike headers, 1,333 local probe rows, the provider client's parser). "It exceeds 1.0 on an overage plan" is open in both directions: no capture exists, every local plan carries
overage_status = rejectedand blocks at 100%, and the public docs do not document the unified family. The fix is right regardless — Trinity now agrees with the provider's client for every input; a percent-shaped"42.5"reads loud-wrong (4250%) on both, versus today's quiet-plausible 1.2%. The overage header set in the tests is synthetic, modelled on that parser, and says so.Plan-gate rulings (2026-09-17): AC #4 ("existing #471 tests unchanged") is amended for exactly one assertion —
_parse_utilization("42.5") == 42.5pinned the defect and is replaced; every other #471 / ent#433 / ent#434 / #2409 test is untouched. Negative input →Noneis a stated behaviour change (a-0.1reading classifiedhas_headroombefore,unassessablenow; the ranker's own guard stays because pre-fix Redis snapshots live up to 7 days). A follow-up issue for overage-aware predicates (overage_status/unified_statusare parsed and persisted but read by no predicate) is filed separately.Related Issue
Fixes #2419
Journey Impact
Journey Impact: none: a parser unit fix inside the headroom probe — no journey promise is added or extended; the subscription journeys read the percentage the probe already produced
Type of Change
Testing
tests/unit/test_2419_utilization_fraction.py(parser table, synthetic overage header set, the filed bug through the REAL chain: headers →_to_model→classify_headroom == saturated+selection_verdict == (measured, 120.0),decide_tierCRIT, ranker sorts the overage subscription last,_history_rowpassthrough, the arrival log in both directions)test_471,test_ent434,test_2409,test_447,test_433,test_2396,test_2638,test_2443,test_2572,test_ent582,test_1677,test_297,test_subscription_auto_switch_pingpong); the full unit island was still running at push time and its tally follows as a PR commentMutation:
tests/unit/test_2419_utilization_fraction.py(20 cases acrossTestParser,TestHeaders,TestChain,TestArrivalSignal) andtests/unit/test_471_subscription_usage_observability.py::TestHeadroomParsing::test_header_is_a_fraction_never_a_percentgo RED without the fix — observed on the pre-fix tree before the change was applied: 21 failed / 37 passed on the two files, green after (58/58).Local end-to-end (live instance, dev backend hot-reloaded from the bind mount)
0.11/0.36→ 11% / 36%, history row written, no arrival line_store_snapshotGET /usage→ 120.0 both windows;GET /api/agents/subscription-pressure→ the agent on it reads 120.05h: 120% · 7d: 120%saturated; the overage subscription ranked last of threeChecklist
Residuals (also in the commit message)
Cached Redis snapshots self-heal within one refresh interval (≤1 h unwatched, ≤15 min watched, 60 s after a click). Pre-fix history rows cannot be repaired — 1.2 from
"0.012"and from"1.2"are byte-identical. The first post-deploy sweep may raise critical operator-queue items for every overage subscription, bounded by the per-window dedup id and the per-cycle cap.Security:
/cso --diff— 0 findings, report indocs/security-reports/cso-diff-2026-09-17-2419-utilization-fraction.md.🤖 Generated with Claude Code