Repository navigation
Gate token publication on persistence; OAuth timeout; scoped mint (parity with wefunder-ruby review) - #2
Conversation
…oped mint in the install guide Parity with wefunder-ruby#1 / #3 / #4 (Rivet's pre-release review), applied to this SDK: - TokenManager / AsyncTokenManager persisted the rotated set AFTER publishing it, so a concurrent caller could use an undurable token and a failed save left the process working in memory but unable to reconnect after a restart. Now: persist first; on failure the set stays in `pending_tokens`, no request is made with it, the consumed refresh token is never reused, the next call retries the save (no second rotation), `mark_persisted()` publishes a set saved out of band, and WefunderTokenPersistenceError (with .tokens) is raised. Readers re-check under the lock. Barrier + failure tests for both managers, plus a transport-level test that the undurable token is never sent. - `timeout` reached the API client but not the OAuth round-trips (httpx's 5s default applied to refresh / re-mint / the initial client-credentials mint). It now does; tested via request.extensions["timeout"]. - The install-target guide now mints the existing install's token with the same read-only scope it requested on create (asserted in the example test). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rivetbot
left a comment
There was a problem hiding this comment.
Reviewed head 7005bd6. The store-only failure path now retains and retries the rotated set, and the scoped installation mint is correct. Three issues remain.
-
[P2] Keep the rotated set pending until the persistence callback succeeds. Both TokenManager and AsyncTokenManager clear pending and publish current before invoking/awaiting on_token_refresh; only store.save is caught. With callback-only database persistence, a blocked callback permits another API call to use the fresh token before it is durable. If the callback raises, later calls continue using that token without retrying the callback. This leaves the original failure in a supported persistence path. Wait for all configured persistence hooks before publication, retain the rotated set on either failure, and add sync/async callback barrier, failure, and retry coverage.
-
[P2] Propagate timeout through refresh, re-mint, and code exchange. The sync constructor and async constructor do not pass timeout into their token managers or recovery re-mint closures. A client configured with timeout=11 (or None) uses that setting for the initial mint/API request but falls back to 30 for automatic refresh or re-mint after a 401. The new timeout parameter is also omitted when exchange_code forwards the request, including the async path. Carry it through those calls and add sync/async transport-recorder tests for expired-token refresh, 401 re-mint, and code exchange. The existing new timeout test only covers initial mint plus a successful API call.
-
[P2] Bind mark_persisted to the exact token set that was saved. The sync acknowledgment and async acknowledgment blindly publish whichever set is currently pending. Using public wf.token_manager: refresh rotates r1→r2 and save fails; caller A retains error.tokens and starts an out-of-band save. Another caller retries persistence successfully via get_access_token, then calls refresh again, rotating r2→r3 whose save fails. A’s delayed save of r2 completes and calls mark_persisted, which publishes pending r3 even though only r2 was saved. The next request can use undurable r3 and restart recovers consumed r2. Accept an expected token set/generation and compare it under the lock. Add a deterministic public-API test proving a stale r2 acknowledgment leaves r3 pending and unused.
Validation was read-only source and existing CI-log inspection; the described public call sequences were not executed locally. CI run 36964292828 passed Python 3.11/3.12/3.13 jobs and generated-code sync; the Python 3.12 log records 155 passing tests. The tested PR merge tree matches this head. Live sandbox e2e was skipped. No live API calls, credentials, local tests, or CI reruns were used.
…o the saved set; timeout everywhere Rivet's review of #2 (three P2s): 1. on_token_refresh is a persistence path, so it now runs BEFORE publication inside the same pending transition as store.save, for both managers; a raising/blocked callback keeps the set pending and surfaces WefunderTokenPersistenceError. Callback barrier/failure/retry tests, sync and async. 2. timeout now reaches the token managers and the re-mint closures in both constructors, and exchange_code / async_exchange_code forward it. Recorder tests cover expired-token refresh, the initial mint, and code exchange, sync and async. 3. mark_persisted(tokens) takes the exact set that was saved and is a no-op (False) for a stale acknowledgment, so an older r2 save can never publish an unsaved r3 — deterministic public-API test included. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Addressed all three points in 458b3ab: |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pyright matches protocol parameter names, so a user store written as `def save(self, s)` was rejected against `save(self, tokens)`. Make the protocol parameter positional-only so any name satisfies it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rivetbot
left a comment
There was a problem hiding this comment.
Re-reviewed head fbf7876. All three P2 findings from my prior review are resolved, with no remaining actionable findings.
Both sync and async managers now complete store/callback persistence before publishing, retain pending tokens on failure, and retry persistence. Timeout is forwarded through the previously missing manager, re-mint, and code-exchange calls. mark_persisted validates the saved token pair under the lock, so an old acknowledgment cannot publish a newer pending set.
CI run 37036496520 passed Python 3.11/3.12/3.13 and generated-code sync, with 162 passing tests. The tested PR merge tree matches this exact head. Live sandbox e2e was skipped.
Validation was source and existing CI-log review; no local tests or live API calls were run. The re-mint timeout fix was verified in source: the timeout test does not actually force a re-mint, so I am not claiming executed coverage of that recovery path.
Parity with the findings Rivet raised on the Ruby SDK (wefunder-ruby#1, #3, #4); the same defects existed here.
Token persistence (#1)
TokenManager/AsyncTokenManagerpublished the rotated set beforestore.save. Now:pending_tokens, no request is made with it, and the consumed refresh token is never reusedget_access_token()/refresh()retries the save (no second rotation);mark_persisted()publishes a set saved out of bandWefunderTokenPersistenceError(with.tokens) surfaces the failure; readers re-check under the lockTests: store sees the OLD token current while saving; barrier (reader blocked behind an in-flight save never sees the undurable token); failing store keeps the set pending and the next call retries;
mark_persisted; transport-level — the undurable token is never sent; async manager.Timeout (#3)
timeoutreached the API client but not the OAuth round-trips (httpx's 5 s default applied to refresh, re-mint, and the initial client-credentials mint). It now propagates everywhere; tested viarequest.extensions["timeout"].Scoped mint (#4)
The install-target guide mints the existing install's token with the same read-only scope it requested on create (asserted).
Verification
ruff + pyright clean; 155 tests passed (131 conformance + the new ones).
@rivetbot please review.
🤖 Generated with Claude Code