Skip to content

OAUTH-001B9 — Refuse stale Strava disconnect cleanup - #609

Merged
daliu merged 1 commit into
mainfrom
codex/issue-608-strava-disconnect-cas
Aug 2, 2026
Merged

daliu merged 1 commit into
mainfrom
codex/issue-608-strava-disconnect-cas

Conversation

@daliu

@daliu daliu commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Outcome

A disconnect that pauses for Strava can no longer erase a newer reconnect. The Function records the original secret document version, then transactionally deletes the local secret/connection pair only when the secret is still that version or is already absent.

Officer impact: None now. This is protected Function source only and is not deployed. If a stale cleanup is refused after a future release, members keep the newer connection and receive the existing fixed unknown-result instruction.

Officer documentation: None — docs/officers/EVENTS_SHOP_MEMBERS.md already tells a no-terminal backup officer to preserve the connected view, refresh, stop, and escalate after every unconfirmed disconnect result. This change adds no officer action, screen, wording, permission, data-flow, account-ownership, or deployment-topology change, so no Mermaid update is required.

Deployment evidence: Source changed and local tests passed. This PR has not been merged or published. The website and runmprc.com are not affected; Firebase was not deployed; no Strava provider setting or production data was changed; no real token, account, revocation, or live behavior was used or verified.

Invariant and transitions

  • Provider revocation remains outside the Firestore transaction and is attempted at most once per invocation.
  • The cleanup transaction rereads the secret before either local delete.
  • An unchanged secret version may delete the secret and connection together.
  • An already-absent secret may clean up the connection and converge.
  • A newer secret version, including a byte-identical replacement, or an absent-to-present transition is preserved and returns the existing fixed unknown result.
  • A transaction conflict rereads current state without repeating provider work.
  • App Check and Auth admission remain unchanged and precede secret access.

No schema or migration is required.

Trustworthy RED

On exact base 24ec0e5789c0ff26cba49371d298aea5c0cdf3ce, the test-only paused-revoke regression committed replacement pair B while revoke A was pending. The old blind batch cleanup then deleted B and fulfilled { ok: true }; the assertion required fixed rejection and byte-preserving B.

Test-only binary diff SHA-256: 1d8477963d87cc555147f7ac961ddf29ffcf696846714b14be254cd41177fe03

Reviewed implementation binary diff SHA-256: 645fd89eb771011c634ee37677471a7b1fe59ba7b46d04415fbac77e1d6f005c

Reviewed head: 0baf701e6dad16f5d5a6f842001c2c3216921d88

Verification

Node.js 20 and Java 21 were used where applicable.

  • Focused OAUTH-001B9/OAUTH-001B7: 15/15 passed
  • Full Strava suite: 721/721 passed
  • Full Functions suite: 7,524 passed, 64 intentionally skipped
  • Functions lint: passed
  • Frontend Jest: 951/951 passed
  • Frontend lint baseline: unchanged 113 known errors and 6 known warnings across 118 files
  • Static workflow/safety suites: 93/93 passed
  • Firestore Rules emulator: 418/418 passed
  • Commerce transaction emulator: 63/63 passed
  • SPA navigation: 11/11 passed
  • Diagnostic production build: passed
  • git diff --check: passed
  • Dependency audits, no fixes applied: root 59 total/4 production; Functions 8 total/8 production; no critical advisories

A first diagnostic Functions run accidentally inherited Node 22 and reproduced 12 known Stripe projection runtime incompatibility failures. The exact isolated suite passed 25/25 under required Node 20, and the exact full Functions suite then passed under Node 20.

Self-review

  • Authorization/App Check: unchanged; no new boundary or bypass
  • Input/state bounds: no new input; cleanup is fenced by immutable Firestore update time
  • Privacy/logging: fixed event names only; no token, UID, path, version, provider response, or raw error is logged
  • Idempotency/retry: transaction conflicts retry local reads only; provider work is not replayed; lost cleanup acknowledgement converges safely
  • Atomicity: both local records delete in one transaction or neither is committed
  • Compatibility/migration: backward-compatible source change; no data migration

Independent runtime/security, test/fake, and backup-officer reviews all returned GO with no edits.

Residual risk and exclusions

Provider calls remain unserialized. Provider/local and lost-acknowledgement reconciliation remain open. A trusted connection-only writer that does not also version the secret remains outside this fence. Malformed secrets can still be removed locally without provider revocation. Client confirmation admission, protected Firebase deployment, runtime IAM/encryption, outside-provider state, and live behavior are not proven here.

Closes #608.
Parent #88 remains open.

@netlify

netlify Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for luminous-fox-7c393f ready!

Name Link
🔨 Latest commit 0baf701
🔍 Latest deploy log https://app.netlify.com/projects/luminous-fox-7c393f/deploys/6a6eea20c6fa380008f2371a
😎 Deploy Preview https://deploy-preview-609--luminous-fox-7c393f.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@daliu

daliu commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

SELF-REVIEW GO on exact remote head 0baf701e6dad16f5d5a6f842001c2c3216921d88 (base 24ec0e5789c0ff26cba49371d298aea5c0cdf3ce, binary diff SHA-256 645fd89eb771011c634ee37677471a7b1fe59ba7b46d04415fbac77e1d6f005c).

No issue remains in the reviewed scope:

  • the immutable secret update time plus transactional reread fences newer and byte-identical reconnects;
  • provider revocation stays outside transaction retries;
  • unchanged/absent state converges atomically and stale state returns the fixed unknown result;
  • App Check/Auth ordering is unchanged;
  • fixed logs expose no token, UID, path, version, provider body, or raw failure;
  • no schema, migration, officer task, guide, or Mermaid change is required;
  • provider serialization/reconciliation, connection-only writers, client admission, deployment, IAM/encryption, and live behavior remain explicitly outside this claim.

Independent exact-head runtime/security, test/fake, and backup-officer reviews each returned GO. PR CI run 30736839293 passed all five jobs. No Firebase deploy, Strava provider action, production data operation, or live verification occurred.

@daliu
daliu merged commit 9d8bfaa into main Aug 2, 2026
9 checks passed
@daliu
daliu deleted the codex/issue-608-strava-disconnect-cas branch August 2, 2026 06:59
@daliu

daliu commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

POST-MERGE GO: merged as exact release SHA 9d8bfaae0a4f9e573053b4035954e933ae626875; merge tree matches reviewed head, exact-main CI 30736937726 passed all five jobs, production Netlify attempt 6a6eeae42c8dae0008c35f63 remained unpublished, GitHub deployments are empty, and the live #473 marker is unchanged. Firebase/Strava/provider/data/live behavior was not deployed or verified. COMPLETE / CLOSED / CLAIM RELEASED.

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.

OAUTH-001B9 — Refuse stale Strava disconnect cleanup

1 participant