Commit Graph

37 Commits

Author SHA1 Message Date
null 5f7c785f5c docs(manual): batch 5 review — 3 missing rule helpers + releaseKeys sender-read fix
The Firestore security rules Helper functions table was missing 3
helpers that the rules file actually exports:
  - otherCoupleMember(coupleId, uid) - the partner of a couple
  - partnerReflectedDate(coupleId, dateId, uid) - has the partner
    already reflected on a date reflection?
  - isPublicKey(value) - matches the pub:v1:... wire format for the
    ECIES P-256 public key write to users/{uid}/devices/primary

The isPublicKey regex I added at first was wrong (I wrote
'pub:v1:[A-Za-z0-9_-]+={0,2}$' but the real one is
'pub:v1:[A-Za-z0-9_-]{40,}$' - 40-char minimum, no padding).
Corrected.

The releaseKeys per-collection enforcement said 'readable only by the
named recipient' but the rule actually allows the sender (answer
owner) to read their own releaseKeys doc as well - the rule comment
explains: writeReleaseKey does an idempotency existence-check get()
before writing, and without the sender-read allowance that get()
returned PERMISSION_DENIED and releaseOwnKey threw, breaking the
daily reveal. The keybox is ECIES-encrypted to the recipient, so
the sender reading it leaks nothing.

Other Batch 5 claims verified clean:
- All 25 helpers in the table exist in firestore.rules
  (isSignedIn through isUpdatingCoupleRhythm).
- users/{uid}/fcmTokens/{tokenId}: isOwner(uid) for read+write.
- users/{uid}/devices/{deviceId}: read = isOwner(uid) OR same-couple
  partner; create/update = isOwner(uid).
- invites/{code}: read = isSignedIn() && inviter match; writes denied.
- couples/{coupleId}: create requires E2EE field presence, update via
  isUpdatingCoupleRhythm OR isUpdatingRecoveryWrap only; delete denied.
- daily_question/{date}/answers/{userId}: metadata-only fields
  (userId/questionId/answerType/schemaVersion/answerDate/createdAt/
  updatedAt/isRevealed) per isCoupleKeyAnswerCreate.
- couples/{coupleId}/{this_or_that|desire_sync|how_well|wheel} wildcard
  match: answers map keyed by uid, enc:v1: per user, plus
  categoryName/questions, requires coupleEncryptionEnabled.
- entitlement_events/{eventId}: allow read,write = false (no client).
2026-07-08 23:24:38 -05:00
null 5e292e66ea docs(manual): batch 4 review — DOW mode table was partial (missed Saturday)
The server-authoritative mode-aware deterministic selection sub-section
listed the DOW -> mode map as:
  Monday mode_soft_monday, Tuesday mode_snack_mission, ..., Sunday
  mode_tiny_date_night

But the source-of-truth WEEKDAY_MODE_TAGS table in
functions/src/questions/assignDailyQuestion.ts has 7 entries, one per
weekday:
  0 Sunday    mode_tiny_date_night   (Slow Burn Sunday)
  1 Monday    mode_soft_monday       (Mood Check Monday)
  2 Tuesday   mode_snack_mission     (Tiny Win Tuesday)
  3 Wednesday mode_no_phone_moment   (Real One Wednesday)
  4 Thursday  mode_laugh_reset       (Laugh It Off Thursday)
  5 Friday    mode_flirty_friday     (Flirty Friday)
  6 Saturday  mode_weekend_side_quest (Side Quest Saturday)

Saturday was missing. Replaced the partial listing with the full
7-entry table, the constant name, and a note that the map is also
unit-tested (assignDailyQuestion.test.ts). Future reader can now grep
the mode tag against both files at once.

Other Batch 4 claims verified clean:
- Schedule '0 23 * * *' America/Chicago, memory 512MiB, timeoutSeconds 300
  in assignDailyQuestion source - all match.
- Document shape: questionId, date, assignedAt, expiresAt - all match the
  create() payload (assignedAt is serverTimestamp, expiresAt is
  timestampAt6PmCst(nextDay)).
- PAGE_SIZE = 300, ordered by __name__, startAfter pagination - matches.
- The daily_question allow create rule uses isCoupleKeyAnswerCreate OR
  isSealedAnswerCreate - matches the manual's 'must match one of two
  shapes' claim.
- The secure/{doc} read rule uses the partner-has-also-answered exists()
  check - matches the Reveal flow claim.
