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