## Phase 2 (2026-07-08) — single comprehensive pass
Triggered by the 2026-07-08 Cloud Functions v2 deploy (B0–B6d, Node 22 bump, 5 per-game part-finished triggers) + the R30 games/UX/seed round + the `b99a8338` RevenueCat↔Firebase-Auth identity link. Single comprehensive pass rather than the original 9-batch plan because the changes were concentrated in three areas (Cloud Functions, Billing, Daily question) and the manual had drifted in known directions.
### What changed (Phase 2)
- **Cloud Functions** — replaced the stale functions tree with the v2 layout (added `options.ts`, `log.ts`, the `push.ts`/`quietHours.ts`/`idempotency.ts`/`pruneTokens.ts`/`time.ts` shared infra, `releaseKey/`, `backup/`, `dates/onDate*`, `couples/aggregateOutcomes`, `notifications/sendStreakReminder` + `sendThinkingOfYouCallable`). Replaced `onGamePartFinished` (single) with the four per-game part-finished triggers (`onThisOrThatPartFinished`, `onWheelPartFinished`, `onHowWellPartFinished`, `onDesireSyncPartFinished`). Added the v2-migration deploy-failure pattern landmine (`FUNCTIONS-V2-DEPLOY`). Webhook reliability section now states the webhook is **not deployed** (RevenueCat project not yet created).
- **Daily question lifecycle** — replaced the "picks a random active free question" description with the server-authoritative, mode-aware, deterministic picker that mirrors the client's `DailyModeResolver`; added a new sub-section `### Server-authoritative mode-aware deterministic selection` with the frozen DOW → mode map; noted the picker is FROZEN against `daily_fun_mc` for HowWell/Desire Sync pool hygiene.
- **Billing** — added the `Purchases.logIn(firebaseAuth.currentUser!!.uid)` identity-link flow (commit `b99a8338`) and the `BillingException` typed error mapping; added a "Currently NOT deployed" callout on the webhook section that points to the Webhook reliability deploy steps.
- **iOS section** — corrected the Repository layout iOS `Crypto/` block (it is no longer empty; ships the R24 E2EE code) and the "pairing from iOS currently fails" claim (it does not, for the schemaVersion 2 path; schemaVersion 3 is still infrastructure-gated per `IOS_E2EE_STATUS.md`).
- **TOC + anchors** — added the new sub-anchors (`server-authoritative-mode-aware-deterministic-selection`, `webhook-reliability`, `schedule`, `server-verified-entitlements`, `webhook`, `premium-gated-features-and-gate-pattern`); fixed three pre-existing broken anchors (`#foreground-game-alert-banner-r10`, `#ios--android-sealed-answer-bridge-wrapreleasekeycallable`, `#recovery-phrase-change-desync--...`).
- The Android Gradle config drift (`versionCode = 1`, `versionName = "0.1.0"` in `app/build.gradle.kts` while HISTORY.md describes v0.2.1) is already flagged in the manual ("build config has drifted from HISTORY; do not ship a release until they're reconciled"). Bumping the version is a release-readiness task, not a docs task.
- The iOS E2EE section (sub-section "iOS E2EE gap (status as of current batch)") was already accurate after the R24 work; left unchanged.
- The "Files that must never be committed" gitignore + the iOS CryptoKit guidance section were both already correct.
| 2 | ✅ done | No anonymous sign-in or account linking in code; Android uses legacy Google Sign-In SDK (idToken), not Credential Manager; `encryptionMigrationUsers` field does not exist | Removed anonymous auth and account-linking claims; corrected Google Sign-In description; removed `encryptionMigrationUsers` from couples model and added note that `encryptionVersion` is always `2`. |
| 3 | ✅ done | `/users/{uid}` model missing `sex`, `partnerId`, `plan`, `lastActiveAt`, notification prefs, quiet-hours, `fcmToken`; `hasPremium` is not a real root field (premium lives in `/entitlements/premium`); `/couples/{coupleId}` listed non-existent `encryptionMigrationUsers`; date plan fields wrong; date plan preference fields wrong; bucket list fields wrong; missing `/answers/{userId}/secure/{doc}` for schemaVersion 2 | Updated `/users/{uid}` to full allowlist; removed `hasPremium` root field and added note about `/entitlements/premium`; removed `encryptionMigrationUsers`; corrected date plan, preference, and bucket list fields; added `secure` subdoc to daily-question model. |
| 6 | ✅ done | Billing Webhook flow still said "ack 200 then process" (process-before-ack was fixed in Batch 4 but Billing section had stale duplicate); missing `CouplePremiumChecker`; Notifications said quiet-hours server-side suppression not implemented (it is, via `recipientInQuietHours`); Notifications said `notification_queue` reads denied for clients (Android reads it for Together feed); `onEntitlementChanged` handler table description said it mirrors `plan` (it notifies partner instead) | Updated Billing webhook flow; added `CouplePremiumChecker` note; corrected quiet-hours to server-side-implemented; corrected `notification_queue` read claim; fixed `onEntitlementChanged` description. |
| 7 | ✅ done | iOS E2EE gap paragraph said iOS create invite writes an invite with null E2EE fields (server now rejects empty data, so no invite is created); ProGuard section claimed Tink reflection paths are kept (they are not in current rules) | Updated iOS create-invite consequence to "rejected by server"; clarified ProGuard does not currently keep Tink. |
| 8 | ✅ done | Theme landmine entry said C-ART-EDGE-002 still open (R13 fixed it); missing the mandatory `theme-scan.sh` Pass C pre-check | Updated theme landmine to mark C-ART-EDGE-002 closed and document `scripts/theme-scan.sh` as mandatory pre-check. |