- isSealedThreadAnswerCreate / Update have NO answerDate and NO
  isRevealed field - matches the Thread questions claim.
2026-07-08 23:22:47 -05:00
null 0af6f24ab8 docs(manual): batch 3 review — Argon2id memory was 46080 KiB but the code is 47104
The Argon2id parameter block said:
  - memory: 46 MiB (46080 KiB)
  - iterations: 3
  - parallelism: 1

But the source constant in RecoveryKeyManager.kt is:
  private const val ARGON2_MEMORY_KB = 46 * 1024

46 * 1024 = 47104, not 46080 (a slipped digit). 46080 KiB would be
45 MiB. The iOS-side docstring in CoupleEncryptionManager.swift
already says 46 MiB = 47104 KiB, so this is the Android-side drift.

Replaced the parameter block with the source-of-truth constant names
(ARGON2_MEMORY_KB, ARGON2_ITERATIONS, ARGON2_PARALLELISM) and the
correct KiB value (47104) so the next reader can grep the code.

Other Batch 3 claims verified clean:
- Encryption version table: EncryptionVersion.STRICT=2, acceptInviteCallable
  hardcodes 2, throws if any of wrappedCoupleKey/kdfSalt/kdfParams is null.
- Tink AEAD wire formats: enc:v1:base64, sealed:v1:urlsafe-base64-no-padding,
  keybox:v1:urlsafe-base64-no-padding, pub:v1:urlsafe-base64-no-padding,
  sha256:urlsafe-base64-no-padding (43 chars). All match the source constants.
- AAD: FieldEncryptor uses coupleId; SealedAnswerEncryptor uses
  coupleId|questionId|userId; both match.
- ECIES P-256: UserKeyManager uses
  HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM;
  ReleaseKeyEncryptor contextInfo is coupleId|questionId|senderUserId|recipientUserId.
- All 5 firestore.rules regex helpers match the manual's reference table.
- wrapReleaseKeyCallable reads the recipient public key from
  users/{uid}/devices/primary (verified in function source).
- CoupleKeyStore persists Tink keyset handles in EncryptedSharedPreferences
  (Keystore-backed) via SecurePreferencesFactory.
