Commit fb2bccf
fix(spec): close the shared rate-limit budget — one declaration was answering two doors, and one of them dropped the key in silence (#18861)
Fixes #18578
Clause-②: yes (widening)
`ServerRateLimitConfigSchema` was declared `strictObject({ … guidance: {
keyBy, store } }, RateLimitConfigSchema.shape)` — built from the OPEN
schema's own shape object. One declaration therefore answered for
**two** emitted defs with opposite doors:
| def | door before | an authored `keyBy` |
|---|---|---|
| `system/ServerRateLimitConfig` | closed | refused, loudly, with the
prescription |
| `shared/RateLimitConfig` | plain open `z.object` | accepted, then
dropped in silence |
The two `guidance` entries prescribed to nobody on the open twin. And a
misspelled budget was the same story one key over: on the bare mount
`windowSeconds: 60` parsed green and metered the 60000 ms default — a
thousandfold miss on the one key whose job is to bound spend, reported
as success.
## The census — the method, re-run on current `main`
⛔ Not re-derived by grep: the card records why a `CONTRACT_REVIEW_TIER`
sweep with live controls found nothing here (it hunted an OPEN clone
built from a STRICT schema's shape; this is the STRICT one built from
the OPEN one's shape, which every grep shape in that sweep is blind to
by construction). The instrument is the gate's own —
`computeGuidanceRoutes()` in `packages/spec/scripts/build-schemas.ts`:
match every emitted def to its declaration by sorted key set plus
per-entry instance identity, then write the key at the def and read what
comes back.
Driven over every emitted def at `42f8df1723` (current `main` when this
branch was cut), and again on this branch:
| reading | before | after |
|---|---|---|
| emitted defs | 1527 | 1527 |
| `strictObject` declarations registered | 551 | 551 |
| defs resolving to exactly one declaration | 258 | 258 |
| … of those, naming at least one undeclared key | 147 | 147 |
| keys promised | 779 | 779 |
| keys **delivered** | 770 | **772** |
| keys **not delivered** | 9 | **7** |
The filer's figures reproduce exactly (258 / 779 / 770 / 9); the def
total moved 1525 → 1527 with the tree. One refinement worth recording:
**258 is the count of defs resolving to exactly one declaration**, and
the subset whose declaration also names an undeclared key is **147** —
the card's sentence folds the two.
The 9 not-delivered, itemised:
- **2** — `shared/RateLimitConfig:keyBy` and `:store`, the live members
this card is about. Probe verdict `accepted-and-stripped`.
- **7** — `ui/ChartGroupBy:function`, `ui/ChartGroupBy:groupBy`,
`ui/RecordHighlightsField:icon`, `ui/ViewItem:confg`,
`ui/ViewItem:isPinned`, `ui/ViewItem:sortOrder`,
`ui/ViewItem:columnState`. **An acknowledged probe boundary, ⛔ not a
clean zero and ⛔ not a finding** — and the mechanism is now measured
rather than assumed. Each is a discriminated union; the probe writes `{
[key]: null }` and nothing else, so the DISCRIMINATOR is missing and the
union answers `invalid_union` on `viewKind` before any arm's door is
reached. Written whole, the same document DOES raise the prescription.
So they are refused loudly today, just not through a door this
instrument can watch.
## The shape chosen, and why
The strictness and both tables move to the **shared** schema;
`ServerRateLimitConfigSchema` keeps only what is genuinely server-only —
its two bounds checks — and is now
`RateLimitConfigSchema.superRefine(…)` rather than a second
`strictObject` over the same shape object.
That leaves **one declaration and one door for both defs**, which is
load-bearing in three ways:
1. the declaration match still resolves to exactly **one** declaration,
so `matched.length !== 1` never fires and proof 4 keeps working for both
defs. Declaring a second `strictObject` over the same shape object would
have restored the ambiguity in the other direction, where the match
resolves to neither and both defs silently read "no evidence";
2. the closed twin's verdict is untouched — same acceptances, same
refusals, same prescription bullet, same bounds;
3. the `shared/` ledger row's own rationale — *strictness decided at the
consuming schema* — is false for this shape, exactly as it was false for
`shared/protection.zod.ts`. Of the two mounts only one re-postured;
`api/endpoint.zod.ts` mounts it bare on `apis[].rateLimit`, a registered
metadata type authored through `defineStack({ apis })`, the Studio form
and `PUT /meta/api/:name`, where nothing re-postures it. The row is
annotated for what is now the fourth instance of a shape that ledger has
recorded three times.
## Controls
**LIT — behaviour flips.** Same probe, run against this branch and
against the two source files restored to the base commit (ablation with
an `EXIT`/`INT`/`TERM` restore trap; both ablated blobs verified by `git
hash-object` against the base blob hashes, and the restore verified by
an empty `git diff HEAD`):
| written on `shared/RateLimitConfig` | before | after |
|---|---|---|
| `{ …valid, keyBy: 'ip' }` | PARSED OK → `{enabled, windowMs,
maxRequests}` — key gone | REFUSED, `Unrecognized key(s) on this
rate-limit budget …: keyBy.` + the `keyBy` prescription |
| `{ …valid, store: 'redis' }` | PARSED OK, key gone | REFUSED + the
`store` prescription |
| `{ enabled: true, windowSeconds: 60, maxRequests: 100 }` | PARSED OK →
`windowMs: 60000` | REFUSED, `Did you mean windowSeconds → windowMs?` |
| an `api` endpoint whose `rateLimit` carries `keyBy` | PARSED OK, key
gone | REFUSED with the same prescription, at the author's own path |
Census leg of the same flip: 770 → 772 delivered, 9 → 7 not delivered.
**DARK — reads what it must.** `system/ServerRateLimitConfig`, before
and after: a legitimate budget parses to the same document; `{ enabled:
true }` still materialises the same defaults; `max: 5` is still renamed
to `maxRequests`; `maxRequests: 0` and `windowMs: 0` are still refused
on `path: ['maxRequests']` / `['windowMs']` with their own messages; `{
…valid, keyBy: 'ip' }` is still refused carrying the prescription
bullet. A legitimate endpoint document with a legitimate `rateLimit`
still parses, before and after. The census's 258 / 779 are unchanged, so
the closed twin's declaration resolution did not move either.
1 parent a43b9d0 commit fb2bccf
7 files changed
Lines changed: 297 additions & 125 deletions
File tree
- .changeset
- docs/audits
- packages/spec
- scripts
- src
- integration
- system
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
264 | 264 | | |
265 | 265 | | |
266 | 266 | | |
267 | | - | |
| 267 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1374 | 1374 | | |
1375 | 1375 | | |
1376 | 1376 | | |
1377 | | - | |
| 1377 | + | |
1378 | 1378 | | |
1379 | 1379 | | |
1380 | 1380 | | |
| |||
0 commit comments