diff --git a/Engineering_Reference_Manual_Plan.md b/Engineering_Reference_Manual_Plan.md index f7f26f48..1a69f223 100644 --- a/Engineering_Reference_Manual_Plan.md +++ b/Engineering_Reference_Manual_Plan.md @@ -78,8 +78,8 @@ collection name, and architectural fact. Never assume. | 1 | ✅ done | `core/feature/` note inaccurate (dir doesn't exist); `data/questions/` listed `QuestionDao` but it's in `data/local/` | Corrected `core/feature/` note; moved `QuestionDao` to `data/local/`; kept `QuestionJsonParser` in `data/questions/`; updated older-description note. | | 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. | -| 4 | in progress | | | -| 5 | todo | | | +| 4 | ✅ done | Handler table missing functions (`syncEntitlement`, `sendDailyQuestionProactiveReminder`, `sendReengagementReminder`, `sendChallengeDayReminders`, `unlockDueMemoryCapsules`, `sendGentleReminderCallable`, `onEntitlementChanged`, `onAnswerRevealed`, `onCoupleLeave`, `leaveCoupleCallable`, `submitOutcomeCallable`, `scheduledOutcomesReminder`, `onGamePartFinished`, `notifyOnDateMatch`); still listed removed `health` endpoint and old name `createDateMatchOnMutualLove`; webhook section said 200-ack-before-process (now process-before-ack); `scheduledOutcomesReminder` timezone wrong | Removed `health`; added all current functions and corrected `notifyOnDateMatch`; updated module responsibilities; corrected webhook to process-before-ack with 500-on-failure; fixed schedule timezones (only `assignDailyQuestion`, `sendDailyQuestionProactiveReminder`, `sendReengagementReminder` use `America/Chicago`). | +| 5 | in progress | | | | 6 | todo | | | | 7 | todo | | | | 8 | todo | | | diff --git a/docs/Engineering_Reference_Manual.md b/docs/Engineering_Reference_Manual.md index 6dbb3249..05090e64 100644 --- a/docs/Engineering_Reference_Manual.md +++ b/docs/Engineering_Reference_Manual.md @@ -715,13 +715,13 @@ isUpdatingCoupleRhythm() only streakCount/lastAnsweredAt ### Per-collection enforcement -**`users/{uid}`** — owner can read/create/update their own doc but `hasPremium` is server-only. `entitlements/`, `notification_queue/`, and `outcomes/` are server-only writes; `entitlements/` is also readable by the user's current couple partner (couple-shared premium; see [Couple-shared premium](#couple-shared-premium)), and `notification_queue/` is readable by the owner for the in-app activity feed (the owner can flip the `read` flag). `fcmTokens/` and `devices/` are owner-writable. The `devices/` public key is readable by the user's current couple partner only (to wrap release keys) — restricting it prevents speculative pre-encryption by non-partners. +**`users/{uid}`** — owner can read/create/update their own doc. Root-level premium state is **not** stored here; the client-writable `plan` field is informational only, and authoritative premium state lives in the server-only `entitlements/premium` subdoc. `entitlements/`, `notification_queue/`, and `outcomes/` are server-only writes; `entitlements/` is also readable by the user's current couple partner (couple-shared premium; see [Couple-shared premium](#couple-shared-premium)), and `notification_queue/` is readable by the owner for the in-app activity feed (the owner can flip the `read` flag). `fcmTokens/` and `devices/` are owner-writable; the `devices/` public key is readable by the user's current couple partner only (to wrap release keys). Quiet-hours fields (`quietHoursEnabled`, `quietHoursStartMinutes`, `quietHoursEndMinutes`, `timezone`) and notification preferences (`notifPartnerAnswered`, `notifChatMessage`) are client-writable so Cloud Functions can honor them server-side. **`date_ideas/`** — read-only for any signed-in user; writes are admin-only. **`invites/{code}`** — reads are restricted to the inviter (`request.auth.uid == resource.data.inviterUserId`). All writes are denied for clients. This is the core defense against 6-character code enumeration: even legitimate create/update/delete must go through a Cloud Function, which can enforce rate limits, uniqueness, and key-material checks. -**`couples/{coupleId}`** — only the two members may read. Writes are denied for clients entirely; the rules restrict the shape of the doc and let Cloud Functions do all updates. Field-level immutability helpers (`isUpdatingCoupleRhythm`, `isUpdatingRecoveryWrap`, `isStartingEncryptionMigration`, `isCompletingOwnEncryptionMigration`) define what each update path is allowed to touch. +**`couples/{coupleId}`** — only the two members may read. Direct client `create` is possible only if the document exactly matches the server-created shape (all required fields including `encryptionVersion == 2` and valid E2EE key material); in practice `acceptInviteCallable` creates the doc. `update` is limited to the rhythm/recovery-wrap field sets via `isUpdatingCoupleRhythm()` or `isUpdatingRecoveryWrap()`; everything else is server-only. `delete` is denied for clients. Field-level immutability helpers (`isUpdatingCoupleRhythm`, `isUpdatingRecoveryWrap`) define what each update path may touch. There is no encryption-migration path in the current rules. **`couples/{coupleId}/daily_question/...`** — server-only writes. Daily-question assignment and answer-related subcollections are tightly constrained.