docs: record Round 1 QA and the Batch 01 log entry

Round 1 was partial and says so, pass by pass, with each not-run row carrying
why. It found two defects that 70 green unit tests could not: the primary
button crashing the app on a second tap, and two API 34 date calls on the
recalculation path that would crash every device below Android 14.

Neither is filed in the tracker. Both were found and fixed inside the batch that
introduced them, before any build left this repository, and an issue closed by
the commit that created the code would be bookkeeping rather than a record.

New standing gap: nothing has been run on a device at this project's own minSdk.
Lint is a good guard for that and is not a substitute for one run on API 26.
This commit is contained in:
null 2026-08-18 03:01:33 -05:00
parent f5e9fbe53c
commit eb3bebcf2f
3 changed files with 121 additions and 33 deletions

View File

@ -32,6 +32,61 @@ written and stay true. It is exempt from review for the same reason a receipt is
## Entries
### 2026-08-18 — Batch 01 foundation: seven of nine issues, and three guards that were wrong
Room, DataStore, the repository layer, period CRUD end to end on a device, and
the module boundary guard. #3 to #7 closed; #8 (branding) and #9 (webhook) are
the two that need a person rather than an agent.
**What was built.** `core/database` with the four entities from §10, DAOs
returning `Flow`, and the schema exported and committed. `core/datastore` for
settings, deliberately separate from the cycle database so Delete My Data cannot
reset a privacy choice the user made. `core/data` as the seam: domain types out,
cycles derived rather than stored, the forecast a function of the data instead
of a field somebody has to refresh. Hilt wiring and a working Today surface that
says "Batch 01 · working surface" so nobody mistakes it for the designed screen.
**Three guards were written, and all three were wrong at first.** This is the
day's real lesson and it is worth carrying forward:
1. `SchemaTest` looked like a Room schema-drift guard. Room regenerates the
schema export during compilation, so both sides of every comparison agreed by
construction — adding a column without bumping the version left it green.
`scripts/schema-guard.sh` asks git instead, which Room cannot overwrite.
2. `checkModuleBoundaries` reported "7 modules checked, no violations" while
checking nothing: the root project is configured before its subprojects, so
every configuration read as empty. Caught by `prove-guard.sh` on its first
run. Collection moved to `afterEvaluate`, and the task now throws rather than
passing when it examined nothing.
3. The repository let `SQLiteConstraintException` escape into
`viewModelScope.launch`, so **tapping the primary button twice killed the
app** — found by hand on an emulator, with 70 unit tests green.
Each was caught by actually trying to break it. None would have been caught by
reading the code, and two of them would have been trusted for months.
**Two more bugs came from wiring the guard into `./gradlew check`**, which ran
Android lint for the first time: `LocalDate.ofInstant` and `LocalDate.EPOCH` are
API 34 and `minSdk` is 26. Both sit on the recalculation path — a crash on every
device below Android 14, invisible to the unit tests and to an API 36 emulator.
**QA.** Round 1 recorded as partial in `docs/qa/`. Passes A and B green, C and D
and H partial, the rest not run and each saying why.
- **Closed:** #3, #4, #5, #6, #7
- **Next action:** Batch 02 — replace `BaselinePredictionEngine` with the engine
§12 specifies: recency weighting, a robust centre, variability-driven windows,
trend detection, and "not yet" as a real conditioning step rather than a floor
on the window. The §51 acceptance tests already exist and must keep passing
against the new engine, which is what makes the replacement demonstrably
better rather than merely different. Before that, one cheap thing worth doing:
run the app once on a device at `minSdk` 26, because nothing here ever has.
- **Blockers:** None for code. #8 needs the three branding marks drawn — an
agent must not fake them, so the project card shows an initials tile until
somebody does. #9 needs the Command Center's webhook URL and secret, which are
not readable from this machine; until it is registered an opened `P0` raises
no alert at all.
### 2026-08-18 — Template adopted; Kotlin/Compose skeleton builds
Period went from a bare directory holding one specification file to a git

View File

