(motir-core) The fixed-window alignment class survives in two more suites — `surfaceGuards.test.ts` (37 unpinned budgets) and `api-coding-convention-route.test.ts` — because the guard's file list is ENUMERATED, not derived
Repo: motir-core. One PR. Found by the motir run of MOTIR-2647 (2026-08-11) while sweeping for that card's defect, and logged rather than absorbed (notes.html #27) — 2647 is scoped to tests/mcp/rate-limit-gate.test.ts and this is a different, larger surface.
This is the fifth and sixth occurrence of the class MOTIR-2101 → MOTIR-2224 → MOTIR-2598 → MOTIR-2647 have each fixed one file of: a test that pins a rate-limit BUDGET, makes more than one counted call, and leaves the WINDOW at the shipped 60 s default, so the calls straddle an epoch-aligned boundary and the one expected to be refused is served.
The two survivors, grepped on origin/main
Sweep used (a file that assigns a *_RATE_LIMIT env, counted against the *_RATE_LIMIT_WINDOW_MS assignments in the same file):
| file | budget assignments | window pins | imports the helper |
|---|---|---|---|
tests/rateLimit/surfaceGuards.test.ts | 37 | 0 | no |
tests/api-coding-convention-route.test.ts | 1 | 0 | no |
| every other budget-setting suite | 1–3 | ≥1 | yes (or single-call) |
tests/rateLimit/surfaceGuards.test.ts— the largest instance in the tree. ItsENVSarray at:36–50lists all six*_WINDOW_MSnames for cleanup and the file sets none of them: the same "cleans up a knob it never sets" tell MOTIR-2647 was filed on, at six times the scale. Confirmed accumulating cases include:292(generation takes its OWN budget— spend 1, expect the 2nd refused),:302,:313,:322(aDEFAULT_AI_GENERATE_RATE_LIMIT-long loop, then one more),:347,:356,:364. All run against the real Postgres store throughenforceAiRateLimit/enforceInternalServiceRateLimit→consumeSharedRateLimit, i.e. the same epoch-aligned bucket.tests/api-coding-convention-route.test.ts:212—refuses the over-budget re-audit with a 429 BEFORE it submits: budget1, a 202 then an expected 429, no pin. A straddle serves the second request, soexpect(refused.status).toBe(429)sees 202 andtoHaveBeenCalledTimes(1)sees 2.
The reason it keeps surviving — fix THIS, not only the two files
tests/api/v1/rate-limit-window-alignment.test.ts already exists as the anti-recurrence guard, and it has two halves:
- a derived half — no file under
tests/may compute a window phase (Date.now() %) except the shared helper. Total, and it works. - an enumerated half — an
it.each([...])naming the files that must import the helper. Hand-maintained. A file joins it when whoever fixed that file remembered to add it, which is precisely the step that has now been missed four times in a row.
So the guard is a list of the fires already put out. Replace the enumeration with a derivation over the same tree the first half already walks: a test file that assigns a *_RATE_LIMIT env must either pin the matching *_RATE_LIMIT_WINDOW_MS or import waitForWindowBoundary — with an explicit, commented allow-list for the genuinely single-call suites, so an exemption is a decision on the record rather than an omission.
Acceptance criteria
tests/rateLimit/surfaceGuards.test.tspins the window on every case that sets a budget and makes more than one counted call, viaALIGNED_WINDOW_MS+waitForWindowBoundaryfromtests/helpers/rateLimitWindow.ts— no second copy of the arithmetic. Ordered before AC 3 so the derived guard is written against a tree that already passes it.tests/api-coding-convention-route.test.ts:212does the same.rate-limit-window-alignment.test.ts'sit.eachfile list is DERIVED from the test tree rather than enumerated: any file assigning a*_RATE_LIMITenv fails unless it pins the matching window or imports the helper. Every exemption is named with the reason it is one.- The derived guard is proven by DELIBERATELY introducing the violation — a fixture string (or a temporary file) that assigns a budget without a pin makes it fail — mirroring the
the guard actually fires on a hand-rolled copycase already in that suite. A guard nobody has watched fail is indistinguishable from no guard. surfaceGuards.test.ts's added wall-clock cost is stated in the PR body. At 37 sites an unconditional align would add ~37 s; pin the window on every case and align only the ones whose outcome depends on the ACCUMULATED count.
Out of scope
tests/mcp/rate-limit-gate.test.ts (fixed by MOTIR-2647, which also adds itself to the enumerated list this card replaces), and the limiter's fail-open store-deadline arm (lib/rateLimit/limiter.ts:77) — a different bug if it ever fires, and one that produces a byte-identical symptom, so keep them apart. No production code changes.
Context refs
tests/rateLimit/surfaceGuards.test.ts·tests/api-coding-convention-route.test.ts— the two survivors.tests/api/v1/rate-limit-window-alignment.test.ts— the guard, and theit.eachto derive.tests/helpers/rateLimitWindow.ts—ALIGNED_WINDOW_MS,waitForWindowBoundary, and the header explaining the class.lib/rateLimit/limiter.ts:67·lib/rateLimit/budgets.ts— the epoch-aligned bucket and the 60 s defaults.