Repository navigation
Conversation
…L cutoff `redeem_registration_delegation` called `ensure_session` before the `mcp_register_v2` call, and `set_grant_expiration`/`set_permissions` after it, so a session reaped (15 minutes after its connect began) or evicted at capacity while the call was in flight was silently re-created with a fresh key that II never registered. A code minted for it led to a session that could not sign anything. #234 avoided that by refusing every code past the 10-minute CONNECT_TTL, at the cost of throwing away registrations that started in time and had succeeded. `imcp2-core` now looks the session up instead of creating it, and records the outcome through `record_registration`, which requires the session to still hold the exact key II registered. Otherwise the redemption fails with `SIGN_IN_DROPPED_MSG` and nothing is re-created. With that, the hosted mint step no longer refuses a connect past its TTL: the TTL bounds when a redemption may start (the gate, and the claim under the lock), and one that started in time gets its code. That matters because II keeps one MCP session per user; a successful registration has already replaced the user's previous one, so refusing the code left them with neither. `reserve_code` still refuses, and discards the granted session, when the connect itself is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VP2vTJm9ipTPD18xW9HyVG
|
The red Update: Generated by Claude Code |
`resolve_app_identity_skips_probe_for_known_apps` resolved oisy.com and expected `DerivationSource::Known`. oisy.com now publishes its own /.well-known/ii-derivation-origin (`https://oisy.com`), and the app's own declaration deliberately wins over the registry, so it resolves as `Declared` and the test failed on main and on every PR. The test also pinned the over-cap Layer 5 regression on oisy.com serving its SPA shell there, which it no longer does. - The live test now checks three registry apps that still declare nothing (nns.internetcomputer.org, multidex.ai, app.icpswap.com). Each must resolve to its registry derivation origin, unprobed, either via the registry (`Known`) or via its own agreeing declaration (`Declared`), and at least one must still go through the registry. One app adopting the declaration no longer breaks the test. - The over-cap regression gets an offline test, `an_over_cap_layer5_body_reads_as_no_declaration`: a loopback listener serves an oversized page, and `fetch_well_known` must read it as `Absent` under `Overflow::NotTheDocument` and `Unreachable` under `Overflow::Unknown`. - `Overflow::NotTheDocument`'s doc no longer says OISY serves that page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VP2vTJm9ipTPD18xW9HyVG
There was a problem hiding this comment.
🟡 Changes recommended
Missing or re-keyed sessions can cause unlogged redemption failures after registration succeeds and the prior MCP session has been replaced, leaving operators without a diagnostic.
1 open issue
What changed in this PR
This follow-up to #234 replaces the blanket sign-in completion cutoff with session/key validation, preventing unusable sessions without rejecting valid late completions.
Changes:
- Reject missing or re-keyed sessions without recreating them, and record grants and permissions atomically.
- Allow hosted redemptions started within the TTL to finish and receive authorization codes.
- Add regression tests for session loss, key replacement, late completion, and vanished connects.
| File | Description |
|---|---|
src/auth.rs |
Removes the mint-time TTL cutoff and updates redemption tests and comments. |
crates/imcp2-core/src/identities.rs |
Validates session keys before recording registration, adds a user-facing failure message, and tests session-loss handling. |
Review effort: Max
main now carries #237's fix for the stale oisy.com discover test, so the copy of #238's version this branch carried is dropped in favour of main's discover.rs, and #200's auth.rs changes merge cleanly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VP2vTJm9ipTPD18xW9HyVG
When `record_registration` refuses because the connect's session is gone, or was re-created with another key, while `mcp_register_v2` ran, II has already registered the key and replaced the user's previous MCP session. The user only saw the callback page's message; nothing reached the logs. Warn with the instance, session id, and which of the two happened, after the session lock is released. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VP2vTJm9ipTPD18xW9HyVG

Summary
Follow-up to #234. #234 refused a code to any sign-in that finished after the 10-minute
CONNECT_TTL. This PR replaces that blanket cutoff: a redemption now fails only when its session is actually gone.The underlying bug (in
imcp2-core):redeem_registration_delegationcalledensure_sessionbefore themcp_register_v2call, andset_grant_expiration/set_permissionsafter it.Why #234's cutoff was too broad: II keeps one MCP session per user, and a successful
mcp_register_v2replaces the previous one (see II'smcp_register_v2_is_multi_use_but_sessions_replace). So refusing the code for a registration that started in time and succeeded left a reconnecting user with neither their old session nor the new one.What changes:
imcp2-coreno longer re-creates the session on the redeem path. If the session is gone, or no longer holds the key II registered, the redemption fails cleanly withSIGN_IN_DROPPED_MSGand logs a warning.Related issues
Follow-up to #234.
Changes
imcp2-core:identities.rsredeem_registration_delegationlooks the connect's session up instead of callingensure_session. If the session is missing, it fails with the newpub const SIGN_IN_DROPPED_MSGbefore any network call. The message is written for users, because both callback pages show it verbatim.record_registrationrecords the grant's expiration and access level in one step under the lock.session_der) that II just registered.SIGN_IN_DROPPED_MSG.warn!with the instance, the session id, and whether the session was gone or re-created with another key. It logs after releasing the lock. II has already replaced the user's previous session at this point, so operators need the trace (suggested by the Copilot review).set_grant_expiration/set_permissionskeep their behavior. Their docs now say redemption doesn't use them.record_grant(the not-live → live transition andlast_seen) andaccess_level(permissions parsing).log_session_opened.PENDING_CONNECT_TTL_NS: the doc now says what the 15-minute reap can cut short, and that it fails cleanly.src/auth.rsreserve_codeno longer refuses a connect pastCONNECT_TTL. It refuses only when the connect itself is gone (evicted at the cap, or forgotten pastEXPIRED_CONNECT_RETENTION), and in that case still discards the granted session.claim_redemptionkeeps its TTL recheck. Its comment now explains that the TTL bounds when a redemption may start.imcp2-localuses the same redeem path. Its connect creates the session first (registration_pubkey_b64), so the change is transparent there.The branch merges
main(7cdc218), which brings in #200 and #237. That merge keptmain'sdiscover.rs, so this PR's diff againstmainis limited toidentities.rsandauth.rs.Testing
record_registration_records_on_the_session_it_registered(new)record_registration_refuses_a_session_gone_mid_call(new)record_registration_refuses_a_session_re_created_with_another_key(new)redeem_refuses_a_connect_whose_session_is_gone(new)ensure_session.a_redemption_started_in_time_gets_its_code_even_past_the_ttl(replacesno_code_is_minted_for_a_connect_past_its_ttl)a_redemption_whose_connect_is_gone_gets_no_code(new)redeem_guards_missing_key_and_empty_chain(updated)cargo build --locked --all-targetscargo test --locked --workspace --all-targets: all pass on the merge withmain(imcp2lib 91, bin 8, routers 11;imcp2-core220;imcp2-local21).cargo fmt --all/cargo clippy --workspace --all-targets(tree is warning-free).--all-features, which includes the e2e code, also compiles. The End-to-end (PocketIC) CI job, which runs the real II handshake, passed onf8bcdd3.npm test --prefix monitoring/mcp-status(n/a, dashboard untouched)Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01VP2vTJm9ipTPD18xW9HyVG