@ -13,40 +13,71 @@ Review trigger: Any QA round run
> like a pass that succeeded, and that is how untested code ships believing it
> was tested.
## No round has been run
## Round 1 — 2026-08-18 at `adc5075`, partial
There is nothing to report yet, and this section says so rather than leaving the
file looking like a round that found nothing. The first round can only happen
once there is an app to run — the passes in
[`ClaudeQAPlan.md`](ClaudeQAPlan.md) all require a build, and Batch 01 is what
produces one.
Not a full round. Batch 01 produced the first build, and this covered the two
passes that build could support. Recorded as partial rather than left out,
because a pass omitted from a report reads exactly like a pass that succeeded.
The table below is the shape each round fills in. It is deliberately left with
no rows rather than pre-filled with "Not run", because a round that never
happened and a pass that was skipped are different facts.
## Round N — YYYY-MM-DD at `sha`
**Environment:** emulator `PeriodQA`, API 36 (`sdk_gphone64_x86_64`), Pixel 6
profile, debug build.
| Pass | Result | Notes |
| --- | --- | --- |
| A — First run | | |
| B — Core loop | | |
| C — Failure paths | | |
| D — Persistence and migration | | |
| E — Forecast under hard histories | | |
| F — Notification privacy on a lock screen | | |
| G — Accessibility | | |
| H — Data ownership and leakage | | |
| A — First run | **Pass** | Clean install, cold start, empty state reads correctly ("No forecast yet", "No periods logged yet"), all four tabs reachable. No notification permission prompt yet — none is requested until Batch 05. |
| B — Core loop | **Pass** | Logged a period; cycle day, countdown, window and confidence appeared. Edited the start date twice; forecast moved from 15 Sep to 13 Sep and the record was marked `edited`. Deletion covered by instrumented test rather than by hand. |
| C — Failure paths | **Partial** | Only one path exercised, and it found a crash — see below. Airplane mode, denied permissions and a killed process mid-entry were not tried. |
| D — Persistence and migration | **Partial** | Covered by an instrumented test that closes and reopens a file-backed database, which is what a force-stop does. A real force-stop, a reboot and an update-over-install were not tried. |
| E — Forecast under hard histories | **Not run** | The §51 cases pass as unit tests; none has been driven through the UI, which is what this pass is for. |
| F — Notification privacy on a lock screen | **Not run** | Nothing sends a notification yet — Batch 05. |
| G — Accessibility | **Not run** | TalkBack, font scaling and greyscale legibility untried. The working surface is not the designed screen, so this is worth deferring to Batch 03 rather than testing a screen that is about to be replaced. |
| H — Data ownership and leakage | **Partial** | Delete-all covered by an instrumented test. Export, app lock and the built-artifact inspection do not exist yet. |
Results are `Pass`, `Partial`, `Blocked` or `Not run` — and the last three carry
what stopped them and the issue number, never a blank.
### What pass C found
Tapping **Started today** twice on the same day killed the app:
```text
FATAL EXCEPTION: main
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed:
period_records.startDate
```
Not filed as an issue: it was found and fixed inside the same batch, before any
build left this repository, and a tracker issue closed by the commit that
introduced the code would be bookkeeping rather than a record. It is written
down here, in the commit that fixed it, and in
[`../architecture/README.md`](../architecture/README.md), with four regression
tests pinning the behaviour.
The interesting part is not the crash. It is that **the first thing tried by
hand broke immediately**, on the app's primary button, with 70 unit tests
green — which is the argument for pass C existing at all.
### What lint found, that no pass would have
Wiring the boundary guard into `./gradlew check` ran Android lint for the first
time, on `f5e9fbe`:
```text
NewApi: java.time.LocalDate#ofInstant requires API 34 (minSdk is 26)
NewApi: java.time.LocalDate#EPOCH requires API 34 (minSdk is 26)
```
Both on the recalculation path — a crash on every device below Android 14.
Invisible to the unit tests and invisible to an API 36 emulator. Fixed, and
worth recording as a standing gap below: **this project has no test on a device
at its own `minSdk`.**
## Standing gaps
Things no round has ever covered, carried forward until they are. This list
existing is not a failure; it not existing while the gaps do is.
- **Everything.** No build exists yet.
- **No device at `minSdk`.** Everything so far ran on API 36. The two NewApi
bugs above would have crashed on Android 8 through 13 and nothing except lint
could see them. Lint is a good guard and it is not a substitute for one run on
an old API level.
- **Physical-device coverage is undecided.** Passes F and G need a real device
with a lock screen and TalkBack; which device that is has not been chosen, and
an emulator is not a substitute for either.

View File

@ -19,17 +19,19 @@ Review trigger: Any QA round run
## Current run-state
- **Last QA round:** None — no round has been run
- **Last verified build SHA:** none
- **Last tested device / environment:** none
- **Overall status:** No QA round has been run, because there is nothing to run
one against yet — this repository currently holds the product specification,
the documentation tree and a Kotlin project skeleton, and the first buildable
version of the app is what Batch 01 produces. The two things that will decide
whether this product is trustworthy are already known and already testable in
principle: whether the forecast stays personal for a user whose cycle is not
28 days, and whether the lock screen keeps quiet in Discreet mode. Neither has
been measured.
- **Last QA round:** Round 1 — 2026-08-18, partial (passes A and B; C, D and H partial)
- **Last verified build SHA:** `adc5075`
- **Last tested device / environment:** emulator `PeriodQA`, API 36, Pixel 6 profile, debug build
- **Overall status:** The Batch 01 foundation works end to end on a device — log
a period, see a forecast with an honest window and confidence, edit it and
watch the forecast move — and the first partial QA round found two defects
that 70 passing unit tests could not: tapping the app's primary button twice
crashed it, and two date APIs on the recalculation path would have crashed
every device below Android 14. Both are fixed, with regression tests. That
ratio is the honest summary of where this project stands: the logic is well
covered and the product has barely been used, so the next round matters more
than the last one. Nothing has been tested at the minimum Android version this
app claims to support.
## Open defects