Skip to content

fix(core,auth,http,time): randomToken(NaN) was the empty string, and a deploy abandoned its own shutdown - #377

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

fix(core,auth,http,time): randomToken(NaN) was the empty string, and a deploy abandoned its own shutdown#377
sebyx07 merged 1 commit into
mainfrom
fix/sweep-17-blind-spots

Conversation

@sebyx07

@sebyx07 sebyx07 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

The defects the finite-bounds ratchet cannot see. It matches a.b ?? <number> and nothing else; these are bare parameter defaults and one !== undefined assignment, which is why five merged slices (#364, #370, #374, #375) walked past them while driving that rule from 129 sites to 4.

All three were found the same way the ratchet's own two blind spots were found: by testing the guard rather than reading it.

The three that matter

randomToken(NaN) returned "". The framework's secret generator, producing no secret and reporting success. nanoid and randomHex did the same — and randomHex is what traceId(), spanId() and uuid() mint from. A negative length threw a bare, uncoded RangeError.

generateRecoveryCodes(Infinity) wedged the process. Synchronously, on the enrolment path, from a public barrel export: for (index = 0; index < Infinity; …) has no exit. Measured — timeout 15 → exit 124 — and the mutation proof for that case is that the test run never terminates. NaN enrolled a user with zero recovery codes in a well-formed RecoveryCodeSet; 2.5 silently gave three. The second hang in @ultimat3/auth after mfa.drift.

configureLifecycle({ deadlineMs: NaN }) made a deploy abandon its own shutdown. In-flight requests dropped, close hooks ABANDONED on the first tick. Measured: drain() returned in 16 ms with inflight still 1, and Bun printed TimeoutNaNWarning. packages/cli/src/hold.ts carried the identical defect and is fixed transitively, so a reader of that file will see no diff.

The X_SHUTDOWN_TIMEOUT message is deliberately unchanged, and that is the answer

It said 1 in-flight operations still running after NaNms, with a fix: telling the operator to raise the budget — advice that cannot help, because the value is not a budget that is too small.

With deadlineMs screened, deadlineMs has exactly two writers and deadlineAt is finite by construction, so NaNms is unreachable. Writing a "the budget is not a number" branch would be a branch no test can reach — in the one repo whose gate has a ratchet named a test that cannot fail. The screen's own refusal is the message for that case, and it arrives at boot, at the declaration, not at the deploy that drops the requests.

Two more

  • A route's cache hint was only caught on the response path, once per request, forever. Now refused where it is declared, at createRouter, naming the route file and meta.cache.<field>. The layered form the whole sweep uses: refuse where the value is written, be total where it is used. maxAgeSeconds: 0 stays legal — it is "revalidate every time", three framework defaults declare it, and a floor of 1 refuses the framework at its own boot.
  • @ultimat3/time's toMs passed a non-finite number straight through, so time and notify gave opposite answers to one input after the tier-4 slice screened the copy and not the original. It gets finiteOption, not finiteCount: toSeconds(-3000) === -3 and toSeconds('-1500ms') === -2 are shipped, tested behaviour, so negatives and fractions are unchanged.

Two of my own premises were wrong, and the code disproved both

  1. notify's toDurationMs is not a duplicate. notify,time: toDurationMs in notify is a second copy of time's toMs, and the two now DISAGREE — one screens a NaN, the other passes it through #372 called it byte-for-byte identical to toMs. The tier-4 slice had already turned it into a screened narrowing with a named subject, and that wrapper is exactly what makes it not a copy — routing it through toMs would fire a generic refusal first and lose the option name (deliver[email].digest.window) that slice added on purpose. So no public export is deleted and there is no breaking removal.
  2. A second finite-bounds matcher for the !== undefined form is not worth building. I suggested it in auth,core: a bare parameter default is the sweep's fourth blind spot — randomToken(NaN) is the empty string, and generateRecoveryCodes(NaN) enrols a user with zero codes #371. Measured instead: 24 occurrences across core+auth, and exactly one is numeric. The productive scan was bare parameter defaults, which found five exported functions — the three named in the issue, plus randomHex (fixed here) and startMetricExport (already screened by an earlier slice).

A third correction: one worker caught itself mid-draft writing Number.isSafeInteger(v) && v >= 0 into two files — the exact duplication this sweep exists to close — and collapsed it into one exported predicate before finishing.

Also here

  • MAX_PROXY_HOPS is exported from @ultimat3/http and packages/cli/src/dev-roles.ts imports it instead of restating 64. The behavioural test comparing the two ceilings is kept, not deleted: it compares what each screen accepts, so it still catches them diverging for a reason a shared constant cannot fix — one side gaining a range check the other lacks.
  • release-facts reads a stat strip now, closing social-media-clone: the deployed demo contradicts itself on the feed, and release-facts cannot see the string that does it #369's fourth item. The deployed demo rendered 29 packages published in lockstep against a tree that publishes 31 — and widening the globs to JSON would not have caught it. The count and the words it counts are on two different lines under different JSON keys, and every pattern in that file matches a number adjacent to its subject. A {value, label} pair is folded onto one line, the label line blanked so line numbers stay true and a finding cites the line a human edits. Mutation-proven by restoring the shipped 29.
  • The demo's other two self-contradictions: an empty-state string claiming a signed-out reader is shown nothing (the seed writes two public posts), and a post whose own body said it "must never appear" while correctly appearing to an anonymous reader — the policy was right and the sentence was wrong.

Verification

bun run verify14 of 20 passed, 6 skipped. bun run scripts/reference-app-gate.tsevery pin holds; examples/dummy 18/20, dummy/social-media-clone 18/20, 2 pinned each.

The app gate caught a real regression the framework gate cannot see. The seed.ts copy fix lives under dummy/social-media-clone/packages/db/src/, and the drift hash covers both the entity registry and every non-test file in that directory — deliberately, so a seed moving under the schema is caught. x db gen answered no migration needed — schema hash re-recorded, which is the designed path; no empty migration was generated. It was a regression against a passing step, not a pinned one, and the ratchet held me to it.

Every behavioural fix is mutation-proven, including every floor flipped in both directions. configureLifecycle's floor is 0 and not 1, checked rather than assumed: @ultimat3/http screens drainTimeoutMs with a floor of 0 and hands it straight to configureLifecycle, so a floor of 1 would refuse at boot what http accepts in every process that serves web.

No new error code, so no wiki/Error-Codes.md row and no manifest change — X_INVARIANT and X_CONFIG_INVALID are both already documented.

Fixes #369, #371, #372, #373.

Filed, not fixed here

#376 — a THIRD toMs, and the worst-placed one. packages/jobs/src/clock.ts:18, whose comment reads "Numbers pass through so callers may stay explicit", with no screen at all. It is the one step.sleep's wake time, the retry curve's base/max, and every job timeout go through — so step.sleep(Number(process.env.X)) is still a sleep that never ends. Two of its callers wrap the result in finiteOption, which makes the file read as screened.


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

…a deploy abandoned its own shutdown

The defects the finite-bounds ratchet cannot see. It matches `a.b ?? <number>`
and nothing else; these are bare PARAMETER defaults and a `!== undefined`
assignment, which is why five merged slices walked past them.

`randomToken(NaN)` returned `""` — the framework's secret generator, producing
no secret and reporting success. `nanoid` and `randomHex` did the same, and
`randomHex` is what traceId(), spanId() and uuid() mint from. A negative length
threw a bare, uncoded RangeError.

`generateRecoveryCodes(Infinity)` WEDGED THE PROCESS, synchronously, on the
enrolment path, from a public export: `for (index = 0; index < Infinity; …)`
has no exit. Measured, timeout 15 → exit 124, and the mutation proof for that
case is that the test run never terminates. `NaN` enrolled a user with zero
recovery codes in a well-formed RecoveryCodeSet; `2.5` silently gave three.
The second hang in @ultimat3/auth after `mfa.drift`.

`configureLifecycle({ deadlineMs: NaN })` made a deploy abandon its own
shutdown — in-flight requests dropped and close hooks ABANDONED on the first
tick. Measured: drain() returned in 16ms with inflight still 1, and Bun printed
TimeoutNaNWarning. `packages/cli/src/hold.ts` carried the identical defect and
is fixed transitively, so a reader of that file will see no diff.

THE X_SHUTDOWN_TIMEOUT MESSAGE IS DELIBERATELY UNCHANGED. It said `after NaNms`
with a fix: telling the operator to raise the budget, which cannot help when the
value is not a budget. With the bound screened that state is unreachable, so a
branch for it would be a test that cannot fail — in the one repo whose gate has
a ratchet by that name. The screen's own refusal is the message, and it arrives
at boot rather than at the deploy that drops the requests.

A route's `cache` hint is now refused where it is DECLARED, at createRouter,
naming the route file and the key — the layered form: refuse where the value is
written, be total where it is used. `maxAgeSeconds: 0` stays legal; it is
"revalidate every time" and three framework defaults declare it.

@ultimat3/time's `toMs` passed a non-finite number through, so time and notify
gave opposite answers to one input after the tier-4 slice screened the copy and
not the original. It gets finiteOption and NOT finiteCount: `toSeconds(-3000)
=== -3` is shipped, tested behaviour, so negatives and fractions are unchanged.

TWO OF MY OWN PREMISES WERE WRONG, and the code disproved both. `notify`'s
`toDurationMs` is NOT a duplicate — the tier-4 slice made it a screened
narrowing, and collapsing it would have LOST the option name that slice added
on purpose, so the public export stays and there is no breaking deletion. And a
second `finite-bounds` matcher for the `!== undefined` form is not worth
building: measured, 24 occurrences across core+auth and exactly ONE is numeric.

MAX_PROXY_HOPS is exported from @ultimat3/http and the CLI imports it instead of
restating 64. The behavioural test comparing the two ceilings is KEPT, not
deleted: it compares what each screen accepts, so it still catches them
diverging for a reason a shared constant cannot fix.

`release-facts` now reads a stat strip. The deployed demo rendered "29 packages
published in lockstep" against a tree that publishes 31, and widening the globs
to JSON would NOT have caught it — the count and the words it counts are on two
different lines under different JSON keys, and every pattern here matches a
number ADJACENT to its subject. A {value,label} pair is folded onto one line,
the label line blanked so line numbers stay true. The demo's other two
self-contradictions go with it: an empty-state string claiming a signed-out
reader sees nothing (the seed writes two public posts), and a post whose own
body said it must never appear while correctly appearing.

Fixes #369, #371, #372, #373.

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 14 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: c99a7864-6616-4fac-9831-0f565b12e3b3

📥 Commits

Reviewing files that changed from the base of the PR and between 4f650de and 2b7760d.

⛔ Files ignored due to path filters (1)
  • dummy/social-media-clone/packages/db/migrations/20260825232342_record_handle_and_responded_coherence_checks.hash is excluded by !**/migrations/**
📒 Files selected for processing (28)
  • CHANGELOG.md
  • dummy/social-media-clone/packages/db/src/seed.ts
  • dummy/social-media-clone/packages/i18n/catalogs/en.json
  • packages/auth/src/mfa-bounds.test.ts
  • packages/auth/src/mfa.ts
  • packages/auth/src/tokens-bounds.test.ts
  • packages/auth/src/tokens.ts
  • packages/cli/src/dev-roles.ts
  • packages/core/src/ids-bounds.test.ts
  • packages/core/src/ids.ts
  • packages/core/src/lifecycle-bounds.test.ts
  • packages/core/src/lifecycle.ts
  • packages/http/CLAUDE.md
  • packages/http/src/config.ts
  • packages/http/src/errors.ts
  • packages/http/src/index.ts
  • packages/http/src/response.ts
  • packages/http/src/route-cache.test.ts
  • packages/http/src/route-cache.ts
  • packages/http/src/router.ts
  • packages/notify/CLAUDE.md
  • packages/notify/src/plan-bounds.test.ts
  • packages/notify/src/plan.ts
  • packages/time/CLAUDE.md
  • packages/time/src/duration-bounds.test.ts
  • packages/time/src/duration.ts
  • scripts/release-facts.test.ts
  • scripts/release-facts.ts

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

@sebyx07
sebyx07 merged commit e33e32e into main Aug 26, 2026
38 checks passed
@sebyx07
sebyx07 deleted the fix/sweep-17-blind-spots branch August 26, 2026 20:43
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.

social-media-clone: the deployed demo contradicts itself on the feed, and release-facts cannot see the string that does it

1 participant