feat(relay): cap phones per server-id (#30)#43
Conversation
Code Review: #30Decision: PASS Findings
What I verifiedSpec compliance. Every item in the "Done means" section is present:
Blast radius. Architect's non-gating SHOULD FIX is addressed. Security walk (security-sensitive label):
Build/test: SummaryFaithful spec implementation. Dual-method design ( |
Bound the 1:N phone slice per server-id so a misbehaving binary — or one whose token has leaked to many phones — cannot grow phones[serverID] without limit and inflate per-frame fanout cost. - Add ErrPhonesAtCap sentinel and RegisterPhoneCapped(serverID, conn, max) to Registry. RegisterPhone now delegates with max=0 (no cap). The cap check and the slice append run under one write lock; race shape mirrors ClaimServer's first-claim-wins. - ClientHandler gains a maxPhones parameter. Over-cap registrations are rejected with WS close code 4429 and reason "too many phones for server-id" on the underlying *websocket.Conn (stillborn-WSConn pattern, symmetric with the 4404 branch). Log event phone_register_at_cap carries server_id + remote; no token, no device-name. - Wiring: cmd/pyrycode-relay/main.go passes 16 (phone + tablet + several desktops + headroom) at construction; tests pass 0 to disable the cap. - Tests: four new registry unit tests (boundary, recovery, per-server-id independence, wrapper-delegation invariant) and one integration test asserting 4429 reaches the wire. TestRegistry_RaceFreedom now also exercises RegisterPhoneCapped under the race detector. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-ticket note in codebase/30.md plus updates to the client-endpoint and connection-registry feature docs and the knowledge index for the new ErrPhonesAtCap sentinel, RegisterPhoneCapped, the 4429 close code, and the phone_register_at_cap log event. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What
Bound the 1:N phone slice per server-id with a configurable cap (production: 16). A misbehaving binary — or one whose token has leaked to many phones — can no longer grow
phones[serverID]without limit, and the per-frame fanout cost inStartBinaryForwarderis bounded above bymaxPhones.internal/relay/registry.go— new sentinelErrPhonesAtCap; newRegisterPhoneCapped(serverID, conn, max int) errorwith cap-check and append under one write lock;RegisterPhonedelegates withmax=0(no cap).internal/relay/client_endpoint.go—ClientHandlergainsmaxPhones int. Over-cap registrations are closed on the underlying*websocket.Connwith code4429and reason"too many phones for server-id"(stillborn-WSConn pattern, symmetric with the 4404 branch). New log eventphone_register_at_capcarriesserver_id+remote; no token, no device-name.cmd/pyrycode-relay/main.go— passes literal16at the wiring site (phone + tablet + several desktops + headroom).Issue
Closes #30.
Testing
TestRegisterPhoneCapped_BoundaryAndRejection— registering exactlymaxphones succeeds; the next attempt returnsErrPhonesAtCapand does not append.TestRegisterPhoneCapped_RecoveryAfterUnregister— afterUnregisterPhonebrings the count belowmax, the nextRegisterPhoneCappedsucceeds again.TestRegisterPhoneCapped_PerServerIDIndependent— the cap is per-server-id; a second server-id can register up to its own cap independently.TestRegisterPhone_NoCapAfterDelegation— pins the wrapper invariant (RegisterPhone⇔RegisterPhoneCappedwithmax=0).TestClientEndpoint_AtCap_4429— integration test confirming the 4429 close code and reason reach the wire when the cap is exceeded; the in-cap phones remain registered.TestRegistry_RaceFreedomextended to exerciseRegisterPhoneCappedunder-race.go test -race ./...clean;go vet ./...clean;go build ./cmd/pyrycode-relayclean.Architecture compliance
RegisterPhone+RegisterPhoneCapped) to avoid a 23-call-site mechanical edit on the existingRegisterPhonesignature.maxPhoneslives at the wiring site (mirrors the30*time.Secondgrace on the line above); not a package constant.errors.Isbranching follows the existingErrServerIDConflict/ErrNoServerpattern; stillborn-WSConn close direct on*websocket.Connfollows ADR-0005.StartPhoneForwarder/StartBinaryForwarder) or to the 4404 / 4409 close-code semantics.Coordination
WS close code
4429is not yet listed inprotocol-mobile.md's close-code table; per the architect's Open Questions §1 the relay change ships independently of a coordinated spec-side PR adding 4429 (phone clients tolerate unknown 44xx codes per RFC 6455). No interlock.🤖 Generated with Claude Code