Phase C cleanup follow-up to the routes/federation split (#9). Behavior-
preserving; full server suite (790 tests) green.
A) rateLimits.ts: the four near-identical sliding-window limiters
(accept/relay/lookup/ensure) and their duplicated prune loops collapse
into one createLimiter(windowMs, max) factory. Per-call and periodic-
sweep semantics are preserved exactly, including that lookup buckets are
pruned per-call but never swept (unchanged from before). 177 -> 101 lines.
B) Extract sendSignedJson(reply, payload, hmacSecret) — the single
definition of how this instance signs an S2S JSON response — and use it
in the /epoch and /verify-attach-proof|reattach handlers, replacing two
copies of the build-headers-and-send boilerplate.
routes/federation.ts had grown to 7.6k lines, spanning HTTP route
registration, federated identity resolution, ~30 inbound relay event
processors, DM reconciliation, and rate-limiting internals — too large
to review or hold in context, and awkward to change safely.
Split the implementation into 18 focused modules under routes/federation/
(helpers, events/, handlers/) and keep routes/federation.ts as a thin
barrel that re-exports the public API and composes the HTTP registrars
into federationRoutes(). No import paths change anywhere else.
Pure move, no behavior change:
- 61/61 named functions byte-identical; only deltas are 2 dynamic-import
paths adjusted for the new directory depth
- public export surface unchanged (barrel re-exports all 22 symbols)
- all 30 endpoints preserved (identical verb+path set)
- typecheck, build, and full server suite (790 tests) green
Docs: update federation.md source-file map; add split design doc.