118 lines
5.7 KiB
Markdown
118 lines
5.7 KiB
Markdown
|
|
# Claude QA Plan — Period
|
||
|
|
|
||
|
|
```
|
||
|
|
Status: Current
|
||
|
|
Owner: _null
|
||
|
|
Last reviewed: 2026-08-18
|
||
|
|
Governs: what a QA round consists of
|
||
|
|
Review trigger: Any new user-facing surface, or a defect class that got through
|
||
|
|
```
|
||
|
|
|
||
|
|
> The playbook. What a round *is*, so two rounds are comparable and a gap is
|
||
|
|
> visible rather than assumed covered.
|
||
|
|
|
||
|
|
## Before a round
|
||
|
|
|
||
|
|
- Build from a clean checkout at a known SHA, and **record that SHA**. A finding
|
||
|
|
without one cannot be re-tested, and a finding that cannot be re-tested cannot
|
||
|
|
be closed.
|
||
|
|
- Build from a detached worktree if other work is in flight, so uncommitted
|
||
|
|
changes cannot contaminate what is under test.
|
||
|
|
- Note the environment: device or emulator, Android version and API level,
|
||
|
|
display size, font scale, and whether the device has a lock screen set. The
|
||
|
|
last one matters more here than anywhere — half of pass F depends on it.
|
||
|
|
- Seed the cycle history deliberately. A round run against three cycles and a
|
||
|
|
round run against twelve are not comparable, and the forecast is the product.
|
||
|
|
|
||
|
|
## The passes
|
||
|
|
|
||
|
|
Each pass gets a letter, so `ClaudeQACoverage.md` can report per pass and a
|
||
|
|
skipped one is visible.
|
||
|
|
|
||
|
|
| Pass | What it covers |
|
||
|
|
| --- | --- |
|
||
|
|
| A | First run: install on a clean profile, onboarding end to end, notification permission prompt, empty states |
|
||
|
|
| B | The core loop as a real user: log a period, see the forecast update, log the next one |
|
||
|
|
| C | Things going wrong: airplane mode, notification permission denied, invalid or duplicate dates, period logged in the future, app killed mid-entry |
|
||
|
|
| D | Persistence: force-stop and relaunch, reboot, background for days, app update over an existing install with a Room migration |
|
||
|
|
| E | **The forecast under the histories that break naive engines** — the §51 acceptance cases exercised through the UI rather than only in unit tests: stable 35-day user, highly variable user, 45-day outlier, repeated "Not yet" |
|
||
|
|
| F | **Notification privacy at every mode**, on a real lock screen: Discreet, Maximum privacy, Direct. What is visible without unlocking is the finding |
|
||
|
|
| G | Accessibility: TalkBack through the core loop, largest font scale, calendar states distinguishable in greyscale, touch targets, focus order, reduced motion |
|
||
|
|
| H | Data ownership and leakage: export, Delete My Data, biometric/PIN gate, incognito launcher, and the built artifact inspected for anything health-derived reaching the ads or analytics path |
|
||
|
|
|
||
|
|
<Add, remove and rename to fit. A pass that never applies is noise; a pass that
|
||
|
|
is always skipped is a lie.>
|
||
|
|
|
||
|
|
### Why E is separate from B
|
||
|
|
|
||
|
|
Pass B walks the loop as a satisfied user — a plausible cycle history, a
|
||
|
|
forecast that looks right. It cannot see the defect the product is most likely
|
||
|
|
to actually ship, because that defect only appears with a history B would never
|
||
|
|
generate.
|
||
|
|
|
||
|
|
[`../planning/PRODUCT_PLAN.md`](../planning/PRODUCT_PLAN.md) names it as a
|
||
|
|
**core product defect**: a user recording 34, 35, 36, 34, 35 who is predicted a
|
||
|
|
28- or 29-day cycle. No amount of walking the happy path finds that. E exists to
|
||
|
|
run the histories in §51 and check the *window and the confidence*, not just the
|
||
|
|
date — a right date with a wrong window is still wrong.
|
||
|
|
|
||
|
|
### Why F is a pass and not a checkbox
|
||
|
|
|
||
|
|
The most likely real breach in this product is not a database compromise. It is
|
||
|
|
a lock screen in a shared room.
|
||
|
|
|
||
|
|
Discreet is the default and Maximum privacy exists for people who need it, which
|
||
|
|
means both are load-bearing and both are only testable by looking at an actual
|
||
|
|
locked device. A unit test can assert the string; it cannot tell you Android
|
||
|
|
expanded the notification, or that a heads-up popup showed the private text on
|
||
|
|
its way past. Run F on hardware with a lock screen set, at every mode, for every
|
||
|
|
notification type in §29.
|
||
|
|
|
||
|
|
### What is deliberately not here
|
||
|
|
|
||
|
|
**No pass for money flowing backwards.** The template carries one, and it is
|
||
|
|
deleted rather than carried as permanently skipped: the only money here is a
|
||
|
|
one-time Play purchase, refunds are handled by Google, and there is no
|
||
|
|
entitlement of ours to claw back beyond what Play reports. If a subscription is
|
||
|
|
ever added, this pass comes back with it.
|
||
|
|
|
||
|
|
**No pass for authorisation.** There are no accounts and no server, so there is
|
||
|
|
no entitlement to confuse with authentication. That is a property of the
|
||
|
|
architecture, and if it changes this section is the trigger to re-add the pass.
|
||
|
|
|
||
|
|
## What counts as a finding
|
||
|
|
|
||
|
|
A finding needs: what was done, what happened, what should have happened, and
|
||
|
|
the build SHA. Without the SHA it cannot be re-tested, and a finding that cannot
|
||
|
|
be re-tested cannot be closed.
|
||
|
|
|
||
|
|
## Severity
|
||
|
|
|
||
|
|
Findings are filed as issues, labelled:
|
||
|
|
|
||
|
|
- **P0** — ships broken, or loses data
|
||
|
|
- **P1** — materially wrong, but shippable
|
||
|
|
- **P2** — cosmetic or low impact
|
||
|
|
- **release-blocker** — a release built today would be wrong rather than merely
|
||
|
|
incomplete
|
||
|
|
|
||
|
|
Exactly these label names: the Command Center queries them by name, and a
|
||
|
|
repository that spells them differently has its defects reported as *not
|
||
|
|
adopted* rather than counted wrongly.
|
||
|
|
|
||
|
|
Severity is what it costs, not how annoying it is to fix.
|
||
|
|
|
||
|
|
## After a round
|
||
|
|
|
||
|
|
File each finding as a labelled issue. Update `ClaudeReport.md`'s run-state
|
||
|
|
block and its overall sentence, and `ClaudeQACoverage.md` with what each pass
|
||
|
|
actually reached. A pass that could
|
||
|
|
not be run is recorded as blocked, with what blocks it — never quietly left out,
|
||
|
|
which reads identically to "passed".
|
||
|
|
|
||
|
|
Then **push, and reconcile**. The verdict on the project screen at
|
||
|
|
privacyllc.dev is read out of `ClaudeReport.md` in the pushed repository, so a
|
||
|
|
round whose report is committed but not pushed — or pushed but not reconciled —
|
||
|
|
leaves a stakeholder reading the previous round's judgment with no indication
|
||
|
|
that a newer one exists. The rest of the cycle is in `docs/WORK_CYCLE.md`.
|