#!/usr/bin/env node // tools/scripts/lint-rate-limit-budgets.mjs // Source: 04-CONTEXT.md D-22 — drift guard for RATES constants in // apps/server/src/rate-limit.ts. Refusing silent budget changes via PR. // // Usage: node tools/scripts/lint-rate-limit-budgets.mjs // Exit: 0 in-sync, 1 drift detected. import { readFileSync } from 'node:fs'; // Phase 6 D-09 — `input` budget relaxed from 25/35 to 10/15. c2s.input is // event-driven now (keydown/keyup transitions + 15s heartbeat reaffirm), // not per-tick, so the legitimate rate is ~3-5 msg/s; we keep enough // headroom for stop-start spamming without giving an attacker the per-tick // flood ceiling. ADR 0007 documents the change. const EXPECTED = [ { type: 'input', rate: 10, burst: 15 }, { type: 'chat_send', rate: 2, burst: 5 }, { type: 'room_join', rate: 1, burst: 2 }, { type: 'heartbeat', rate: 2, burst: 4 }, { type: 'auth', rate: 0.1, burst: 3 }, ]; const src = readFileSync('apps/server/src/rate-limit.ts', 'utf-8'); let violations = 0; for (const e of EXPECTED) { const re = new RegExp(`${e.type}\\s*:\\s*\\{\\s*rate\\s*:\\s*${e.rate}\\s*,\\s*burst\\s*:\\s*${e.burst}\\s*\\}`); if (!re.test(src)) { process.stderr.write(`lint-rate-limit-budgets: missing or drifted '${e.type}: { rate: ${e.rate}, burst: ${e.burst} }'\n`); violations++; } } if (violations > 0) process.exit(1); console.log('lint-rate-limit-budgets: OK (5 D-22 budgets locked)'); process.exit(0);