Closer/ClaudeQACoverage.md

95 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Claude QA Coverage Matrix
> **Resume anchor — current status only.** Statuses: `pass | fail→id | todo | n/a | not implemented→Future.md | blocked→id`.
> Build `23dd6a7`. Position + verdict: see `ClaudeReport.md` run-state. **Verdict: 0 open issues (P0P3); AG covered, I/J pending.**
> Hygiene: this is a *current-status* matrix, not a per-round changelog — `fail→id` flips to `pass` once a fix is
> confirmed (ID archived below); finished rounds collapse to the history line. (See Report hygiene in `ClaudeQAPlan.md`.)
## Status at a glance
| Pass | Coverage | Status |
|---|---|---|
| A — Couple-shared premium | all gated features × neither/partner/self | ✅ pass |
| B — Games lifecycle | all 7 games played full, 2-device, real user-nav | ✅ pass |
| C — Visual (light+dark) | ~14 core screen-types both themes | ✅ pass · deep/list screens **deferred** |
| D — Security & encryption | D1 at-rest · D2 rules · D3 live raw-API · D4D6 | ✅ clean |
| E — Notifications | chat + game start/finish/results live, both-client + suppression | ✅ pass · full fg/bg/killed matrix **partial** |
| F — Resilience | concurrency · offline · lifecycle · process-death · time | ✅ pass |
| G — Account creation / fake-account | sign-up · validation · duplicate · invite-abuse | ✅ pass |
| H — Branding & artwork | consumer brand walk → prompts | see `ClaudeBrandingReview.md` |
| I — Performance & route efficiency | — | **todo (Round 8)** |
| J — Accessibility | — | **todo (Round 8)** |
**Archived issue IDs (fixed + confirmed, detail in git):** A-001 · A-003 · A-OBS · B-001 · B-002 · B-003 · B-004 · C-CC-001 · C-DS-001 · C-NAV-001 · D-001 · E-001 · E-002 · E-003 · E-OBS · F-OBS. Pending one confirm: **F-RACE-001**.
---
## Pass A — Couple-shared premium (neither / partner-only / self)
| Feature | neither→locked | partner→both unlock | self→unlock | Status |
|---|---|---|---|---|
| Chat media + reactions | pass | pass | pass | ✅ pass (reference pattern) |
| Play: Desire Sync | pass | pass | pass | ✅ pass |
| Play: Memory Lane | pass | pass | pass | ✅ pass |
| Play: Connection Challenges | pass | pass | pass | ✅ pass |
| Question Packs (premium) | pass | pass | pass | ✅ pass |
| Wheel: Category Picker / Spin / History | pass | pass | pass | ✅ pass |
| Date Match / Plan Date | pass | pass | pass | ✅ pass |
| Subscription screen (own status) | n/a | n/a | n/a | ✅ pass (by-design per-user) |
Verified live: neither→paywall ("Go deeper together"); partner→couple-shared unlock (Sam free entered Desire Sync + Memory Lane); self→unlock; premium badges hidden under premium / shown when free. (A-001 couple-shared gap + A-003 badge fixed & confirmed.)
## Pass B — Games lifecycle (start / play / finish + results, 2-device, real user-nav)
All 7 played one complete time through on both devices via the real in-app path; gameplay all PASS.
| Game | starts | plays | finishes/results | no crash | Evidence |
|---|---|---|---|---|---|
| 1. This or That | pass | pass | pass | pass | 5/5 via Play hub, answers synced, results match both ("Two peas in a pod"). |
| 2. How Well Do You Know Me | pass | pass | pass | pass | QA answered 5 (incl. 15 scale); Sam predicted via hub → 4/5, wrong one marked ✗ on both, scoring accurate. |
| 3. Desire Sync | pass | pass | pass | pass | QA(free) entered w/o paywall; both answered 5 Yes/No → exactly 3 mutual desires, mismatches hidden, match on both. |
| 4. Connection Challenges | pass | pass | pass | pass | Gratitude Week → both did Day 1 → 🔥1, advanced to Day 2 synced. (7-day series time-gated; per-day cycle verified.) |
| 5. Memory Lane | pass | pass (create+seal) | pass (sealed) | pass | Capsule sealed "Opens in 29 days", encrypted at rest (title+content `enc:v1:`), cross-device. Unlock future-dated. |
| 6. Spin the Wheel | pass | pass | pass | pass | Spun → category → both answered all 10, per-Q You/partner breakdown matches both, session synced. |
| 7. Date Match | pass | pass | pass | pass | Both swiped deck, 3 mutual likes → 3 `date_matches`, "It's a match!" modal + live push, "Your Matches" shows all 3. |
Note: exit each game via "Back to Play" between games so the session closes (B-001 auto-completion fix verified). F-RACE-001 (simultaneous start) fixed — see Pass F.
## Pass C — Visual (light + dark), all ~50 routes
~14 screen-types swept Dark (5554) + several Light (5556): all render clean, readable, no FATAL, no dark-mode contrast issues; **0 `enc:v1:` leaked to conversation UI**. Covered: Home, Play hub, all 7 game screens (setup/play/reveal), Paywall, Settings (+Subscription +Appearance), Today/daily-question (+answer detail), Messages inbox, Conversation (image+voice+text+reaction). Back-stack clean (deep→hub→Home→launcher, no double-back).
- **Deferred** (standard list/detail, lower risk; cover in Round 8): Question Packs detail · Bucket List · Past Games · Wheel History · Answer Reveal (sealed) · Date Builder/Plan Date · fresh-account auth/onboarding/pairing.
## Pass D — Security & encryption (D1D6) — clean, no P0/P1
- **D1 at-rest (admin ground-truth):** messages `text` + `lastMessagePreview`, all 4 game-answer collections (this_or_that/how_well/desire_sync/wheel, both users), capsule title+content, `date_swipes.actions` = `enc:v1:`; `wrappedCoupleKey` ciphertext (recovery-phrase-wrapped, **argon2id**); `encryptedRecoveryPhrase` server-blind + **wiped on acceptance**; plaintext `inviteCode` **not exploitable** (no code-encrypted secret persists; `/invites/{code}` readable only by inviter).
- **D2 rules:** no catch-all, no blanket `if true`; sessions update allowlist + immutable `startedByUserId` + monotonic status; `hasPremium` + entitlements server-only; ciphertext enforced on private fields; capsules/challenges member-scoped.
- **D3 raw-API negative (LIVE):** non-member ID token → Firestore REST on couple doc/conversation/messages/answers/session/capsules/partner-profile = **all 403**; non-member writes incl. real `users/{uid}/entitlements/premium` = **all 403 → no self-grant**. Member token reads 200 → **App Check not enforced on Firestore; rules are the sole gate and hold**.
- **D4/D5/D6:** wrapped couple key + KDF; App Check (client), gitignored SA JSONs, `allowBackup=false`; analytics metadata-only. Unchanged, hold.
- Two hardening notes → `Future.md` (App Check off on Firestore; `users/{uid}` update rule allows arbitrary non-`hasPremium` fields).
## Pass E — Notifications (type × {foreground / background / killed} + tap-to-open, both clients)
Full live two-device run (games + messages):
- **chat_message** ✅ end-to-end — channel `partner_activity`, title "Sam sent a message" (name, not private), body content-free, **text NOT in payload**; tap → exact conversation with content; white monochrome small icon.
- **partner_started_game** ✅ — channel `game_activity`, "QA is playing… Tap to join!" (content-free); tap → joins the active session.
- **partner_finished_game / results** ✅ — results push delivered to backgrounded partner, channel `game_activity`, content-free; tap → per-session results (per E-003 fix).
- **results-suppression** ✅ — partner foregrounded on the session received 0 pushes (ActiveGameSessionMonitor), while backgrounded partner got the results push. Delivery + suppression both confirmed.
- **Deferred (Round 8):** the full 17-type × {fg/bg/killed} matrix isn't exhaustively run live — remaining types are routing-code-verified + centralized in `PartnerNotificationType`; date_match push verified live. New types added to the plan's Pass E inventory (`join_game`, `partner_joined_game`, `game_ended`, `date_plan_update`, etc.) = **todo**.
## Pass F — Resilience / lifecycle / concurrency / time
- **Concurrency race:** F-RACE-001 (P1) found + **fixed** (atomic transactional create on per-couple `sessions/_active` pointer) + **verified live** (parallel-tap race → 1 session, was 2). *Pending one confirmation round, then prune.*
- **Offline:** airplane mode → Today renders from cache, no crash.
- **Lifecycle:** rotation/config-change → state preserved; ~6 cold restarts → clean to Home (auth persists).
- **Robustness:** malformed/abusive deep-link intents (unknown type, missing extras, injection/path-traversal) → 0 crash; killed-state cold-start chat deep-link → conversation loads.
- **Deferred (Round 8):** time-travel-gated content (capsule unlock, challenge day-gating); broader network-flaky across answers/dates; account-lifecycle (unpair→re-pair, deletion cascade) deep run. Minor note: race-loser sometimes lands on Play hub vs WaitingForPartner (no dup/crash; pre-existing routing).
## Pass G — Account creation, validation & fake-account abuse
Sign-up end-to-end (email/pw/confirm → 3-step profile → unpaired home) ✅; weak-password → friendly "at least 8 characters" ✅; fresh-account isolation (zero couple data) ✅; **duplicate-email → `auth/email-already-exists`** rejected ✅; invite single-use + 24h expiry, **bogus code → "Invite not found."** ✅; recovery phrase client-generated ✅; sign-out → onboarding → debug-token restore ✅. **No security findings.** (Non-member READ denial = live D3 above + app-level isolation.)
## Passes H / I / J
- **H Branding** — deliverable in `ClaudeBrandingReview.md` (consumer brand walk → ready-to-paste art prompts).
- **I Performance & route efficiency** — **todo (Round 8):** gfxinfo/jank, redundant-read counts, listener-leak check, route smoke checklist.
- **J Accessibility** — **todo (Round 8):** font_scale 1.3/1.5/2.0, TalkBack semantics, WCAG-AA contrast, 48dp targets, keyboard, reduce-motion.
---
## Round history (one line each)
- **R7** — security/concurrency deep dive (multi-angle): cornerstone clean; F-RACE-001 found+fixed+verified. 0 new open.
- **R6** — branding drop + Future.md backlog regression: 0 new open.
- **R5** — Cloud Functions deployed (E-OBS/E-003) + new Pass G clean: 0 open.
- **R2R4** — play-as-user game restart + fix phase; all P0P2 fixed + verified (archived IDs above).