48 lines
5.6 KiB
Markdown
48 lines
5.6 KiB
Markdown
# Claude QA Report — Full-App QA (living report)
|
||
|
||
> **Verdict (2026-06-25, R8): 1 open P1 — I-001 ("Your Progress" outcomes read is rules-denied → feature silently broken). All else clean; security cornerstone clean.**
|
||
>
|
||
> This report shows **current state only**. Fixed issues live here for **one** confirmation round, then they're pruned
|
||
> to the archived-ID line below (full detail stays in git history). See **Report hygiene** in `ClaudeQAPlan.md`.
|
||
|
||
## Run-state (current)
|
||
`Round 8 (re-QA + Passes I/J) — IN PROGRESS | 1 open P1 (I-001) + 1 P3 (J-OBS) | Passes I+J done | NEXT ACTION: fix phase — I-001 (build+verify), then re-QA confirm.`
|
||
- **Build:** client HEAD `23dd6a7`, Cloud Functions deployed.
|
||
- **Devices / accounts:** emulator-5554 = QA (`Y05AKO2IlTPMa0JQW1BiNIM0uzK2`) · emulator-5556 = Sam (`imDjjO…`) · paired, coupleId `Xal3Kw3gjSdn0niERYKJ`, both free (baseline restored).
|
||
- **Docs:** Playbook `ClaudeQAPlan.md` · Coverage `ClaudeQACoverage.md` · Ideas `Future.md` `## QA` · Branding `ClaudeBrandingReview.md`.
|
||
|
||
## Severity board
|
||
| Severity | Open | Fixed (pending 1 confirm) |
|
||
|---|---|---|
|
||
| P0 | 0 | 0 |
|
||
| P1 | **1** | 0 |
|
||
| P2 | 0 | 0 |
|
||
| P3 | **1** | 0 |
|
||
|
||
## Open issues
|
||
| ID | Sev | Area | Description | Repro | Suggested fix | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| I-001 | **P1** | Outcomes / "Your Progress" read | `FirestoreOutcomeDataSource.getOutcomes()` (line 45-53) issues a **bare collection list** `.get()` on `couples/{cid}/outcomes`, but the rule (firestore.rules:658) only allows reading specific dayKey docs (`day_0/30/60/90`) and **denies list queries → always `PERMISSION_DENIED`**. `OutcomeRepositoryImpl.getOutcomes` (26-29) swallows it (records non-fatal → returns `emptyList()`). Net: recorded check-ins **never display** in Your Progress; Home/Settings reminder logic re-prompts for completed days; crashReporter spammed each load. Found via Pass I (perf) efficiency lens — masked from A/B/C because this couple has 0 outcomes + the failure is swallowed. | Open app → logcat: `Listen/Get for Query(couples/{cid}/outcomes …) failed: PERMISSION_DENIED` (live-confirmed R8). | Constrain the query to satisfy the rule: `.whereIn(FieldPath.documentId(), listOf("day_0","day_30","day_60","day_90")).get()` (or 4 parallel `getOutcome` gets). No rules change needed. | **Open** |
|
||
| J-OBS | P3 | A11y / touch targets | A few conversation icon-buttons measure **~42–45dp wide** (48dp tall) — single-axis marginal miss of the 48dp target; fully operable. Most controls are 48dp. | Pass J: uiautomator bounds on conversation → 2–3 clickables `<126px` wide. | Bump those icon-buttons to 48dp min (e.g. `Modifier.minimumInteractiveComponentSize()` / `size(48.dp)`). | **Open** |
|
||
|
||
## Resolved & confirmed (archived — full detail in git history)
|
||
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 · F-RACE-001 — all fixed and re-verified (commits in history; **F-RACE-001** fixed `23dd6a7`, re-confirmed live R8: race → 1 session, loser joins same set). Pruned per the one-confirmation-round rule. (C-OBS / `outcomes` list / SubscriptionScreen per-user gate = investigated, **not bugs**.)
|
||
|
||
## Security cornerstone — clean (Pass D, deep dive, Round 7)
|
||
- **D1 at-rest:** chat text + `lastMessagePreview` + all 4 game-answer collections (ToT / How Well / Desire Sync / Wheel, both users) + Memory Lane capsules + date-swipe actions = `enc:v1:`. No plaintext content; only metadata in clear.
|
||
- **D2/D3 access:** non-member denied **all** reads/writes (raw Firestore REST → 403); real premium write `users/{uid}/entitlements/premium` denied (server-only → **no self-grant**); cross-couple denied.
|
||
- **D4 keys:** couple key phrase-wrapped (argon2id); recovery phrase server-blind; `encryptedRecoveryPhrase` wiped on acceptance; plaintext `inviteCode` not exploitable (invite readable only by inviter; no code-encrypted secret persisted).
|
||
- **Robustness:** malformed/abusive deep-link intents (unknown type, missing extras, injection/path-traversal) → 0 crash; killed-state cold-start chat deep-link → conversation loads.
|
||
|
||
## Round history (one line each)
|
||
- **R8** (in progress) — F-RACE-001 re-confirmed live (race → 1 session; loser joins winner's same-set session via "Join the game") + pruned; running Passes I/J.
|
||
- **R7** — multi-angle security/concurrency deep dive → cornerstone fully clean; F-RACE-001 found + fixed + verified. 0 new open.
|
||
- **R6** — branding drop + Future.md backlog regression (white-keyhole icons/loader/splash, inclusive gender, copy, rate-limit split, results-push suppression, paywall retry/offline) → 0 new open.
|
||
- **R5** — Cloud Functions deployed (E-OBS channel fix, E-003 results routing) + new Pass G (account creation / fake-account abuse) clean → 0 open.
|
||
- **R1–R4** — baseline Passes A–F report-only; every P0–P2 found was fixed + verified (see archived IDs).
|
||
|
||
## Operational constants
|
||
- **Execution mode:** autonomous run-to-completion — don't stop; fix blockers inline; cycle fix→re-QA until flawless. Don't hand back when context fills — re-read this run-state + coverage after any compaction. Commit before interruptible work; recover stuck sessions via the session-start ritual.
|
||
- **Standing authorization (user, 2026-06-24):** may `firebase deploy --only firestore:rules` + has admin access (Firestore reads/writes/seeds + entitlement toggles) — run without pausing. Only the macOS requirement for iOS (Parts 2/3) is a hard stop.
|
||
- **Hardening backlog → Future.md:** App Check not enforced on Firestore; `users/{uid}` update rule allows arbitrary non-`hasPremium` fields (tighten to a field allowlist).
|