Skip to content

fix(tier 4): a NaN estimate did not bypass the AI budget, it poisoned it - #374

Merged
sebyx07 merged 1 commit into
mainfrom
fix/sweep-17-tier4
Aug 26, 2026
Merged

fix(tier 4): a NaN estimate did not bypass the AI budget, it poisoned it#374
sebyx07 merged 1 commit into
mainfrom
fix/sweep-17-tier4

Conversation

@sebyx07

@sebyx07 sebyx07 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Tier 4 of the 17.0.0 sweep, third of five slices, in tier order. Tiers 0–1 landed in #364, tiers 2–3 in #370.

Every tier-4 package is now absent from scripts/lib/finite-bounds-pins.ts, which is that file's own convention for "this slice closed all of it" — a count that falls is a claim; an absent row is a machine-checked one. 59 sites → 23, and the 23 are tier 5 plus three audited ui props.

The defect class

A numeric bound whose own non-finite value makes its guard read false. ?? guards nullish, and NaN is not nullish. Math.max/Math.min/Math.floor are not validators either — all three propagate NaN.

The worst one is not a bypass — it is a poisoning

@ultimat3/ai's gateway used its pre-flight token estimate as the ceiling check and then wrote that estimate onto the ledger and the per-process BudgetStore.

Measured, pre-fix: a NaN estimate passed a 1,000-token ceiling — and every later call was then compared against a poisoned total, so a 5,000,000-token call passed the same ceiling. One bad request does not overspend once; it disables the budget for the process.

Screened at the seam every model call crosses, and at each declaration under the key name the declaration itself uses. That second half is not cosmetic: budget.tokensPerRun lands in BudgetLimits as request, so a single screen in the ledger would emit a fix: naming a key the app never wrote.

Two sites were a synchronous infinite loop

chunk({ size }) and embedBatched — past every AbortSignal, past the job timeout, past the watchdog, on the worker's only thread.

Both are now pinned by tests that hang when the screen is removed and have to be killed (timeout 25 → exit 143). That is the honest shape of that assertion; a test that returns cannot express "this never returns."

Four more worth naming

Defect Effect
ISR entry with a non-finite TTL now - generatedAt < NaN is false, so the page is never fresh and regenerates on every request — silent unbounded origin load. The entry comes from a pluggable store, so this is reachable
cache-control emitted max-age=NaN not a shorter age — an unparseable directive a conforming cache ignores, so the response fell back to heuristic caching rather than the declared age
one NaN precache entry makes the total NaN, killing the budget warning for every other entry. Reproduced: an Infinity entry printed precache is 0b (over 1b)
capQueryRows with a non-finite ceiling the agent gets zero rows with truncated: false — an empty table reported as the complete answer — and the 256 KiB context guard switches off
retentionPlan(deploys, NaN) Math.max(1, NaN) is NaN, slice(0, NaN) is []evicts every deploy, including the running one

Three premises of the brief were FALSE, and each was disproved rather than followed

