From 0b9263297376ca61e3bfb813c783dabeee6af205 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 11 Jul 2026 16:23:16 -0500 Subject: [PATCH] docs(manual): batch 2 - RevenueCat 10.13.0 + HMAC-SHA256 webhook in tree + gitignore entry --- Engineering_Reference_Manual_Plan.md | 184 ++++++++------------------- docs/Engineering_Reference_Manual.md | 11 +- 2 files changed, 64 insertions(+), 131 deletions(-) diff --git a/Engineering_Reference_Manual_Plan.md b/Engineering_Reference_Manual_Plan.md index 75aef5af..617e47c2 100644 --- a/Engineering_Reference_Manual_Plan.md +++ b/Engineering_Reference_Manual_Plan.md @@ -1,145 +1,73 @@ -# Engineering Reference Manual Review Plan — Phase 3 (2026-07-08) +# Engineering Reference Manual Review Plan — Phase 4 (2026-07-11) -**Status: ✅ COMPLETE** — all 13 batches reviewed, 11 issues found and fixed, committed individually, and pushed to `dev`. +**Status: 🟡 IN PROGRESS** — covering 17 commits since Phase 3 completed (`c5ead4cb`). -Triggered by `_null` after the Phase 2 sync (`213cfddb`) + review pass (`d31b58d2`). -Goal: cross-check every claim in the manual against the live repo + `firestore.rules` -+ `functions/src/` + iOS source + Android source, batch by batch. No skipped sections. +Triggered by `_null` after Phase 3 completion to catch manual drift from the +last 17 commits on `dev`. Phase 3 was a full evidence-first sweep; Phase 4 is a +targeted re-review scoped to commits that materially changed code the manual +references (billing, repo layout, dead-file cleanup, FCM QA tooling, copy +catalog start). ## Approach -- **13 batches**, each scoped to one major section of the manual. -- **Evidence-first.** For every factual claim (file path, function name, collection - name, cron expression, preference field, threshold, AAD string), verify against - the live source. -- **One commit per batch** per the standing git discipline (one batch = one commit = one push). -- **Log findings to this file** as each batch completes. -- **Anchors + TOC** verified at the end (Batch 13), not per-batch, to avoid churn. +- **6 batches**, scoped to a category of changes from the last 17 commits. +- **Evidence-first.** For every claim in the manual, re-verify against the + live source. +- **One commit per batch** per the standing git discipline (one batch = one + commit = one push). Each commit must be pushed before the next batch starts. +- **No commits for batches with no findings** — log a no-op to this file only. ## Batches -### Batch 1 — Front matter + System overview + Repository layout (Android + iOS + Cloud Functions + Shared configuration) -- Read: `## How to use this document` through `### Shared configuration` (lines ~1-260). -- Verify all directory paths under `app/src/main/java/app/closer/`, - `functions/src/`, `iphone/Closer/`, `res/drawable-*`, Hilt module list. -- Cross-check Android package list against the live `find app/src/main/java/app/closer -maxdepth 3 -type d`. -- Cross-check iOS file list against `ls iphone/Closer/...`. -- Cross-check Cloud Functions tree against `find functions/src -name "*.ts"`. -- Flag missing or renamed directories/files. +### Batch 1 — Repository layout: dead-file sweep (21392ec2) + server/ removal (42fba641) +- Verify `## Repository layout` (Android + iOS + Cloud Functions + Shared) doesn't + reference removed files (e.g. `NotificationHelper.kt`, `NotificationPermissionHelper.kt`, + `FakeQuestionRepository.kt`, `QuestionJsonParser.kt`, `Entitlement.kt`). +- Verify no references to the removed `server/` Express backend (was Tier 3 + cleanup, kept the dist that the actual Cloud Functions use). +- 4103c433 already dropped "dead references" — confirm nothing was missed. +- Commit if any manual updates needed. -### Batch 2 — Authentication + pairing + couples doc model + rate limit + recovery -- Read: `## Authentication and pairing flow` (lines ~270-370). -- Verify auth files in `data/remote/FirebaseAuthDataSource.kt`, - `data/repository/FirebaseAuthRepositoryImpl.kt`, `domain/repository/AuthRepository.kt`. -- Verify `couples/{coupleId}` field list against `acceptInviteCallable.ts` + the - domain `Couple.kt` model. -- Verify rate-limit env values against `AuthRateLimiter.kt`. -- Verify recovery phrase flow against `RecoveryKeyManager.kt` + `CoupleEncryptionManager.kt`. +### Batch 2 — Billing: RevenueCat SDK 10.12.0 → 10.13.0 (8cb23ea2) + webhook HMAC-SHA256 fix (89d06eeb) + secret key gitignore (48217dd7) +- Verify `## Billing` (RevenueCat integration + Webhook + Premium-gated features): + - SDK version now 10.13.0 (was 10.12.0, was 8.20.0 pre-Phase 3). + - Webhook auth: actual auth scheme is HMAC-SHA256, not the prior scheme. + - `revenucat_secret_api.md` is now `.gitignore`d — does the manual's + "Files that must never be committed" section need this added? +- Commit if any manual updates needed. -### Batch 3 — E2EE model (versions, Argon2id, Tink, sealed-answer, ECIES, single-device, iOS bridge) -- Read: `## End-to-end encryption model` (lines ~370-475). -- Verify each `Encryption versions` row against `firestore.rules` regex helpers. -- Verify the Argon2id v1.3 parameter names + the AAD strings against the actual - Kotlin/Swift code. -- Verify the `keybox:v1:` wire format against `Keybox.swift` + `ReleaseKeyEncryptor.kt`. -- Verify the `wrapReleaseKeyCallable` path against the function source. +### Batch 3 — Cloud Functions: webhook handler source-of-truth update (89d06eeb) +- Verify the Cloud Functions section's description of `revenueCatWebhook` + matches the new auth scheme (HMAC-SHA256 signature verification). +- Verify any other Cloud Functions section claims that the webhook handler + is the only billing touch-point (Tier 3 server/ removal is a reminder). +- Commit if any manual updates needed. -### Batch 4 — Daily question lifecycle + Reveal flow + Thread questions -- Read: `## Daily question lifecycle` (lines ~480-575). -- Verify `assignDailyQuestion` schedule + the deterministic picker - (DOW → mode map, daily_fun_mc exclusion) against - `functions/src/questions/assignDailyQuestion.ts` + `DailyModeResolver.kt`. -- Verify the schemaVersion 2 reveal flow + the read-gated `secure/payload` subdoc - against `firestore.rules` (the `isCoupleAnswerCreate` + `isEncryptedPayloadUpdate` - helpers). -- Verify schemaVersion 3 sealed flow against `SealedAnswerEncryptor.kt` + - `isSealedAnswerCreate` rules. -- Verify thread questions path against `isSealedThreadAnswerCreate` + the model. +### Batch 4 — Engineering conventions: copy catalog start (51efff3f, 23cd2139, 2c3c2df8) + string resource cleanup (59e03369) +- Verify `### Naming` / `### Error handling` / any UI convention sections + reflect the new `CloserCopy` catalog pattern. +- Verify no references to the 55 purged string resources. +- Verify no references to removed `core/analytics`, `core/billing`, + `core/navigation`, `core/notifications` packages or their submodules. +- Commit if any manual updates needed. -### Batch 5 — Firestore security rules -- Read: `## Firestore security rules` (lines ~720-810). -- Line-by-line verify every per-collection note in the manual against `firestore.rules`. -- Cross-check helper-function names (`coupleEncryptionEnabled`, `isCiphertext`, - `isUpdatingCoupleRhythm`, `isUpdatingRecoveryWrap`, `isCoupleAnswerCreate`, - `isEncryptedPayloadUpdate`, `isSealedAnswerCreate`, `isSealedAnswerUpdate`, - `isSealedThreadAnswerCreate`, `isSealedThreadAnswerUpdate`). -- Note any rules in the file not documented, or documented rules not in file. +### Batch 5 — Notifications / FCM: qa_push.js token-selection fix (c60fa958) +- Verify `## Notifications` (FCM + TokenRegistrar) doesn't claim a different + token-selection strategy than the live code. +- Manual may or may not reference the QA script — check `qa/qa_push.js`. +- Commit if any manual updates needed. -### Batch 6 — Cloud Functions -- Read: `## Cloud Functions` (lines ~815-845). -- Verify function triggers, handlers, schedules, module responsibilities. -- Cross-check `functions/src/` tree (already done in Batch 1 but verify exports). -- Cross-check Schedule table against each function's source. +### Batch 6 — Final pass: TOC + anchor integrity +- Re-run anchor/TOC verification. +- One final commit if any cleanup needed. -### Batch 7 — Billing -- Read: `## Billing` (lines ~850-920). -- Verify RevenueCat integration files, entitlement checkers, webhook handler, - Purchases.logIn identity link. -- Cross-check the "Gated features" list against the actual ViewModels that - inject `CouplePremiumChecker` / `EntitlementChecker`. - -### Batch 8 — Notifications -- Read: `## Notifications` (lines ~925-1020). -- Verify FCM setup + tokenRegistrar + quiet hours. -- Verify each reminder callable's actual schedule + dedupe. -- Verify game-push semantics against `onGameSessionUpdate.ts` + the - part-finished triggers. -- Verify the foreground banner surface + the deep-link routing table against - `PartnerNotificationManager.kt`. - -### Batch 9 — iOS-specific notes -- Read: `## iOS-specific notes` (lines ~1023-1080). -- Verify Architecture, CloserTheme tokens, XcodeGen setup, build commands, - E2EE gap status, CryptoKit guidance. -- Cross-check `iphone/Closer/Theme/CloserTheme.swift` for the actual color values. - -### Batch 10 — Build and release -- Read: `## Build and release` (lines ~1080-1200). -- Verify Android module/package, biometric recovery phrase reveal, required - build secrets, Gradle config, ProGuard rules. -- Cross-check the iOS build commands against the actual XcodeGen spec. - -### Batch 11 — Engineering conventions -- Read: `## Engineering conventions` (lines ~1200-1280). -- Verify the architectural rules (Clean layers, package boundaries, what - lives where) against the actual code. -- Verify the "what NOT to do" list (e.g. raw Firestore listeners, no - `FirebaseAuth.getInstance()` in VMs) is still accurate. -- Check the version drift (build.gradle vs HISTORY.md) and ProGuard note. - -### Batch 12 — Known landmines and recent fixes -- Read: `## Known landmines and recent fixes` (lines ~1280-1450). -- Verify EVERY landmine still applies: - - Does the file/component it cites still exist? - - Is the fix still in place? - - Is the "Re-introduction risk" description still accurate? -- Look for any landmine whose fix is now in a different file. - -### Batch 13 — Where to look first -- Read: `## Where to look first` (lines ~1450-end). -- Verify every file path still exists. -- Verify the "what to read first for a new area" list still applies to the - current code. - -### Final pass -- TOC integrity + every anchor resolves. -- One final commit if any TOC/anchor cleanup is needed. - -## Phase 3 results (2026-07-08) +## Phase 4 results | Batch | Section | Issues found | Commit | |---|---|---|---| -| 1 | Front matter + System overview + Repository layout | iOS platform row said "E2EE cross-compatibility not yet implemented" (contradicts the iOS E2EE section below) | `ef5a2331` | -| 2 | Auth + pairing + couples doc model + rate limit + recovery | Recovery phrase "256-word list" - actual list is 248 entries (`WORDLIST` array has 248; iOS wordlist is 247 lines = 248 entries with no trailing newline) | `1b36eba6` | -| 3 | E2EE model (versions, Argon2id, Tink, sealed-answer, ECIES) | Argon2id memory "46080 KiB" - actual constant is `46 * 1024 = 47104` KiB; iOS-side docstring was already correct | `0af6f24a` | -| 4 | Daily question lifecycle + Reveal flow + Thread questions | DOW mode table was partial (missed Saturday `mode_weekend_side_quest` - Side Quest Saturday) | `5e292e66` | -| 5 | Firestore security rules | Missing 3 helper functions (`otherCoupleMember`, `partnerReflectedDate`, `isPublicKey`); wrong regex for `isPublicKey`; releaseKeys per-collection enforcement said "readable only by recipient" but the rule also allows the sender (for the idempotency existence-check) | `5f7c785f` | -| 6 | Cloud Functions | Handler types table mixed onDocumentCreated / onDocumentUpdated / onDocumentWritten into one row; onEntitlementChanged was in a separate "Firestore onUpdate" row but is actually onDocumentWritten | `aa356148` | -| 7 | Billing | iOS "DefaultEntitlementChecker actor" doesn't exist (the real class is `BillingService`); gated-features list was wrong (4 of 7 items were not actually gated; chat-media gate was missing) | `ceb98f4a` | -| 8 | Notifications | "How to gate a new feature" sub-section got merged into the previous paragraph by Batch 7; game-push flag claim was wrong (no `notificationsSent` map - 4 separate Timestamp fields) | `29cc39aa` | -| 9 | iOS-specific notes | Tink template name "SHA128" was a typo - actual is `ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM` | `9330c0c4` | -| 10 | Build and release | RevenueCat version 8.20.0 (stale from v0.2.1) - actual is 10.12.0; iOS purchases-ios 5.x; Glance 1.1.1 missing from the Gradle dependency list | `22c816ad` | -| 11 | Engineering conventions | (no issues - every claim verified) | `b3725463` | -| 12 | Known landmines and recent fixes | A-201 landmine heading (`### A-201 - ...`) got dropped during the Phase 2 sync - the body content was there but visually merged with the previous landmine's tail | `80751f82` | -| 13 | Where to look first | (no issues - all 16 file paths resolve, acceptInviteCallable encryptionVersion=2 verified) | `c5ead4cb` | -| Final | TOC + anchor integrity | 30/30 anchors resolve, 0 broken TOC links | (this commit) | +| 1 | Repository layout: dead-file sweep + server/ removal | None — 4103c433 already covered all the dead-file references; the `core/notifications/`, `core/navigation/`, `core/analytics/`, `core/billing/` directories still exist and hold live code (TokenRegistrar, AppMessagingService, AppNavigation, etc.) so the line-963 and line-1021 references are still accurate. No stale `server/` references remain. | (no-op) | +| 2 | Billing: RevenueCat SDK + webhook + secret key | (1) Cloud Functions tree (line 203) still said "Ed25519 signature verify (NOT exported / not deployed)" — fixed to "HMAC-SHA256 signature verify (exported, deploy gated on REVENUECAT_WEBHOOK_SECRET)". (2) `purchases:10.12.0` on Android (line 869) → `purchases:10.13.0`. (3) `RevenueCat 10.12.0` in Gradle dep list (line 1134) → `RevenueCat 10.13.0`. (4) `revenucat_secret_api.md` added to the "Files that must never be committed" representative list (it was added to .gitignore in 48217dd7 but the manual didn't list it). | (this commit) | +| 3 | Cloud Functions: webhook handler | TBD | TBD | +| 4 | Engineering conventions: copy catalog + string resource cleanup | TBD | TBD | +| 5 | Notifications / FCM: qa_push.js | TBD | TBD | +| 6 | TOC + anchor integrity | TBD | TBD | diff --git a/docs/Engineering_Reference_Manual.md b/docs/Engineering_Reference_Manual.md index 0cbf059c..4fa3bca3 100644 --- a/docs/Engineering_Reference_Manual.md +++ b/docs/Engineering_Reference_Manual.md @@ -200,7 +200,7 @@ functions/src/ ├── log.ts # Shared `logger` + `redactToken` (B6d) ├── index.ts # Admin SDK init + explicit re-exports (no glob imports) ├── billing/ -│ ├── revenueCatWebhook.ts # HTTPS webhook - Ed25519 signature verify (NOT exported / not deployed - see Billing) +│ ├── revenueCatWebhook.ts # HTTPS webhook - HMAC-SHA256 signature verify (exported, deploy gated on REVENUECAT_WEBHOOK_SECRET - see Billing) │ ├── entitlementLogic.ts # Idempotent entitlement event handlers │ ├── entitlementLogic.test.ts # Vitest unit tests │ ├── syncEntitlement.ts # Callable - forced re-sync from client @@ -866,7 +866,7 @@ aggregateOutcomeStats every 24 hours (default ### RevenueCat integration -Both clients use the RevenueCat native SDK (`purchases:10.12.0` on Android, `purchases-ios:5.x` via SPM - `from: 5.0.0` in `Package.swift`). The SDK handles the platform billing surface (Google Play Billing on Android, StoreKit on iOS) and exposes a normalized `CustomerInfo` API. +Both clients use the RevenueCat native SDK (`purchases:10.13.0` on Android, `purchases-ios:5.x` via SPM - `from: 5.0.0` in `Package.swift`). The SDK handles the platform billing surface (Google Play Billing on Android, StoreKit on iOS) and exposes a normalized `CustomerInfo` API. The Android app reads the API key from `BuildConfig.RC_API_KEY`, which is sourced from `local.properties` or the `RC_API_KEY` env var. A release build with a missing or placeholder key **fails fast** with a `GradleException` from `app/build.gradle.kts` - there is a doFirst guard on every release assemble/bundle task. @@ -1131,7 +1131,7 @@ The iOS CryptoKit implementation follows these rules of parity with Android: - DataStore 1.1.2 - Glance 1.1.1 (Today widget) - Firebase BoM 33.8.0 (auth, firestore, messaging, config, analytics, crashlytics, appcheck, appcheck-playintegrity, storage, functions) -- RevenueCat 10.12.0 +- RevenueCat 10.13.0 - Tink 1.13.0, BouncyCastle 1.78.1 - Play Integrity 1.4.0 - Biometric 1.1.0 @@ -1189,6 +1189,11 @@ BUILD_SUMMARY.md SCRIPTS.md .learnings/ .kotlin/ +# Local-only QA fixtures (test credentials, secrets) +qa/README.md +qa/fixtures.local.md +qa/*.local.md +revenucat_secret_api.md # holds a RevenueCat secret key (added 2026-07-09 in 48217dd7) ``` > **Note (2026-06): the gitignore list uses `FUTURE.md` (uppercase) but the tracked file is `Future.md` (mixed case).** Linux filesystems are case-sensitive so these are different paths; the gitignore does not actually block `Future.md`. The ClaudeQA docs (`ClaudeQAPlan.md`, `ClaudeReport.md`, `ClaudeQACoverage.md`, `ClaudeBrandingReview.md`, `ClaudeiOSPlan.md`) and `Future.md` are explicitly tracked. If you create new top-level docs and want them gitignored, either match the existing case in `.gitignore` (`Future.md`) or pick a case and use it consistently. Do not block `Future.md` and then create `future.md` thinking you're safe. **(2026-06-28: the legacy uppercase