Intent: when someone revokes a harness on Settings › Tokens, the harness cannot come back. Today deleteToken (src/core/tokens.ts:179) and revokeToken delete the access token, and oauth_refresh.token_id is ON DELETE SET NULL (migration 20261102170000-oauth-refresh.ts), so the unspent refresh token still mints a fresh access token through POST /oauth/token.
Done:
[ ] Deleting a token through deleteToken or revokeToken also deletes every oauth_refresh row of that token's family (the same work endFamily in src/core/oauth.ts does); one helper, called from both.
[ ] An access token that merely expires (the sweep at src/core/tokens.ts:174) does NOT end its family: refreshing after expiry keeps working.
[ ] Tests (test/oauth-refresh.test.ts): mint through the code exchange, delete the access token as its owner, then the refresh grant answers invalid_grant; an expired access token still refreshes.
Files: src/core/tokens.ts, src/core/oauth.ts, test/oauth-refresh.test.ts.
Risk: import direction — keep src/core/tokens.ts free of an import cycle with oauth.ts (a plain SQL delete by family is enough).
Intent: when someone revokes a harness on Settings › Tokens, the harness cannot come back. Today
deleteToken(src/core/tokens.ts:179) andrevokeTokendelete the access token, andoauth_refresh.token_idisON DELETE SET NULL(migration 20261102170000-oauth-refresh.ts), so the unspent refresh token still mints a fresh access token through POST /oauth/token.Done:
deleteTokenorrevokeTokenalso deletes everyoauth_refreshrow of that token's family (the same workendFamilyin src/core/oauth.ts does); one helper, called from both.invalid_grant; an expired access token still refreshes.Files: src/core/tokens.ts, src/core/oauth.ts, test/oauth-refresh.test.ts. Risk: import direction — keep src/core/tokens.ts free of an import cycle with oauth.ts (a plain SQL delete by family is enough).