feat(dev/tunnel): surface platform rejections + auto-refresh access token#32
Merged
Conversation
…oken
Two related resilience fixes for `mirrorstack dev --tunnel` after running
the flow end-to-end against the slug-aware platform:
1. await_register_ack ignored rpc.err — the 10s timeout was firing while
the platform's rejection (module_dev_mode_off, module_not_yours) sat
unread on the wire. Now correlates rpc.err frames by the register
id, decodes the ErrorPayload (incl. the new optional slug field from
api-platform #165), and returns a typed RegisterError variant.
2. Caller in dev/mod.rs maps RegisterError → user-actionable output:
- ModuleDevModeOff: prints a warning, builds the dev-console URL
(localhost:3001 for dev hosts, https://apps.<host> for api.<host>
production, deep-linked to /dev/module/<slug>/dev when the slug
is carried in the payload, falling back to /dev otherwise), and
opens it on Enter.
- ModuleNotYours: explicit "this module isn't owned by you" message
with the `mirrorstack module list` cross-check hint.
- Rejected: generic "register rejected (<code>): <message>" so
future error codes surface without a CLI change.
3. Silent access-token refresh — tunnel_token's 401 path now calls
POST /v1/auth/sessions/refresh with the stored refresh token (30-day
TTL), persists the rotated pair to credentials.json, and retries the
mint once. Without this, the 15-minute access TTL killed long-running
`mirrorstack dev` sessions even though the user's session was still
valid for weeks. The refresh layer is scoped to the tunnel-token call
site for now; can generalize as more long-running calls land.
CLI tests stay 64-passing — these paths are integration-shaped and the
existing test surface doesn't mock the WS frame plane.
Co-Authored-By: Sheng Kun Chang <nothingchang@mirrorstack.ai>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gisterResult alias Three follow-ons from the simplify review on this PR (api-platform side was reviewed and came back clean, no fixes needed): - Add From<serde_json::Error> for RegisterError so the `?` operator on serde_json calls converts to RegisterError directly. tunnel.rs::open drops eight `.map_err(RegisterError::from)?` adornments — the From impl + the existing From<anyhow::Error> impl do the work. - Drop the double-wrap on serde_json::to_string (was `.map_err(|e| RegisterError::from(anyhow!(e)))?`, now just `?`). - Hoist `type RegisterResult<T> = std::result::Result<T, RegisterError>` so the two handshake functions read with the same Result shape as the rest of the file. - await_register_ack's "ignore non-corr_id frames" comment was inside the match against frame_type, but the corr_id check happens above it — move the comment to the function level where the policy lives. No behavior change. cargo test stays 64/64. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's `cargo fmt --check` rejected the localhost/loopback match in
dev_console_url because the multi-arm `Some("...") | Some("...") | ...`
form was wrapped onto a long single line under rustfmt's normalize.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
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
Two resilience fixes for `mirrorstack dev --tunnel` caught running the flow end-to-end against the slug-aware platform (api-platform #165):
`await_register_ack` ignored `rpc.err`. The 10s timeout was firing while the platform's rejection (`module_dev_mode_off`, `module_not_yours`) sat unread on the wire. Now correlates `rpc.err` frames by the register id, decodes the ErrorPayload (incl. the new `slug` field from #165), and returns a typed `RegisterError` variant.
Caller maps `RegisterError` → user-actionable output.
): ` so future error codes surface without a CLI change.Silent access-token refresh. `tunnel_token`'s 401 path now calls `POST /v1/auth/sessions/refresh` with the stored refresh token (30-day TTL), persists the rotated pair to `credentials.json`, and retries the mint once. Without this, the 15-min access TTL killed long-running `mirrorstack dev` sessions even though the user's session was still valid for weeks.
Pairs with
Test plan
🤖 Generated with Claude Code