Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-3016Done

(motir-core) `rateLimit/guard.test.ts` never aligns its window — the LAST unswept member of the epoch-boundary class, and the alignment guard is structurally blind to it

Repo: motir-core. One PR. Runtime tests only — no production code changes. Found by motir run MOTIR-2903 (PR #2120, run 32170310628, job 95819607587), on a diff that touches no limiter, route, store or middleware file.

The failure

FAIL  tests/rateLimit/guard.test.ts > the 429 is spec-correct > carries Retry-After AND the X-RateLimit triple
AssertionError: expected null not to be null
 ❯ tests/rateLimit/guard.test.ts:47:21

tests/rateLimit/guard.test.ts:21 declares const WINDOW = 60_000 and every case builds its budgets from it. The failing case takes limit: 1, calls enforceRateLimit twice back to back, and expects the second to be refused — with no boundary alignment anywhere in the file.

lib/rateLimit/limiter.ts buckets on an epoch-aligned fixed grid (windowStart = Math.floor(now / windowMs) * windowMs), so the window does not open at the first call. When a minute boundary falls between the pair the counter resets, the second call is allowed, and second.response is null. Unlucky PHASE, not a slow runner — which is why it clears on a re-run and is invisible locally.

It is the WINDOW, not the fail-open — the discriminator was run

MOTIR-2658 established that a store timeout produces a byte-identical symptom. Checked, and it is not that: the job log contains exactly one [rateLimit] store unavailable; allowing the request line, at 18:28:23, and vitest's stderr | <file> > <test name> header attributes it to tests/api-ai-chat-route.test.ts. guard.test.ts ran at 18:34:00. Absent for this test ⇒ the window.

Why the existing fix did not reach it

MOTIR-2224 lifted the arithmetic into tests/helpers/rateLimitWindow.ts and swept the accumulating sites, and tests/api/v1/rate-limit-window-alignment.test.ts guards against regression. Neither reaches this file, for two separate reasons:

  1. The sweep was scoped to tests/api/v1/**. tests/rateLimit/ was not in it. Its sibling tests/rateLimit/surfaceGuards.test.ts does align (ALIGNED_WINDOW_MS + waitForWindowHeadroom, with a header explaining why) — so the directory is half-converted and this file is the half that was missed.
  2. The guard cannot see it. rate-limit-window-alignment.test.ts fails a file that recomputes a window phase outside the helper. This file does not recompute anything — it simply never aligns at all, and an absent call matches no grep. That is the same "sweep on the SYMPTOM, not on the helper name" lesson MOTIR-2224 itself recorded, one turn later.

Acceptance criteria

  1. tests/rateLimit/guard.test.ts pins its window to ALIGNED_WINDOW_MS and waits for headroom before every case that ACCUMULATES a count across two or more calls, following tests/rateLimit/surfaceGuards.test.ts's existing shape in the same directory rather than introducing a third idiom. Cases that make a single call and assert only on shape need no wait; say per case which it is.
  2. The WINDOW = 60_000 constant is replaced, not merely supplemented — a file holding both an aligned helper and a raw 60 s constant is the state this defect grew in.
  3. The straddle is proven CONVERTED, not thinned, by measurement in the PR body: run the accumulating pair in a loop for at least one full cell, count the iterations whose start and end fall in different Math.floor(t / windowMs) buckets, and report straddles → failures before and after. MOTIR-2224's numbers (14 straddles → 14 failures unaligned; 0 → 0 aligned) are the template.
  4. The GUARD is widened to catch an absent alignment, not only a recomputed phase — that is what makes this the last instance instead of the next-to-last. Assert over tests/** that every file which builds a rate-limit budget with an accumulating assertion imports the helper; a file that legitimately does not accumulate opts out by a named predicate or an enumerated allowlist with a reason per entry, never by silence.
  5. tests/rateLimit/ is swept whole: report per file whether it accumulates, and whether it aligns.

Out of scope

  • lib/rateLimit/** — the limiter is correct. A fixed epoch grid is the intended design; the tests assert a race they do not control.
  • MOTIR-2658's store-timeout override, which is a different mechanism with the same symptom.

Context refs

  • tests/rateLimit/guard.test.ts:21 (the raw WINDOW), :35, :47 (the failing pair).
  • tests/rateLimit/surfaceGuards.test.ts:31-89 — the aligned shape to copy, in the same directory.
  • tests/helpers/rateLimitWindow.tsALIGNED_WINDOW_MS, waitForWindowBoundary, waitForWindowHeadroom.
  • tests/api/v1/rate-limit-window-alignment.test.ts — the guard to widen.
  • lib/rateLimit/limiter.tswindowStart = Math.floor(now / windowMs) * windowMs.