2026-07-08 23:21:35 -05:00
null 1b36eba692 docs(manual): batch 2 review — recovery phrase '256-word list' was wrong (it's 248)
The Recovery phrase flow said RecoveryKeyManager.generateRecoveryPhrase()
draws from a 256-word list. Verified the actual list size in
RecoveryKeyManager.kt: the hard-coded WORDLIST array has 248 entries
(python re.findall over the array literal), and the iOS wordlist file
iphone/Closer/Crypto/Resources/wordlist.txt is 247 lines (last word
'real' with no trailing newline, so 248 entries). The '256' is also
wrong in the inline comment in RecoveryKeyManager.kt - a pre-existing
comment bug from before the R24 iOS port (IOS_E2EE_STATUS.md notes the
iOS SPEC.md originally said 256 too, corrected in 922364f). The Android
side never got the same comment fix.

Manual now says 248 with the source-of-truth pointers, and computes
the entropy as 248^10 (a quick sanity check) so the next reader doesn't
trust the wrong number on either side.

Other Batch 2 claims verified clean:
- All 5 Android files in the 'Key Android files' list exist.
- All 2 Cloud Functions files in the 'Key Cloud Functions' list exist.
- Rate limit: 1h window, 10 max, 25h TTL on invite_attempts - all match
  ACCEPT_RATE_LIMIT_WINDOW_MS / ACCEPT_RATE_LIMIT_MAX / ACCEPT_ATTEMPT_TTL_MS
  in acceptInviteCallable.ts and the fieldOverrides entry in
  firestore.indexes.json.
- Couples doc model fields (id, userIds, inviteCode, createdAt,
  streakCount, lastAnsweredAt, currentQuestionId, activePackId,
  encryptionVersion, wrappedCoupleKey, kdfSalt, kdfParams) all match
  the create() payload in acceptInviteCallable.ts. createdAt uses
  FieldValue.serverTimestamp() (manual says 'server-side' - correct).
- EncryptionVersion.STRICT = 2 in EncryptionVersion.kt.
2026-07-08 23:20:16 -05:00
null ef5a2331fd docs(manual): batch 1 review — iOS platform row 'E2EE not yet implemented' was stale
The Three platform split table at the top of the manual said iOS
'E2EE cross-compatibility not yet implemented' even though the iOS
E2EE section below it (and IOS_E2EE_STATUS.md) clearly state it is
code-complete for the schemaVersion 2 (couple-key) daily-answer path.
The schemaVersion 3 sealed-answer path is the part that is
infrastructure-gated (paired-CI vector run + macOS end-to-end).

Replaced the row text with a one-liner that points to the existing
iOS E2EE gap sub-section so future readers don't get the wrong first
impression from the overview.

Batch 1 of the Phase 3 plan (Engineering_Reference_Manual_Plan.md).
Repository layout Android/iOS/Cloud Functions all verified against
the live source - no other drift in this batch.
2026-07-08 23:18:33 -05:00
null d31b58d238 docs(manual): review pass — missing dirs + 3 notif prefs + B6c/B6d mix-up
After pushing the Phase 2 sync, evidence-first review against the live
repo caught:

- Repository layout (Android) was missing directories that have shipped
  since v0.2.1: widget/ (Glance Today, R29), core/firebase/, core/media/,
  data/backup/ (R24 E2EE backup + partner-assist), data/local/{converters,
  entity,mapper} subdirs, data/security/, domain/usecase/ (resolver +
  GameSessionManager + SoloAnswerMigrator), top-level notifications/
  package, and ui/{recap,messages,questions}/ + components/ subdirs.
- Repository layout (iOS) was missing Crypto/Resources/ (wordlist) and
  the Crypto/ design notes (SCHEMA_VERSION_DECISION.md, SPEC.md).
- User doc field list + per-collection enforcement were missing the 3
  R20 notif preferences: notifDailyReminder, notifStreakReminder,
  notifPromotional. Verified all 5 are mirrored by
  FirestoreUserDataSource.updateNotificationPrefs() and listed in the
  firestore.rules user-doc allowlist.
- One copy fix: 'The B6d split of onGameSessionUpdate' -> 'The B6c
  split' (B6c did the part-finished split; B6d was the logger finish).

Anchors verified clean (30/30). DEVELOPMENT_LOG.md is gitignored so
the local review notes stay on this box.
2026-07-08 22:10:40 -05:00
null 213cfddb16 docs(manual): sync Engineering Reference Manual with post-R30 / v2-functions codebase
- Cloud Functions: rewrite the module tree for the v2 migration (B0-B6d,
  2026-07-08); add options.ts (global v2 setGlobalOptions, Cloud Run CPU
  quota workaround), log.ts, the shared push/quietHours/idempotency/
  pruneTokens/time infra under notifications/, releaseKey/, backup/,
  the dates/onDate* triggers, couples/aggregateOutcomes, and the new
  sendStreakReminder + sendThinkingOfYouCallable. Replace the single
  onGamePartFinished with the four per-game part-finished triggers
  shipped in B6c. Note the webhook is not deployed (RevenueCat project
  not yet created; export commented out in functions/src/index.ts).
- Daily question lifecycle: replace the 'picks a random' description
  with the new server-authoritative, mode-aware, deterministic picker
  that mirrors the client's DailyModeResolver; add a Server-authoritative
  sub-section with the frozen DOW -> mode map and the daily_fun_mc
  exclusion. Note the couple-scan pagination + unseeded-pool skip.
- Billing: add the Purchases.logIn(firebaseAuth.currentUser!!.uid)
  identity link (commit b99a8338) and the typed BillingException mapping.
  Cross-link to the Webhook reliability section for the not-deployed state.
- iOS: fix the Repository layout iOS Crypto/ block (R24 E2EE code ships
  in it; no longer 'intentionally empty'); correct the 'pairing from iOS
  fails' claim (works for schemaVersion 2 path; schemaVersion 3 is
  infrastructure-gated per IOS_E2EE_STATUS.md); correct the 'iOS couples
  have no recovery path' claim (R24 batch 2 added iOS recovery phrase).
- TOC + anchors: add the new sub-anchors; fix three pre-existing broken
  anchors (r10, ios-android-sealed-answer-bridge, recovery-phrase-change
  desync). 30/30 anchors verified clean with a GFM slug checker.
- New landmines: BANNER-LIFE-001 (R30 game banner lifecycle; the B6c
  per-game split means a new game also needs a per-game monitor hook)
  and FUNCTIONS-V2-DEPLOY (2nd-gen deploy / CPU-quota / Eventarc
  propagation pattern, the 'Changing from an HTTPS function to a
  background triggered function' error and the launch-time quota
  increase to restore 1 vCPU + concurrency 80).
