# Pathrule Pattern: Real-Time UI with WebSockets (1.0.0)
# ::pathrule:package:websocket-realtime-ui

### [RULE] Authenticate and authorize the live subscription  (path: /server/realtime)
<!-- scope: folder | priority: high | strict -->

A long-lived connection can outlast tokens, membership, and resource permissions. Accepting a socket once and trusting every later subscribe command creates access that cannot be revoked correctly.

- Authenticate the handshake or first protocol message through a reviewed token mechanism and validate browser origin where the deployment relies on it.
- Authorize each channel, document, tenant, or topic subscription against current server state; do not trust a client-supplied tenant or room name.
- Define token expiry and permission-change behavior, including reauthentication, subscription removal, or connection close with a stable reason.
- Rate-limit connection, authentication, subscription, and message actions separately so reconnect storms and channel scanning cannot consume the service.

See /src/realtime for the adjacent decision or procedure that completes this constraint.

---

### [RULE] Validate every message against a versioned envelope  (path: /server/realtime)
<!-- scope: folder | priority: high | strict -->

WebSocket frames are untrusted input without HTTP route and body-parser boundaries. A generic event name plus arbitrary object recreates remote procedure calls without a reviewable contract.

- Define an envelope with protocol version, type, message or operation identity, and a payload schema per allowed message.
- Reject unknown types, oversized frames, invalid nesting, and unsupported versions before dispatching to domain behavior.
- Separate commands, events, acknowledgements, and errors so clients do not confuse a requested action with an authoritative state transition.
- Log protocol type, stable identity, size, and outcome without recording secret or personal payload fields.

See /tests/realtime for the adjacent decision or procedure that completes this constraint.

---

### [RULE] Bound outbound and inbound connection buffers  (path: /server/realtime)
<!-- scope: folder | priority: high | strict -->

A connection that receives faster than it can transmit or process accumulates memory. Backpressure must become a protocol decision before one slow browser harms every session in the process.

- Track buffered bytes and queued messages per connection and define a maximum with a close, snapshot, or coalescing policy.
- Coalesce replaceable state updates such as progress or presence instead of retaining every intermediate value.
- Keep durable domain events outside the socket process so dropping an ephemeral connection does not lose the authoritative transition.
- Apply inbound rate and concurrency limits before expensive validation or database work, and return a protocol-level throttle or close reason clients understand.

See /src/realtime for the adjacent decision or procedure that completes this constraint.

---

### [MEMORY] Reconnect resumes from an authoritative cursor  (path: /src/realtime)

A new socket is a new stream. Reconnecting without a cursor either loses events created during the gap or forces the client to append a full replay over state it already has.

- Give authoritative events a stable stream and sequence identity and make client application idempotent by event ID.
- Persist or retain the last applied cursor at the state owner, not merely in the socket instance that was lost.
- On reconnect, request events after the cursor and apply them in order; buffer out-of-order arrivals only within a strict bound.
- When the cursor is too old or the server cannot prove continuity, replace local state from a versioned snapshot and resume from its cursor.

See /server/realtime for the rule or workflow that puts this decision into practice.

---

### [MEMORY] Presence is leased, approximate state  (path: /src/realtime)

Disconnect events are not guaranteed when a laptop sleeps, a network changes, or a process dies. Presence must expire without a clean close and tolerate brief disagreement between viewers.

- Issue a connection or session presence identity distinct from the user account and refresh it through bounded heartbeats.
- Expire presence after a documented lease and communicate that online indicators are approximate rather than instantaneous truth.
- Aggregate multiple tabs and devices intentionally so one closing tab does not mark a still-connected user offline.
- Keep authorization and durable workflow state in the authoritative backend; presence can inform UI but cannot grant access or prove attendance.

See /server/realtime for the rule or workflow that puts this decision into practice.

---

### [SKILL] test-websocket-resynchronization  (path: /)

---
name: test-websocket-resynchronization
description: Test a WebSocket protocol or real-time UI change before release.
---

# Test Websocket Resynchronization

Run this procedure when the affected surface changes, before the result is promoted to production. Record evidence for every step instead of accepting a plausible-looking result.

- [ ] Connect with valid, expired, unauthorized, wrong-origin, malformed, oversized, and unsupported-version inputs and confirm rejection occurs before subscription or domain work.
- [ ] Produce events while the client is disconnected, then reconnect from a current, old, invalid, and missing cursor; verify replay or snapshot behavior.
- [ ] Duplicate and reorder events around optimistic UI changes and prove the final state follows authoritative sequence without duplicate rows or effects.
- [ ] Throttle the client and server consumer to grow buffers; verify coalescing, limits, close reason, and recovery without process-level memory growth.
- [ ] Restart and deploy socket servers while several tabs and devices are connected, then revoke access and confirm every connection eventually loses the subscription.

## Exit criteria

The change is complete only when the expected behavior, failure behavior, and rollback path have all been exercised with representative data. Preserve the evidence with the change so the next operator can repeat the same checks.
