Commit graph

2 commits

Author SHA1 Message Date
Ullrich Schäfer
2b0717fe21
fix(planner): cap per-WS-frame + per-session Yjs doc size
Addresses planner audit #5 — a connected client could feed unlimited
waypoints / no-go / notes into the Yjs doc, growing the in-memory map
and the persisted \`sessions.yjs_state\` blob until OOM or DB blowout.

Two guards:

1. **Per-frame**: \`MAX_MESSAGE_BYTES = 256 KB\`. Anything bigger arrives
   from a buggy or hostile client — a single waypoint add is hundreds
   of bytes. Oversized frame → \`ws.close(1008)\` and drop the message.

2. **Per-doc**: \`MAX_DOC_BYTES = 5 MB\`. After a sync-message apply
   succeeds, recompute \`Y.encodeStateAsUpdate(doc).byteLength\`. If it
   crossed the cap, mark the session \"quarantined\": close every
   connected client (\`1008 policy violation\`), and short-circuit
   subsequent handleMessage calls so no further bytes can be applied
   and the debounced save can't write an oversized blob to Postgres.

The 5 MB limit is generous — a typical multi-day route's serialized
state is well under 100 KB. The cap exists to make abuse expensive,
not to constrain real use.

Exports \`MAX_MESSAGE_BYTES\`, \`MAX_DOC_BYTES\`, and \`docByteSize\`
for testing. Tests cover:
- constants are positive and ordered
- docByteSize is 0 for unknown sessions
- a realistic 500-waypoint route stays well under the cap
- ~6MB of garbage in a Y.Text trips the cap (smoke test for the guard)

Full repo: pnpm typecheck / lint / test all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:13:01 +02:00
Ullrich Schäfer
0553cf4a5e
Fix planner_active_sessions gauge drifting negative
The gauge was maintained by conditional inc on "this sessionId isn't
in the in-memory `docs` Map yet" and dec on "last client for the
sessionId left." Because `docs` caches entries indefinitely — even
after every client disconnects — a reconnect found the doc still
cached, skipped the inc, then decremented again on the next
disconnect. Every reconnect-after-last-disconnect leaked one
decrement. After a day of normal reload / wifi-blip / tab-hibernate
patterns the gauge hit -182.

Replace the transitions with a `set()` derived from the live `conns`
map: count distinct session ids with at least one open WebSocket.
Self-correcting, no drift possible.

Extract `countActiveSessions()` as a pure helper + unit tests
including a regression case that simulates repeated reconnect/
disconnect cycles on one session and asserts the count never goes
negative.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:37:58 +02:00