Repository navigation
OAUTH-001A1H — Refuse stale Strava refresh persistence #606
Description
Parent: #88
Officer impact: After a future protected Functions release, a slow Strava refresh response cannot persist after the stored access-token, refresh-token, or expiry tuple changes, disappears, or becomes malformed. There is no current visible page change and no officer action.
Officer documentation: None — this is a server-only secret-persistence boundary. Existing SOURCE ONLY / NOT LIVE Strava failure guidance remains accurate.
Deployment evidence: Source, tests, merge, website, runmprc.com, Firebase, Strava provider, production data, and live behavior are all unverified at issue creation.
Problem
The trusted refresh path reads an expired access/refresh/expiry tuple, calls Strava, and then performs an unconditional merge write. A concurrent refresh, reconnect, disconnect, or other trusted rotation can change or remove the secret while that provider request is pending. The older response may then overwrite or resurrect stale credentials. Strava requires applications to persist and use the most recently returned refresh token because an older rotated token is invalidated immediately.
Invariant and allowed transitions
After a provider response, persist it only when a fresh Firestore transaction reread exists, is structurally valid, and its exact access token, refresh token, and expiry tuple still equals the pre-provider snapshot.
- Unchanged valid tuple: make one transactional merge write and preserve current downstream behavior.
- Changed, missing, or malformed tuple: make no write, return the fixed private refresh failure, and make no downstream bearer call.
- Transaction conflict retry: reread and recheck; refuse if the tuple changed.
- Reference, read, set, commit, or post-commit acknowledgement failure: preserve the existing fixed redacted refresh failure.
- Never log or expose raw tokens or provider response details.
Atomic scope
- functions/strava.js: equality and compare-before-write transaction only in refresh persistence.
- functions/strava.test.js: a trustworthy named RED case; transaction-set mock support; positive, negative, conflict-retry, and persistence-failure coverage; mechanical updates to existing refresh persistence assertions required by direct-set-to-transaction movement.
- SECURITY.md: one narrow current-truth row/hunk for OAUTH-001A1H and RISK-024.
Acceptance
- Record a focused test-only RED result against the exact base.
- Prove stale and deleted tuples are preserved/refused with no downstream request.
- Prove retry after a Firestore conflict refuses changed state.
- Prove an unchanged tuple persists once and continues normally.
- Preserve fixed failure behavior for malformed state and transaction/write/ack failures.
- Run focused and full relevant checks and complete independent review.
- Record exact merge and deployment-surface evidence before closure.
Migration impact: None. The existing exact token tuple is the compare-and-set fence.
Exclusions / stop conditions
Do not expand into provider-call serialization or leasing, identical-tuple replacement detection, lost-ack reconciliation beyond current fixed behavior, response-body byte limits, client disconnect behavior, provider configuration or calls, Firebase deployment, real tokens or production data, or live verification. Do not close #88 from this child alone.
Issue creation is not a claim.
Activity
daliu commentedon Aug 2, 2026
CLAIMED by /root at 2026-08-02T05:59:37Z (GitHub createdAt).
Exact base: a62c147
Branch: codex/issue-606-strava-refresh-cas
Parent: #88
Owned outcome:
Owned paths/hunks:
Excluded: provider-call serialization/leases, identical-tuple replacement detection, response byte limits, client disconnect behavior, OAuth email verification, provider settings/calls, Firebase deploy, real tokens/data, live behavior, and closure of #88.
Officer impact: After a future protected Functions release, a slow Strava refresh response cannot persist after the stored access-token, refresh-token, or expiry tuple changes, disappears, or becomes malformed. No page or officer procedure changes.
Officer documentation: None — server-only secret persistence boundary; existing SOURCE ONLY / NOT LIVE guidance remains accurate.
Deployment evidence: No source change, test result, merge, website publish, runmprc.com verification, Firebase deploy, provider configuration, production-data action, or live behavior is claimed by this marker.
daliu commentedon Aug 2, 2026
RED evidence on exact base a62c147:
daliu commentedon Aug 2, 2026
Implementation candidate is ready in PR #607.
Exact reviewed head: 49e81b5
Exact base: a62c147
Tree: 6b3a0db31e87ca7e247c92835f4ccd1a575e0bbf
Patch SHA-256: bc2c962c56329bef7aa5664542c71b34c05d19a03f439c769910a25c26dcfef5
PR: #607
Outcome: a refresh response is merge-written only when a Firestore transaction rereads a valid access-token, refresh-token, and expiry tuple exactly equal to the pre-provider snapshot. Changed, deleted, malformed, and conflict-retried-to-changed state fails with the fixed private refresh result before downstream bearer calls.
Trustworthy RED: 1 failed / 7,567 total on the exact base; test-only diff SHA-256 35821aa35db663cca52871ad6476c41e33055d7b73f9d285ac572138dd40ae61.
Final local Node 20 evidence: A1H 8/8; Strava 711/711; full Functions 7,514 pass plus 64 intended skips; frontend 951/951; Rules 418/418; commerce emulator 63/63; SPA 11/11; static workflow/safety 93/93; Functions and frontend lint, TypeScript, diagnostic production build, and diff checks passed. Three independent reviews are GO after one documentation precision correction.
Officer impact: After a future protected Functions release, a slow refresh response cannot persist after the stored three-field tuple changes, disappears, or becomes malformed.
Officer documentation: None — server-only persistence boundary; existing SOURCE ONLY / NOT LIVE failure guidance remains accurate.
Deployment evidence: Source/tests and pushed PR only. Code is not merged. The website and runmprc.com are not published or verified. Firebase is not deployed. Strava is not configured or contacted. No production data or live behavior is verified.
daliu commentedon Aug 2, 2026
COMPLETE / CLOSED / CLAIM RELEASED.
Released claim: /root at exact merged main 24ec0e5.
Reviewed head: 49e81b5
Base: a62c147
Tree: 6b3a0db31e87ca7e247c92835f4ccd1a575e0bbf
Patch SHA-256: bc2c962c56329bef7aa5664542c71b34c05d19a03f439c769910a25c26dcfef5
PR: #607
PR CI: 30735558743, all five jobs passed.
Exact-main CI: 30735667378, all five jobs passed.
Remote topic branch: deleted.
Outcome: the source now transactionally rereads and validates the exact stored access-token, refresh-token, and expiry tuple before persisting a Strava refresh response. Changed, deleted, malformed, or conflict-retried-to-changed state stops with the fixed private refresh result and makes no downstream bearer call.
Officer impact: After a future protected Functions release, a slow refresh response cannot persist after the stored three-field tuple changes, disappears, or becomes malformed. No page or officer action changed.
Officer documentation: None — server-only secret persistence boundary; existing SOURCE ONLY / NOT LIVE guidance remains accurate.
Deployment evidence by surface:
Residuals remain under #88: provider refresh calls are not serialized; identical-tuple replacement is not detected; provider/local and post-commit acknowledgement reconciliation, IAM, encryption, audit, protected deployment, and live proof remain open. This child does not close #88.