- Engineering_Reference_Manual_Plan.md: add Phase 2 status entry.
2026-07-08 21:56:44 -05:00
null 55699e17ed feat: security hardening, CI, analytics, invite deep links, solo mode, weekly recap
Roadmap phases 0-3.1 (see plan). Verified: unit tests, functions tests,
firestore rules 119/119, live emulator smokes.

Security + CI:
- Move App Check debug token out of committed build.gradle.kts to
  local.properties (secret() resolver: local.properties -> -P -> env);
  admin service-account key relocated outside the repo.
- Add GitHub Actions (android unit/scan/gitleaks, backend functions +
  rules emulator via emulators:exec) with a CI google-services.json stub
  and a reviewed .gitleaksignore baseline.
- Fix firestore rules tests to the current deployed contract (was 108/118,
  now 119/119).

Analytics + consent:
- FirebaseRetentionAnalytics behind a fail-safe composite sink, single-
  sourced hashing (AnalyticsHashing), Settings -> Privacy opt-out toggle
  gating Firebase + the sink, and the ~11 missing event call sites.
- NoPlaintextInAnalyticsTest guards the content-free rule at build time.

Funnel:
- Invite deep links (https App Link + closer:// fallback), JoinLink parser,
  PendingJoinCodeStore, prefilled/already-paired pairing, richer share text.
- Solo pre-pairing: UnpairedLockedCard on Play, SoloAnswerMigrator + consent
  dialog moving pre-pairing answers into Couple Lore.

Weekly recap:
- WeeklyRecapScreen driven by the existing generator from local answer data,
  Home card rewired, content-free shareable card.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:33:38 -05:00
null 38eae8f915 docs: R25 coverage entries, instrumented test landmine warning, tokenized snapshot URL note 2026-06-30 23:33:54 -05:00
null 4773570745 docs: update ClaudeReport (R24-d/c) and Engineering_Reference_Manual (keybox phrase envelope) 2026-06-30 21:25:00 -05:00
null 1e9f8b97bc docs: update Future.md, ClaudeQAPlan.md, ClaudeReport.md, ClaudeiOSPlan.md, Engineering_Reference_Manual.md for R24 backup/restore 2026-06-30 20:43:34 -05:00
null 968ab563a0 docs: add R23-DQ-001 entry to Engineering Reference Manual 2026-06-30 19:06:18 -05:00
null 2a5c40508e feat(notifications): QuietHoursManager + NotificationSettingsScreen rewrite, Cloud Functions (streakReminder, quietHours, reengagement, gameRetention), UserRepository E2EE wiring, SettingsDataStore, firestore rules, wiring-scan 2026-06-30 00:38:06 -05:00
null f6291e1f2e feat(home): HomeScreen rewrite, HomePriorityEngine polish, CoupleRepository E2EE wiring, OutcomeCheckInDialog, YourProgress, MemoryLane, settings/pairing/paywall/play/wheel/question screens cleanup, brand illustrations, QA docs 2026-06-29 16:51:46 -05:00
null b5b8ad8df9 feat(games): GameSessionManager cleanup, QuestionSessionRepositoryImpl fixes, HomeViewModel game-state wiring, QA docs 2026-06-29 12:20:07 -05:00
null 582aefcec2 feat(tools): capture_android_canonical_vectors.sh for paired-CI fixture filling; sync Engineering Manual for wrapReleaseKeyCallable + iOS Keychain 2026-06-28 17:31:30 -05:00
null 736885c103 docs: update README and Engineering Manual — auth uses Credential Manager, iOS pairing blocked, add scripts/ layout, new screenshot placeholder 2026-06-28 12:55:24 -05:00
null faa0d9007f docs: consolidate Future backlog, update ClaudeQAPlan/ClaudeReport, note FUTURE.md removal in Engineering Manual 2026-06-28 12:45:54 -05:00
null 186b40546b docs(manual): Batch 9 — fix TOC nesting, broken anchor, and stale iOS claim in repository layout 2026-06-28 11:14:37 -05:00
null df2837dc06 docs(manual): Batch 8 — update theme landmine entry (C-ART-EDGE-002 closed, theme scanner mandatory) 2026-06-28 11:13:21 -05:00
null eb78b920a4 docs(manual): Batch 7 — correct iOS E2EE gap details and ProGuard description 2026-06-28 11:12:15 -05:00
null e4175ebb52 docs(manual): Batch 6 — correct Billing webhook flow, add CouplePremiumChecker, fix quiet-hours and notification_queue claims 2026-06-28 11:10:08 -05:00
null 74e46761f2 docs(manual): Batch 5 — correct Firestore rules per-collection notes (users premium, couples create/update shape, remove migration helpers) 2026-06-28 11:08:42 -05:00
null d19f0f8eb8 docs(manual): Batch 4 — update Cloud Functions list, module responsibilities, webhook ack order, and schedule timezones 2026-06-28 11:07:24 -05:00
null 08368b3e01 docs(manual): Batch 3 — correct E2EE and Firestore data model (user fields, couple fields, date plans, preferences, bucket list, secure subdoc) 2026-06-28 11:06:20 -05:00
null a6aa23eee2 docs(manual): Batch 2 — correct auth providers and couples model (no anonymous, no migration field, Google SDK not Credential Manager) 2026-06-28 11:04:15 -05:00
null be710a0c9d docs(manual): Batch 1 — correct repository layout paths (core/feature, QuestionDao, QuestionJsonParser) 2026-06-28 11:02:45 -05:00
null 4deed13845 feat: theme-scan.sh + improved Pass C scanner methodology (tier 1A-1G, tier 2, tier 3 roadmap) 2026-06-28 10:28:17 -05:00
null fe3ea7715c feat: add automated theme-mismatch scanner to Pass C methodology (Tier 1-3) 2026-06-28 10:20:41 -05:00
null 37ed7cebec feat: quiet hours notifications, settings UI, game session updates, docs 2026-06-28 10:00:25 -05:00
null c31eea2549 feat(premium): one-time PremiumUnlockOverlay + theme/art fixes (R13)
- New PremiumUnlockOverlay.kt — one-time 'Premium unlocked' celebration for both partners on couple-shared Premium activation. Driven off CouplePremiumChecker (not the push) so it surfaces for both wherever they are. Gated by persisted premiumUnlockCelebrated flag, auto-reset on lapse.
- New illustration_premium_unlock.png asset for the overlay.
- AppNavigation hosts the overlay at root alongside MessageBubbleOverlay.
- SettingsDataStore: new premiumUnlockCelebrated flag + setter.
- ThisOrThatScreen: theme-token fixes for A/B options, mood chips, versus badge, progress, ChoicePromptBackdrop — all read from MaterialTheme.colorScheme. Bumps dark-mode legibility.
- ConversationScreen: bump PendingMediaChip retry/dismiss IconButtons to 48dp touch targets.
- PlayHubScreen / ActivityScreen / HomeScreen / SubscriptionScreen / OnboardingScreen / PairPromptScreen / PaywallScreen / LocalQuestionContent / OutcomeCheckInDialog / ChatComponents: assorted R13 polish.
- firestore.rules (n/a this batch), SettingsRepository, manual: doc + flag wiring.
- Manual: new C-DARK-UI-001 + C-ART-EDGE-002 landmines, Premium-unlock-modal pattern note.
2026-06-27 21:01:16 -05:00
null 361eff18e3 docs(manual): review pass 3 — routing route names, notification_queue reality, /questions server-only, conversations/messages data model, premium sharing, entitlement fields, daily reminder skip conditions, iOS tree fix 2026-06-27 15:27:14 -05:00
null c167211323 docs(manual): review pass 2 — iOS pairing actually broken, notification routes corrected, repo tree fixes, schedules updated, reveal-flow read-path gotcha 2026-06-27 15:14:09 -05:00
null 439ae7ce51 docs(manual): review fixes — secure subdoc reveal flow, encryption version accuracy, anchor slug corrections, ToC/how-to updates, helper function list, gitignore case-sensitivity note 2026-06-27 15:00:47 -05:00
null 3924d63c7b docs(manual): R10 architecture updates — game push semantics, foreground banner, deep-link routes, premium gate pattern, landmines section 2026-06-27 14:50:23 -05:00
null 54fdaf831f docs(manual): fix placeholder notification function descriptions, add new collections, biometric flow, iOS gitignore note 2026-06-21 18:47:18 -05:00
null 578851964c docs: write Engineering Reference Manual with verified facts, fix Bishop pass issues 2026-06-21 18:44:31 -05:00