This is the part I want reviewed hardest, because in all three cases doing what I asked would have made the tree worse.

  1. gateway's baseDelayMs/maxDelayMs are deliberately NOT screened. gateway-backoff.test.ts:74 is a shipped test asserting baseDelayMs: -500 yields a 0 wait. A finiteCount there would have refused green, intentional code. Non-finiteness is already refused downstream by core's backoffDelay. Same trap kdf.maxConcurrent: 0 set on the previous slice, avoided by grepping the tests before choosing a floor.
  2. The cache-hint fix does not belong in @ultimat3/render. I routed it there on the reasoning that it belongs at the declaration site. grep -rn "CacheHint|maxAgeSeconds|sMaxAgeSeconds|staleWhileRevalidateSeconds" packages/*/src returns zero hits in packages/render/defineRoute carries revalidate: { ttl, tags } and render composes cache-control itself. The boot-time half is http: Route.cache is a declaration site with no screen, so a bad cache hint is only caught on the response path where throwing is not allowed #373; looking for the declaration site is what turned up the ISR twin above.
  3. Two of the four ui sites are already screened one hop awaydebounce() and headingTag() both throw X_UI_INVALID_VALUE. Adding a second screen would have given one value two error codes. That is the ratchet's own documented blind spot 3 ("a repair in a different file"), working as designed.

ui keeps a pin of 3, with the reason written out

Not everything should be screened, and this is the case for it. debounceMs and level are covered elsewhere. rows is the only genuinely unscreened one, and it is the only prop of the four whose bad value produces neither silent zero-work nor a throw: a rows attribute the HTML parser rejects falls back to the element default — a two-row box instead of three. Throwing an X_INVARIANT out of a render would blank a whole form over a cosmetic typo. The pin carries that sentence and the one-line edit that overturns it.

Floors are asserted, not assumed

Every min is pinned by a mutation. Flipping any of them turns a test red — including three that turn a pre-existing shipped test red, which is how we know the floor is right and not merely plausible:

  • retentionPlan(deploys, 0) — shipped test never evicts everything
  • Skeleton lines: 0 — shipped test asserts it renders one line
  • hive concurrency: 0 — has always meant one worker
  • mail's timeoutMs floors at 1, because both drivers hand the value straight to AbortSignal.timeout with no zero branch, so 0 aborts on the next tick and is not "no deadline"

Verification

bun run verify14 of 20 passed, 6 skipped (drift, contract-diff, budgets, seo, i18n, policy — all gate on app.config.ts and skip at the repo root).

The first run was RED on 2 steps, and both were the predictable cost of forbidding workers to run tsc -b on a shared tree: 5 test typecheck errors and one file over the 500-line ceiling. Both were fixed, not pinned — the gate offered raise 'render' to 4 in scripts/lib/test-typecheck-pins.ts on purpose and taking it would have written this slice's own mistakes into the ratchet as permanent debt.

bun run manifest → no change. No new error code: every refusal is X_INVARIANT from core's finiteOption/finiteCount, so no wiki/Error-Codes.md row is needed.

Issues filed by this slice, not fixed by it


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Tier 4 of the 17.0.0 sweep, third of five slices. Every tier-4 package is now
ABSENT from scripts/lib/finite-bounds-pins.ts, which is the machine-checkable
claim that this slice closed all of it: 59 sites to 23, and the 23 are tier 5
plus three audited `ui` props.

The worst of the twelve is not a bypass. `@ultimat3/ai`'s gateway used its
pre-flight token estimate as the ceiling check AND then wrote that estimate onto
the ledger and the per-process BudgetStore. Measured: a NaN estimate passed a
1,000-token ceiling, and every later call was compared against a poisoned total,
so a 5,000,000-token call passed the same ceiling. Screened at the seam every
model call crosses, and at each declaration under the key name the DECLARATION
uses — a bound reported under the framework's internal name sends the reader to
a key their app never wrote.

Two sites were a synchronous infinite loop, past every AbortSignal and past the
job timeout, on the worker's only thread: `chunk({ size })` and `embedBatched`.
Both are pinned by tests that HANG when the screen is removed and have to be
killed, which is the honest shape of that assertion.

An ISR page with a non-finite TTL was never fresh, so it regenerated on EVERY
request — `now - generatedAt < NaN` is false, and the entry comes from a
pluggable store. Repaired totally rather than by throwing.

`cache-control` emitted `max-age=NaN`, which is not a shorter age: it is an
unparseable directive a conforming cache IGNORES, so the response fell back to
heuristic caching rather than to the declared age. Total, never a throw — this
is the response path. Every fallback goes the shorter direction, so nothing can
lengthen an age the caller did not ask for.

THREE PREMISES OF THE BRIEF WERE FALSE, and each was disproved rather than
followed. `gateway`'s baseDelayMs is deliberately NOT screened: a shipped test
asserts `baseDelayMs: -500` yields a 0 wait, so a screen there would have
refused green code — the same trap `kdf.maxConcurrent` set on the last slice.
The cache-hint fix does not belong in `@ultimat3/render`, which never
constructs a CacheHint at all (zero hits tree-wide); the boot-time half is
#373. And two of the four `ui` sites are already screened ONE HOP AWAY, so a
second screen would have given one value two error codes.

`ui` keeps a pin of 3 rather than a repair, with the reason written out:
`debounceMs` and `level` are screened elsewhere, and `rows` is the one prop
whose bad value produces neither silent zero-work nor a throw — an unparsed
`rows` attribute falls back to the element default, and throwing an X_INVARIANT
out of a render would blank a whole form over a cosmetic typo.

No new error code: every refusal is X_INVARIANT from core's finiteOption /
finiteCount, so no wiki row and no manifest change.

Issues FILED by this slice, not fixed by it: #371 (a bare parameter default is
the fourth blind spot, and randomToken(NaN) is the empty string), #372 (notify's
duration parser is a copy of time's that now DISAGREES with it, because the sweep
screened the copy and not the original), #373 (Route.cache has no
declaration-site screen).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 25 days. After that, they cost $0.25 per reviewed file.

Or wait 4 minutes for your next included review.

View limit details

Limit details: You’ve used the included review currently available. Your 76 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d899a127-461c-48fb-b0d3-fa1ebf025435

📥 Commits

Reviewing files that changed from the base of the PR and between 739eb4b and 0c0cccb.

📒 Files selected for processing (86)
  • CHANGELOG.md
  • packages/ai/CLAUDE.md
  • packages/ai/src/agent-bounds.test.ts
  • packages/ai/src/agent.ts
  • packages/ai/src/bounds-fixture.ts
  • packages/ai/src/budget.test.ts
  • packages/ai/src/budget.ts
  • packages/ai/src/embeddings.test.ts
  • packages/ai/src/embeddings.ts
  • packages/ai/src/evals.test.ts
  • packages/ai/src/evals.ts
  • packages/ai/src/gateway.test.ts
  • packages/ai/src/gateway.ts
  • packages/ai/src/hive.test.ts
  • packages/ai/src/hive.ts
  • packages/ai/src/llm.test.ts
  • packages/ai/src/llm.ts
  • packages/ai/src/models.test.ts
  • packages/ai/src/models.ts
  • packages/ai/src/pg-vector.test.ts
  • packages/ai/src/pg-vector.ts
  • packages/ai/src/rag.test.ts
  • packages/ai/src/rag.ts
  • packages/ai/src/remote-embedder.test.ts
  • packages/ai/src/remote-embedder.ts
  • packages/ai/src/scorers.test.ts
  • packages/ai/src/scorers.ts
  • packages/ai/src/vector.test.ts
  • packages/ai/src/vector.ts
  • packages/http/CLAUDE.md
  • packages/http/src/response.test.ts
  • packages/http/src/response.ts
  • packages/mail/CLAUDE.md
  • packages/mail/src/driver-resend.test.ts
  • packages/mail/src/driver-resend.ts
  • packages/mail/src/driver-smtp.test.ts
  • packages/mail/src/driver-smtp.ts
  • packages/manifest/CLAUDE.md
  • packages/manifest/src/agents-md.test.ts
  • packages/manifest/src/agents-md.ts
  • packages/mcp/src/query-limits.test.ts
  • packages/mcp/src/query-limits.ts
  • packages/mcp/src/transport-http.test.ts
  • packages/mcp/src/transport-http.ts
  • packages/mcp/src/transport-stdio.test.ts
  • packages/mcp/src/transport-stdio.ts
  • packages/notify/src/inbox-pg.test.ts
  • packages/notify/src/inbox-pg.ts
  • packages/notify/src/inbox.test.ts
  • packages/notify/src/inbox.ts
  • packages/notify/src/ledger.test.ts
  • packages/notify/src/ledger.ts
  • packages/notify/src/notifier.test.ts
  • packages/notify/src/notifier.ts
  • packages/notify/src/plan.ts
  • packages/pwa/CLAUDE.md
  • packages/pwa/src/install.test.ts
  • packages/pwa/src/install.ts
  • packages/pwa/src/precache.test.ts
  • packages/pwa/src/precache.ts
  • packages/pwa/src/version-skew.test.ts
  • packages/pwa/src/version-skew.ts
  • packages/render/CLAUDE.md
  • packages/render/src/finite-status.test.ts
  • packages/render/src/finite-status.ts
  • packages/render/src/head.test.ts
  • packages/render/src/head.ts
  • packages/render/src/registry.test.ts
  • packages/render/src/registry.ts
  • packages/render/src/render-isr-bounds.test.ts
  • packages/render/src/render-isr.ts
  • packages/render/src/render-ssr.test.ts
  • packages/render/src/render-ssr.ts
  • packages/render/src/render-stream.test.ts
  • packages/render/src/render-stream.ts
  • packages/ui/src/components/DataTable.test.ts
  • packages/ui/src/components/DataTable.tsx
  • packages/ui/src/components/Skeleton.tsx
  • packages/ui/src/components/combobox-filter.test.ts
  • packages/ui/src/components/combobox-filter.ts
  • packages/ui/src/components/file-controls.test.ts
  • packages/ui/src/components/layout.test.ts
  • packages/ui/src/components/presentation.test.ts
  • scripts/finite-bounds.test.ts
  • scripts/finite-bounds.ts
  • scripts/lib/finite-bounds-pins.ts

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

@sebyx07
sebyx07 merged commit 6a92d8b into main Aug 26, 2026
38 checks passed
@sebyx07
sebyx07 deleted the fix/sweep-17-tier4 branch August 26, 2026 19:19
sebyx07 added a commit that referenced this pull request Aug 26, 2026
… 129 places (#378)

One sweep, five slices, in tier order: #364 (tiers 0–1), #370 (2–3), #374 (4),
#375 (5), #377 (the blind spots). `bun run finite-bounds` goes from 129 sites to
4, and both survivors are AUDITED pins carrying the sentence saying why
screening them would be worse, not unexamined debt.

THE DEFECT CLASS. `??` guards nullish and `NaN` is not nullish, so
`Number(process.env.X)` on an unset variable, a parseInt of a typo and an
untyped config value all walk past the default and land on the bound intact.
`Math.max`, `Math.min` and `Math.floor` are not validators either — all three
PROPAGATE NaN, and this repo was relying on all three as guards.

What that produced, each measured rather than reasoned about:

  a NaN token estimate did not bypass the AI budget, it POISONED it — after one
  such call a 5,000,000-token request passed a 1,000-token ceiling;
  `awaitActionable({ timeoutMs: NaN })` ran 835,462 polls in 3 seconds against a
  real browser, past ctx.signal, past the watchdog, past the job timeout;
  `syncAuthenticator({ ttlMs: NaN })` held a revoked session across a full year
  of clock advance;
  `randomToken(NaN)` returned "" — the framework's secret generator, producing
  no secret and reporting success;
  `generateRecoveryCodes(Infinity)` wedged the process on the enrolment path,
  and `NaN` enrolled a user with zero recovery codes;
  `configureLifecycle({ deadlineMs: NaN })` made a deploy drop in-flight
  requests and abandon close hooks on the first tick;
  an ISR page with a non-finite TTL was never fresh, so it regenerated on EVERY
  request;
  `chunk({ size })` and `embedBatched` were synchronous infinite loops.

THREE BREAKING ENTRIES, all the same shape: a numeric option that used to accept
NaN refuses it, at boot or at the call boundary rather than mid-request. An app
passing real numbers is unaffected. An app passing NaN was not working — the
bound it declared was not being enforced, and nothing said so. `0` stays legal
everywhere it means something: port 0 asks the OS for a free port, timeout 0 is
one look, seed 0 is a seed, maxAgeSeconds 0 is "revalidate every time".

THE RATCHET SHIPPED WITH THE SWEEP AND WAS WIDENED THREE TIMES BY DEFECTS THAT
WALKED PAST IT — an optional chain on the object, a default read out of a table
of numbers, and bare parameter defaults. All three were found the same way: by
TESTING the guard rather than reading it. One clause added along the way
measured inert and was deleted rather than left reading as a rule holding a line
it was not holding. Its non-vacuity guard also broke because the tree got
better: `total > 10` was calibrated at 129 sites, so it failed as the count
approached zero. It now asserts that every PINNED package still reports exactly
its pinned count.

Docs made true rather than restated: CLAUDE.md said `@ultimat3/notify` had never
been published and owed a hand publish before the next release run. True when
written; the 16.0.0 run published it, and the audit answers 31/31 attested — so
following that paragraph would have produced an E403.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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