Add JWT expiry warning and account-stream heartbeat/staleness detection (fixes #225)#236
Merged
Conversation
Fixes #225. Token lifetime was effectively the hard cap on maker run duration with no early warning, and the account stream could sit on a half-open TCP connection forever while reporting healthy. JWT expiry: - Decode the JWT `exp` claim (best-effort, no signature check) in Credentials. `is_expired`, `remaining_seconds`, and `expires_at_string` now honour the real venue cutoff and clamp the optimistic local metadata (notably env-var tokens tracked as ~1 year). - The live maker now monitors remaining token lifetime each loop (throttled to 60s) and emits a warning risk notification through the existing notifier at <2h, escalating to critical at <15m, once per severity band. No renewal endpoint exists in the codebase, so this ships the warning + documented gap rather than a fabricated refresh. Account-stream heartbeat/staleness: - Add a client-side ping (30s) plus a read-side idle timeout (90s) to the account stream, mirroring the market websocket heartbeat pattern. Any inbound frame resets the idle deadline; on a silent half-open connection the timeout marks AccountStreamHealth unhealthy and emits a Disconnected event, so the runtime freeze/reconnect path triggers. Tests: JWT exp parsing/threshold clamping, token-expiry severity bands, and an idle-connection staleness test driving a silent WS server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wjllance
force-pushed
the
fix-225-jwt-heartbeat
branch
from
July 13, 2026 08:25
a447021 to
966ce37
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #225. Two related durability gaps in the live maker:
Credentials::is_expired()/remaining_seconds()were computed purely from storedcreated_at + validity_secondsmetadata — env-var tokens are tracked as ~1 year, so the local view could be wildly optimistic versus a real 24h JWT.AccountStreamHealth::is_healthy()stayedtrueforever and the runtime never froze/reconnected.JWT expiry
Credentialsnow decodes the JWTexpclaim (best-effort, no signature verification — malformed/opaque tokens fall back to the old metadata).is_expired,remaining_seconds, andexpires_at_stringclamp to whichever expiry (JWT vs. metadata) comes first, so the venue's real cutoff wins.MakerNotifier: warning under 2h, escalating to critical under 15m, alerted once per severity band. The message points the operator atstandx auth login.Account-stream heartbeat/staleness
AccountStream, mirroring the marketwebsocket.rsheartbeat pattern. Any inbound frame (data/ping/pong) resets the idle deadline; a silent half-open connection trips the timeout, which callsmark_unhealthyand emitsAccountEvent::Disconnected. This feeds the existing runtime freeze → cleanup → reconnect path.Test plan
cargo fmt -- --check— cleancargo clippy --workspace --all-targets -- -D warnings— cleancargo test -p standx-sdk/-p standx-cli/-p standx-maker— all passexpdecoding (numeric/missing/non-numeric/opaque), and that a short JWTexpoverrides optimistic ~1yr metadata for bothis_expiredandremaining_seconds.token_expiry_levelseverity banding + ordering used for escalation.idle_connection_is_marked_unhealthy: drives a WS server that authenticates then goes silent; asserts the stream reports an "idle"Disconnectedand health flips unhealthy.Follow-up
standx auth loginpastes a JWT; there is no challenge/refresh flow in the SDK). Real background renewal (lead-time refresh + retry + failure alert) is therefore left as a follow-up pending venue auth API details. This PR ships the early-warning path so an operator can re-authenticate before the token lapses. Once a refresh endpoint is available, the per-cycle monitor is the natural hook to trigger it.Fixes #225
🤖 Generated with Claude Code