# Tasks — Timing completion (rateLimit / once / times) > Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. ## Audit Note (2026-05-02) Status: **DEFERRED**. None of `rateLimit`, `once`, `times`, or the `RATE_LIMITED` sentinel exist in `packages/timing/timing/src/throttle/` (currently only contains `throttle.ts`, `throttle.types.ts`, `throttle.schemas.ts`, plus `debounce.test.ts`). 0% of work is done. ## 1. Spec authoring - [ ] 1.1 Read `../suaveplan/packages/universal/timing/src/throttle/throttle.ts` (rateLimit / once / times sections, L275-L346) - [ ] 1.2 Read `openspec/specs/timing/spec.md` and the archived `2026-04-29-timing-spec-conformance` proposal - [ ] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14 - [ ] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md` - [ ] 1.5 Fill in `## Source mapping` table — every requirement cites a line range OR appears in the "no source line" table with justification - [ ] 1.6 Draft `specs/timing/spec.md` from `openspec/templates/spec-delta.md` - [ ] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles ## 2. Implementation - [ ] 2.1 Register error code `TIMING.RATE_LIMIT_INVALID_WINDOW` in `src/errors/` - [ ] 2.2 Create `src/throttle/rate-limit/` feature module with `rate-limit.ts`, `rate-limit.types.ts`, `rate-limit.schemas.ts`, `rate-limit.md`, `index.ts` - [ ] 2.3 Implement `rateLimit(fn, options)` with sliding-window FIFO and injectable `Clock`; export `RATE_LIMITED` sentinel and `RateLimitedSentinel` type alias - [ ] 2.4 Create `src/throttle/once/` feature module with `once.ts`, `once.md`, `index.ts` - [ ] 2.5 Implement `once(fn)` with throw-and-retry semantics - [ ] 2.6 Create `src/throttle/times/` feature module with `times.ts`, `times.md`, `index.ts` - [ ] 2.7 Implement `times(fn, maxCalls)` - [ ] 2.8 Wire OTel counters `timing.rate_limit.rejected`, `timing.once.invocation`, `timing.times.invocation` via `src/otel/` - [ ] 2.9 Add subpath exports `./throttle/rate-limit`, `./throttle/once`, `./throttle/times` to `package.json::exports` - [ ] 2.10 Re-export the three helpers + `RATE_LIMITED` from the root barrel ## 3. Tests - [ ] 3.1 Unit tests at `src/throttle/rate-limit/rate-limit.test.ts` — bursts inside window, eviction across window boundary, sentinel returned at cap, injectable clock drives eviction - [ ] 3.2 Spec-conformance tests at `src/throttle/rate-limit/rate-limit.spec-conformance.test.ts` — `RATE_LIMITED` unique symbol pinned via `expectTypeOf`, counter increments on rejection (real `MeterProvider`), invalid options throw `TIMING.RATE_LIMIT_INVALID_WINDOW` - [ ] 3.3 Unit tests at `src/throttle/once/once.test.ts` — runs once, caches return, throw-and-retry flow - [ ] 3.4 Spec-conformance tests at `src/throttle/once/once.spec-conformance.test.ts` — counter increments on first invocation only, `TReturn` (not `TReturn | undefined`) on wrapper - [ ] 3.5 Unit tests at `src/throttle/times/times.test.ts` — runs `n` times, returns `undefined` thereafter - [ ] 3.6 Spec-conformance tests at `src/throttle/times/times.spec-conformance.test.ts` — counter increments per invocation up to `maxCalls` - [ ] 3.7 Property-based tests via `fast-check` for `rateLimit` window invariant: at any instant, the count of timestamps in the FIFO is at most `maxCalls` - [ ] 3.8 Every `it(...)` carries `{ timeout: 5000 }` ## 4. Documentation - [ ] 4.1 `src/throttle/rate-limit/rate-limit.md` — sibling doc (≥200 words, basic + advanced, full API including `RATE_LIMITED` sentinel) - [ ] 4.2 `src/throttle/once/once.md` — sibling doc (≥200 words, document throw-and-retry semantics explicitly, distinguish from legacy) - [ ] 4.3 `src/throttle/times/times.md` — sibling doc (≥200 words) - [ ] 4.4 README links all three `.md` files under `## Modules` - [ ] 4.5 Update `CLAUDE.md` `@suaveplan/timing` entry to include `rateLimit`, `once`, `times` ## 5. Pre-archive verification (the 5-gate triple) - [ ] 5.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo - [ ] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/timing` — green - [ ] 5.3 `bunx turbo test:coverage --filter=@suaveplan/timing` — 100/100/100/100 - [ ] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/timing` — exit 0, no `error` findings - [ ] 5.5 `bunx turbo build --filter=@suaveplan/timing` — green ## 6. Pre-archive 7-row checklist (AGENTS.md Section 13) - [ ] 6.1 Generics threaded through every wrapper; `expectTypeOf` confirms `RATE_LIMITED` is a unique symbol - [ ] 6.2 Zod schemas wired at `rateLimit` boundary (validates `maxCalls >= 1`, `windowMs > 0`) - [ ] 6.3 `TIMING.RATE_LIMIT_INVALID_WINDOW` thrown by at least one production path - [ ] 6.4 Strict-mode behaviors tested (e.g. `once` throw-and-retry vs throw-and-cache contrast) - [ ] 6.5 OTel counters emitted from production paths and asserted with a real in-memory `MeterProvider` - [ ] 6.6 Lifecycle dedup — `once` itself is the dedup primitive; tested directly - [ ] 6.7 No retroactive tick — every box above reflects work done in this change ## 7. Archive - [ ] 7.1 Fold spec delta into `openspec/specs/timing/spec.md` - [ ] 7.2 Move `openspec/changes/timing-completion/` → `openspec/archive/-timing-completion/` - [ ] 7.3 Refresh `MANIFEST.md` if dep set changed (no expected change)