feat(BL-P2-083): interim session-expired guard for rescoped tokens#88
Merged
Conversation
BL-P2-052 Part A (full background auto-refresh) is scoped beyond P0/P1,
but the 55-minute deterministic auth failure that follows every rescoped
session ships with P0/P1 today. Without this guard, the failure surfaces
as `ApiError::AuthFailed("refresh_token refused: active scope ... differs
from initial scope ...")` — indistinguishable from real credential
rejection. Operators have prompted users to "check credentials" when the
actual fix is `:switch-context` to reauth, and we have no telemetry on
how often this happens.
This change splits the path off cleanly:
- `ApiError::SessionExpired { project }` — distinct from AuthFailed so
callers (worker funnel + future BL-P2-052 Part A) can branch.
- `KeystoneAuthAdapter::get_token` — pre-empts the existing 1-minute
fast path for rescoped sessions only (active scope != initial scope):
if the cached token is within 5 minutes of expiry (or missing for an
active rescoped key), short-circuit with `SessionExpired` and
`tracing::warn!(project, expires_at, "session_expired")`. Initial-scope
tokens fall through to refresh_token unchanged — those CAN be
self-refreshed via do_authenticate.
- `worker::api_error` — routes `ApiError::SessionExpired` to a dedicated
`AppEvent::SessionExpired { project }` variant instead of the generic
`AppEvent::ApiError` funnel.
- `App::generate_toast` — Korean reauth message: "<project> 세션이 만료
되었습니다. :switch-context로 재전환하거나 앱을 다시 시작하세요."
Telemetry: tracing::warn at the adapter (structured fields for grep) +
tracing::warn at the worker (operation tag for find-by-operation
workflows). Both fire on the same event so analysts can correlate.
TDD: 5 new tests covering the full path:
- test_get_token_returns_session_expired_for_expired_rescoped_token
- test_get_token_returns_session_expired_within_5min_margin_for_rescoped
- test_get_token_initial_scope_near_expiry_does_not_return_session_expired
- test_api_error_routes_session_expired_to_dedicated_event
- test_generate_toast_for_session_expired_uses_reauth_message
cargo test = 1509 passed (1504 → +5). clippy clean. fmt clean.
Out of scope (BL-P2-083 spec): background auto-refresh for rescoped
sessions (BL-P2-052 Part A). The interim guard ships in front so BL-P2-052
arrives with telemetry-justified rollout instead of guessing how often
55-minute failures occur.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex review P2: the original toast suggested either `:switch-context` or app restart, but `KeystoneRescopeAdapter::rescope` authenticates the rescope request with the (now-expired) current token, so for a fully-expired session `:switch-context` fails with a generic `RescopeRejected` — the user follows the prompt and hits a second cryptic error. The interim toast now recommends only the path that always works (app restart). The "still rescopeable within the 5-min margin" UX nuance is tracked as BL-P2-096 follow-up. Also registers BL-P2-096 in devflow-docs/backlog.md so the smart recovery work doesn't get lost. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local rustfmt was lenient with chain wrap + format!() single-line; CI's stricter rustfmt flagged two diffs. No semantic change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 / 요약
What changed
TDD trail
Telemetry rationale
`tracing::warn!(project, expires_at, "session_expired")` at the adapter (structured) + `tracing::warn!(operation, project, "session_expired surfaced to UI")` at the worker (op-tagged). Both fire on the same event so log analysts can correlate adapter detection to UI surface. This is the data BL-P2-052 Part A rollout needs ("how often does this fire in production").
Verification
Out of scope
Test plan
Refs: 2026-04-21 Codex adversarial-review M1. Parent: BL-P2-052 Part A.
🤖 Generated with Claude Code