From eb3bebcf2f90c0ef9a7bde5421c9d560832ca1f8 Mon Sep 17 00:00:00 2001 From: null Date: Tue, 18 Aug 2026 03:01:33 -0500 Subject: [PATCH] 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. --- docs/history/DEVELOPMENT_LOG.md | 55 ++++++++++++++++++++++++ docs/qa/ClaudeQACoverage.md | 75 +++++++++++++++++++++++---------- docs/qa/ClaudeReport.md | 24 ++++++----- 3 files changed, 121 insertions(+), 33 deletions(-) diff --git a/docs/history/DEVELOPMENT_LOG.md b/docs/history/DEVELOPMENT_LOG.md index 7b6fd5b..6aed7ed 100644 --- a/docs/history/DEVELOPMENT_LOG.md +++ b/docs/history/DEVELOPMENT_LOG.md @@ -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 diff --git a/docs/qa/ClaudeQACoverage.md b/docs/qa/ClaudeQACoverage.md index d67596c..0eb1195 100644 --- a/docs/qa/ClaudeQACoverage.md +++ b/docs/qa/ClaudeQACoverage.md @@ -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. diff --git a/docs/qa/ClaudeReport.md b/docs/qa/ClaudeReport.md index ddd71ae..244e314 100644 --- a/docs/qa/ClaudeReport.md +++ b/docs/qa/ClaudeReport.md @@ -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