Skip to content

Gate on grant_types_supported skips refresh instead of terminating - #58

Merged
germ-mark merged 2 commits into
mainfrom
llm/refresh-gate-skip-not-terminate
Jul 29, 2026
Merged

Gate on grant_types_supported skips refresh instead of terminating#58
germ-mark merged 2 commits into
mainfrom
llm/refresh-gate-skip-not-terminate

Conversation

@germ-mark

Copy link
Copy Markdown
Contributor

Suggestion into #52 — targets feat/refresh-token-gate.

The defect, proven against the real adopter: return nil from the refresh closure is the terminate-session signal (AtprotoOAuth: nil.expired → logged out), so a server whose metadata lists grant types without refresh_token logged the user out on the first refresh attempt instead of the session living until access-token expiry. Live mastodon.social advertises ["authorization_code","client_credentials"] — with #47's Mastodon demo in this same stack, this would have bitten immediately. It also inverted #53: that scenario is preserved on #53 alone but was terminated once #52 merged over it.

The fix: throw a dedicated OAuth.Errors.refreshNotSupported instead. Throwing preserves the previous state under the adopter contract, which is the "skip" the changeset already describes — the changelog text becomes accurate rather than aspirational. A dedicated case (rather than reusing notSupported, which after #49 is the PAR signal) lets callers distinguish "this server can never refresh — plan around access-token expiry" from a transient failure.

Kept: the != false leniency for omitted grant_types_supported. Strict RFC 8414 reading says absent defaults to no refresh, but servers routinely omit the field while supporting refresh, and one that truly doesn't answers unsupported_grant_type — which is preserved under #53's classification too. The deviation is now reasoned in a comment rather than implicit.

Branch shape: feat/refresh-token-gate + origin/feat/token-revocation + origin/main merged in (both clean), so this builds and tests against the true post-stack state — 0.3.0, the mocks, and #53's classification together. Once #51 merges and #52 retargets to main, the merge commits' contents drop out of the diff.

Test notes

15 tests, 5 suites, all green — including #53's four refresh-error tests unmodified, and three new: gated server throws refreshNotSupported with zero token-endpoint requests recorded, omitted grant_types_supported still refreshes, advertised support refreshes.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b826f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@germ-network/oauth4swift Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@germ-mark
germ-mark force-pushed the feat/refresh-token-gate branch 2 times, most recently from fe9d7a4 to 0f47abc Compare July 29, 2026 08:27
Base automatically changed from feat/refresh-token-gate to main July 29, 2026 08:27
germ-mark and others added 2 commits July 29, 2026 01:28
return nil from the refresh closure is the terminate-session signal - the
adopter moves to .expired and logs the user out. A server whose metadata lists
grant types without refresh_token (mastodon.social advertises
authorization_code and client_credentials) killed the session on the first
refresh attempt instead of living until access-token expiry. Throwing preserves
the previous state, which is the skip the changeset already describes.

Thrown as a dedicated refreshNotSupported so callers can tell "this server can
never refresh - plan around access-token expiry" apart from a transient
failure; notSupported stays the PAR-endpoint signal.

The nil-leniency stands, now with the RFC 8414 deviation reasoned in a comment:
absent grant_types_supported strictly defaults to no refresh, but servers
routinely omit it while supporting refresh, and one that truly does not answers
unsupported_grant_type - which also preserves the session.

Tests cover the gated server (throws, zero token requests), omitted metadata
(request sent), and advertised support (request sent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refreshNotSupported is public API and deserves its own changelog entry,
including the exhaustive-switch caveat. And the test session does not mirror
the production adopter - production swallows the throw and returns the old
token - it exercises the nil-vs-throw contract, so say that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@germ-mark
germ-mark force-pushed the llm/refresh-gate-skip-not-terminate branch from fa45722 to 8b826f4 Compare July 29, 2026 08:30
@germ-mark
germ-mark merged commit 14b1921 into main Jul 29, 2026
8 checks passed
@germ-mark
germ-mark deleted the llm/refresh-gate-skip-not-terminate branch July 29, 2026 08:35
@github-actions github-actions Bot mentioned this pull request Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant