fix: complete referral and integration authorization recovery
What changed and why
Follow-up to merged #21381; refs #21378. Referral activation and integration connections could remain stuck after browser return, adopt late results for a retired account/page, or reuse spent MCP authorization.
- Share bounded, cancellable, serialized connection reads/waits across native integrations, tasks, marketplace setup and MCP. Publish only server-confirmed state. Save task defaults in choice order after a confirmed grant.
- Fence registration, browser launch, status, detail and referral continuations by account, request and route ownership. Cache MCP registrations by account/URL and generation; non-OAuth registrations reach their explicit Enable action.
- Obtain fresh single-use state/PKCE authorization for MCP retries, on the existing registration and OAuth client. Keep Connect busy for its bounded attempt. Rotate only the verifier, without erasing a concurrent callback's credentials.
- Commit callback credentials/tools/approval transactionally only while owner and verifier still match. A superseded/deleted/changed-owner callback returns 409 without writing or enabling.
- Preserve typed pending/failed/not-required marketplace verdicts, pCloud's fresh launch grants and signed query bytes. Never send Omi's Firebase bearer to vendor setup probes.
- Bound referral token lookup, HTTP and decoding with one deadline; ambiguous token failures retain manual retry. Preserve entitlement authority and destination. External settings/home/referral signup use the system browser.
Product invariants affected
INV-UI-1, INV-UI-3, INV-PERF-1: retain shared feedback/navigation and localization primitives; serial polling owns its lifecycle and never treats browser launch as authorization. No recording, conversation-storage or entitlement-policy changes.
Merge scope versus auth rollout
The recovery code preserves Firebase's existing auth-domain selection, helper proxy and Google/Apple callback configuration. The diff does not change firebase.ts, firebaseAuthDomain.ts, firebaseAuthProxy.ts or the generated-server wiring. #21827 can therefore merge independently of #21355 once its own code/CI checks pass. This corrects the broader coupled merge hold in older readiness snapshots; it does not waive a changed provider boundary or claim the original incident is resolved.
The changed MCP boundary is verified with the production routes, actual local HTTP PKCE exchange/tool discovery, isolated real Redis and Firestore emulator transactions. That provider is synthetic; Firebase login is injected. This is relevant local integration evidence for the changed retry/commit protocol, not proof of Google/Apple console settings.
Release sequencing still matters: deploy the new owner-only POST /v1/apps/{app_id}/mcp/authorize on the target backend before releasing clients that use fresh retries. This is a release requirement, not a claim that merging deploys it. No manual deployment or provider-setting change is part of this task.
#21355 separately changes Firebase's helper origin. Its web client and generated Bun proxy must ship together after callback verification. The complete #21378 outcome also requires that fix and released-build/customer follow-up. Keep #21378 open; do not claim its unspecified remaining Try Again screen is fixed.
Exact remaining auth checks for #21355 / complete incident resolution
- Confirm the project's Google OAuth web client permits https://app.omi.me/__/auth/handler, and Apple's Service ID permits domain app.omi.me and that exact return URL. Preserve existing Firebase Hosting callbacks. Prior review records authorized-domain coverage through the parent omi.me; do not assume an extra subdomain entry is necessary.
- Run the #21355 client and generated Bun proxy together in an appropriate configured integration environment. Verify the same-origin Firebase helper is served (not an external redirect), with SAMEORIGIN framing for the helper while app pages retain their normal protection. The live /login currently returns 200 but /__/auth/iframe returns 404; this means the proposed helper fix is not live, not that its source is proven defective.
- Complete Google and Apple redirect sign-in in Safari/browser or simulator, desktop Google popup sign-in, cancellation/retry and referral return. Confirm session publication precedes navigation, repeated task query parameters and the intended destination survive, and referral application is not duplicated. Use authorized QA accounts/data. A physical phone is not an extra merge gate.
Existing signed-in provider consoles do not expose the needed settings to this account; no credential, role, permission or provider configuration was changed. Owner confirmation or provider-connected QA evidence is needed for those remaining checks, not another blanket CTO code approval.
How it was verified
Final verified head: 918e2b6345fb29da2e725efa74e45249c69119d1 (test repairs a6c4d7b355 and 918e2b6345; production auth source unchanged from ed6cd0f5cf). All applicable current-commit CI has passed, including Formatting, all six backend shards and their aggregate, backend guardrails, web, Flutter, desktop Swift contracts and all eight CodeQL analyses. The scan results are empty, and the fixture finding is fixed/automatically resolved rather than dismissed. No applicable failing or pending check is being waived.
- Reproduced the shard-2 collection failure through backend/test.sh: the old visibility test replaced Google/Firestore/packages in sys.modules and omitted transactional. Migrated it to the actual apps module with a fixture-scoped database seam. It checks in-place identity preservation and propagation of a missing-document error without deletion/recreation. Three-file isolated run passes 22 tests; the same 22 also pass together in one process.
- Full local CI-equivalent shard 6/2 passed: 383 files, 8,519 tests, with the unchanged runner selection, per-file isolation and timing guard. No assertions, timing budgets or baselines were weakened.
- The earlier Formatting job failed before formatter execution: setup-uv timed out downloading its manifest, leaving no pinned Black. Canonical local checks pass all 6 changed backend Python files with Black 26.5.1; Flutter 3.44.5/Dart 3.12.2 formatting passes all 33 surviving non-generated changed Dart files with zero edits. Final CI passes the normal setup/check path; no formatting gate is skipped.
- make preflight passed all 63 selected checks, including web typecheck, all 887 Vitest tests and 6 Bun smoke tests, with four workers and unchanged assertions/timeouts. New follow-up after that run is documentation only; normal pre-push reruns the selected gates.
- Prior unchanged-source auth evidence remains valid: 13 reauthorization/commit tests plus 6 state/security tests; 808 checks including all seven Firestore query/index guard files; real HTTP/Redis/Firestore emulator recovery integration; focused Python 3.11 typecheck (0 errors, existing warnings), async-blocker gate, generated-client/OpenAPI checks, targeted Flutter security/setup/recovery tests and 23 seeded mobile journeys. Simulator and synthetic-provider evidence is not physical-device or live-provider proof.
- Final review found CodeQL alert 504 in the synthetic loopback OAuth fixture. Its callback was already checked for exact equality, but the response header still copied the request value. The fixture now emits only its fixed callback URL and explicitly rejects other URLs. All 4 integration tests pass through backend/test.sh with the local HTTP provider, Redis and Firestore emulator, including unregistered/CRLF redirects, encoded state and the unchanged production-route recovery scenario. No production HTTP handler or provider configuration changed.
- A managed CodeQL JavaScript scan on c846bbb failed connecting to api.github.com (10-second connect timeout); GitHub rejected both job and workflow rerun. A normal empty refresh commit 223d4c6 reran standard checks with the same source tree, and all security scans then completed successfully. The fixture repair's own fresh scan now also passes on 918e2b6, with zero results across all eight languages/categories. No alert was dismissed or scan setting weakened.
- The migrated test is registered in the single-process subset; two entries naming already-removed files were retired. A broader optional mixed-process isolation audit remains inconclusive/red on another file's duplicate Prometheus registration and a load-sensitive VAD timing guard. It is not represented as passed. The focused 22-test shared-process run and full isolated shard pass; the ordinary CI guardrail lane is unchanged.
- Five earlier automated review findings have verified fixes and were resolved. No formal self-approval, force push or hook bypass.
Tests
Relevant coverage: connection_read_test.dart, connection_wait_test.dart, integration_oauth_test.dart, android_hanging_flows_test.dart, app_setup_status_test.dart, app_detail_session_test.dart, app_install_flow_test.dart, mcp_connection_recovery_test.dart, test_mcp_oauth_reauthorize.py, test_mcp_oauth_callback_state.py, test_mcp_oauth_recovery_emulator.py, test_app_visibility_missing_doc_guard.py, referrals.test.ts and LoginClient referral/token tests.
UI evidence
Existing real LoginClient rendering with synthetic auth and local failed/successful claims; not production Google sign-in or customer verification. Unchanged client behavior/layout reuses this evidence and the recorded simulator/local journeys.


Failure class (fixes)
Failure-Class: FC-continuation-resumed-after-unmount
Account/request/mounted fences surround awaited client dependencies; shared ConnectionRead/ConnectionWait own deadlines and cancellation. Controlled late-response regressions exercise the production owners. The additional durable MCP callback guard compares owner/verifier transactionally before committing.
Line-Count-Exception: backend/routers/apps.py | 2696 -> 2750 | The MCP re-authorize route shares the registration, state-store and PKCE helpers of the existing add, callback and refresh MCP routes in this router; splitting only it out would separate one OAuth flow across files.