Skip to content

Rate-limit the hot-seat beacon and cap its stored rows - #35

Merged
JacobStephens2 merged 1 commit into
masterfrom
issue-34
Oct 22, 2026
Merged

JacobStephens2 merged 1 commit into
masterfrom
issue-34

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

POST /api/events/hotseat now uses the room-creation limiter, then refuses a new stored row past 50.

POST /api/events/hotseat
  parse { gameId, type }
  roomCreateCaller(socket peer, X-Forwarded-For)
  RoomCreateLimiter.allow(caller)     # same limiter as POST /api/rooms, 10 per minute
    refuse -> 429, no insert
  recordHotseatEvent
    new hot-seat row and countHotseatRows() >= 50
      refuse -> 400, no insert
    insert room + game_event (mode hotseat, test 0)
  204

The unfinished-room floor is unchanged. RoomManager.create still stops at countActiveRooms() >= 50, and that count is still terminal_event IS NULL.

Seams

  • POST /api/events/hotseat: caller key, shared 10-per-minute allowance, 429 with no insert, cap of 50 new hot-seat rows, repeat of a stored game, floor left at 50 unfinished rooms.
  • RoomDatabase.getRoom, countHotseatRows, and countActiveRooms: a refused beacon leaves no row, and hot-seat rows are not the floor.

Evidence

  • Before: npx vitest run packages/server/tests/hotseat-beacon.test.ts failed on the 11th beacon (expected 204 to be 429). After the limiter, the 51st new beacon still failed (expected 204 to be 400). A repeat at the cap then failed (expected 400 to be 204).
    After: npx vitest run packages/server/tests/hotseat-beacon.test.ts — 7 passed. npm run typecheck -w @clave/server passed. npx vitest run --testTimeout 20000 — 117 passed. Default npm test hit the 5s timeout in packages/engine/tests/properties.test.ts (about 7.7s, not in this diff); that file passed with --testTimeout 20000.

Merge Danger

Door: two-way

Revert the server deploy and unlimited inserts return. Rows already stored stay until the 30-day expiry sweep. No migration.

Blast Radius: hot-seat completions and room creation

One caller now shares 10 requests a minute across POST /api/rooms and the beacon. A 51st new hot-seat row is refused, so SM-1 can miss couch games past that cap or past the rate limit. The beacon body and ops/metrics.sql are unchanged. The online floor stays 50 unfinished rooms.

Unaddressed findings

Standards

  • Mysterious Name (judgement call). roomCreateCaller and RoomCreateLimiter still say room create, at the new call in packages/server/src/app.ts lines 106-111. Issue Rate-limit the hot-seat beacon and cap its stored rows #34 says "Call the existing room-creation rate limiter, with its existing caller key." Those names in packages/server/src/room-create-limit.ts are that limiter. Renaming them is not this issue.

  • Duplicated Code (judgement call). The beacon handler repeats the allow-or-429 shape of POST /api/rooms (packages/server/src/app.ts lines 38-43), and the beacon tests repeat the caller-key cases from packages/server/tests/room-create-limit.test.ts. The room route still answers "Too many rooms created. Try again in a minute." The beacon's 429 string is different because a beacon is not a room create. The beacon tests are what check POST /api/events/hotseat; the room-create tests never hit that route. A shared helper would edit the room-creation path this issue leaves alone.

Spec

None.

Security

None.

Security review outcome

No unaddressed introduced findings.

Review coverage

Both axes read every changed file: packages/server/src/app.ts, packages/server/src/database.ts, and packages/server/tests/hotseat-beacon.test.ts. No changed file was left unread. No generated output was excluded.

Closes #34

Built with grok · grok-4.7 · xhigh

POST /api/events/hotseat inserted a row for every completion, with no caller limit and no cap. It now calls the room-creation limiter, using that limiter's caller key, before insert. The eleventh beacon from one caller in a minute returns 429 and stores nothing.

Stored hot-seat rows stop at 50, counted on their own. The unfinished-room floor stays 50 and still counts only unfinished rooms. A beacon that would store a new row past the cap is refused. A repeat for a game already stored is still accepted. The beacon request and SM-1 are unchanged.

Tests: packages/server/tests/hotseat-beacon.test.ts.
@JacobStephens2
JacobStephens2 merged commit 9a0918a into master Oct 22, 2026
4 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-34 branch October 22, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rate-limit the hot-seat beacon and cap its stored rows

1 participant