_poll_session reports 'revoked' when ServerKit Cloud refuses the poll
with the revoked reason, but the loop only honored 'stopped' and fell
through to re-probe the websocket: ws refused, poll revoked, ws
refused — forever, and the panel never wrote the revoked state. This
is the path a panel behind a close-code-stripping edge depends on, old
builds unconditionally and current ones whenever they are in limited
mode. The poll outcome now gets the same terminal handling as the
websocket one.
The production edge strips non-standard WebSocket close codes, so the
relay's 4009 never reached the panel: a revoked panel kept reconnecting
forever instead of stopping. ServerKit Cloud's relay now sends the
reason as a text frame before closing, and the panel honors it both
during the hello exchange and mid-session, ending the session as
revoked either way. The numeric code still serves direct connections,
and the long-poll fallback's HTTP refusal is unchanged.
ServerKit Cloud mints each signing key the first time it is used and
rotates keys on its own schedule, so a command can legitimately arrive
signed with a key the connection's cached JWKS predates. The panel used
to refuse it as untrusted; it now refetches the JWKS once on an unknown
kid and only refuses a key that is still unknown afterwards.
- app/services/connect_client.py: RelayClient background thread (WS hello
signed device_id|ts|nonce, 20s pings, jittered 1s->60s backoff with reset
after 2 stable minutes, >6 reconnects/10min flap log, close-code ->
state_reason mapping, terminal revoked, long-poll fallback with 15min WS
re-probe); connect-state.json runtime state merged into status();
disconnect() stops the client and removes the state file; run_doctor()
implements the connect check table
- app/services/connect_keys.py: strict load_keypair() for the transport
- app/__init__.py: start_client_if_paired() next to the linked-panel hook
- cli.py: connect status shows transport/last-seen/relay-instance; new
connect doctor command (exit 1 on hard-check failure)
- requirements.txt: websockets==15.0.1 (WS client; the SocketIO stack is
server-side only)
- tests/test_connect_relay.py: 21 unit tests for backoff, flap detection,
close-code mapping, hello signature, state file, loop transitions