Copilot review rounds 8 and 9.
- A cached metadata document with no `issuer` was letting every issuer-bound
token go to its single revocation endpoint. Absence establishes nothing, so
that is now a mismatch: an issuer-bound grant must match, and only a legacy
unkeyed grant — which nothing binds to a different authorization server —
proceeds without the comparison. This was a credential-disclosure risk, not
just a correctness one.
- The ctx-less grant read was skipped whenever it carried an issuer stamp, on
the assumption that slot had already been enumerated. If `listIssuers` fails
there is no enumerated slot behind the stamp, so the one readable grant went
unrevoked while `clear` deleted it. Both reads now share one issuer+token
key, and the stamped issuer is retained rather than discarded.
- `AuthTab`'s repeat guard read `clearState`, which is not updated until React
re-renders, so two `s` events in one input turn both started a clear against
the same store entry. It is a ref now, set before the call.
- A rejected clear left `ownClearRef` set — `handleClearOAuth` never reached
its `oauthRevision` bump — so the next unrelated revision was swallowed and
the failure banner stranded. Cleared on that path.
- The web `clearServerOAuthAndDisconnect` had the staleness the TUI path was
already fixed for: `isActive`/`inspectorClient` are captured before a
five-second await, so a switch during it disconnected the session the user
had just moved to and ran the session-wide cleanup against it. Revalidated
against `sessionRef` after each await.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh
Signed-off-by: cliffhall <cliff@futurescale.com>