SPEC v0.3: orchestrator resolutions of the plans' open points

Records D-004 and D-015 to D-025: shells and ownership, server seams
(router middleware, rate_limit layer, tasks::start, AuditWriter and PushQueue
traits, next_occurrence_date in kvitt-domain, M3 owns recurring generation,
M4 lands after M3), OpenAPI parsed-value equality, feature convention and
strings, empty fake scenario and resolveRange exception, shared ledger rows and
editor fields in :core:design, no bundled font, pruning at 04:00, development
infrastructure as M2c-dev built it, M2c-pi allowances, release keystore,
production import procedure. Plans and prompts close their open points;
HANDOFF rewritten to the real state; CLAUDE.md: implementation agents never
edit SPEC.md or docs/decisions.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 21:23:38 +02:00
parent c885aba313
commit 76ee957f41
45 changed files with 501 additions and 342 deletions
+12 -10
View File
@@ -7,14 +7,17 @@ Assemble the app: `:app` navigation and deep links, Hilt bindings from the `:cor
1. SPEC §6.2 Android app (module table, the paragraph on the architecture check) and §13.2 Navigation model.
2. SPEC §14.6 Push payload v1, "Phone handling" (items 1 to 4).
3. SPEC §4 FR-2 AC-2.6, AC-2.9, AC-2.10; FR-3 AC-3.3, AC-3.4; FR-5 AC-5.1; FR-13 Push notifications (AC-13.5 to AC-13.8); FR-15 AC-15.3; FR-16 In-app update; §13.3 system notification copy table.
4. SPEC §8.2 (token never logged), §8.5 Transport, §8.8 Phone hardening, §10.5.3 (the `kvitt.tls.pinnedRoot` property), §10.6 (version code and name properties, release signing keys), §10.7 (`google-services.json`).
4. SPEC §8.2 (token never logged), §8.5 Transport, §8.8 Phone hardening, §10.5.3 (the `kvitt.tls.pinnedRoot` property), §10.6 (version code and name properties, release signing keys), §10.7 (`google-services.json` in `infra/secrets/`, copied by `infra/dev/copy-google-services.sh`).
5. SPEC §14.4.8 Epoch recovery and re-pairing (the `Unrecognised` path), §14.2.4 (`SessionState`, `UpdateState`).
6. SPEC §10 intro "Development first (D-001)" and `infra/dev/README.md` (how to run and bootstrap the development server).
7. SPEC §11.2 (row M8), §11.3 (note M8), §14.7.
8. Files: `android/app/**`, `android/build-logic/**`, `android/gradle/libs.versions.toml`, the public APIs of `android/data/**` (its Hilt modules), `android/feature/*/**` (their screen entry points), `android/core/model/**`, `docs/ux/strings.csv` (notification copy), `docs/decisions.md` D-001, `CLAUDE.md` (THE PHONE, THE OWNER, BUILD AND VERIFY).
6. SPEC §10 intro "Development first (D-001)" and `infra/README.md` (dev stack section) and the scripts in `infra/dev/` (how to run and bootstrap the development server).
7. SPEC §11.2 (row M8), §11.3 (note M8), §14.7, §6.2 "Feature convention (D-017)"; `docs/decisions.md` D-017, D-022.
8. Files: `android/app/**`, `android/build-logic/**`, `android/gradle/libs.versions.toml`, the public APIs of `android/data/**` (its Hilt modules), `android/feature/*/**` (their screen entry points), `android/core/model/**`, `docs/ux/strings.csv` (notification copy, which lives in `:core:design`), `docs/decisions.md` D-001, `CLAUDE.md` (THE PHONE, THE OWNER, BUILD AND VERIFY).
## 3. Already established
- Only `:app` sees every module and binds the `:data` repositories to the `:core:model` interfaces through Hilt; features never see `:data` or `:domain` (§6.2, CLAUDE.md Layering). M8 is the only plan that edits `:app` after M2b (§11.3).
- Feature convention (D-017): each feature exposes one composable entry point per screen with a `hiltViewModel()`-provided ViewModel and navigation as plain lambdas; `:app` owns the `NavHost` and connects every lambda to a route; fakes are bound only in test and debug-preview source sets.
- Strings (D-017): `:app` holds only the app name; the notification copy and the channel name come from `:core:design`'s shared strings.
- `google-services.json` is in `infra/secrets/` of the main checkout (M2c-dev, D-022); M8 copies it into `android/app/` (git-ignored) with `infra/dev/copy-google-services.sh`.
- Navigation: bottom navigation Home (Översikt), Analytics (Statistik), Settings (Inställningar); FAB "Add expense" on Home and Account detail; account detail, editors, settle up, details and settings sub-screens are pushed screens with back; the expense editor is full screen (§13.2).
- Deep links: `kvitt://invite?server=…&code=…` and `https://icehack.asuscomm.com:8443/i/<CODE>` (unverified link) open S-1 with both fields filled (AC-2.6). Only `MainActivity` and the FCM service are exported (§8.8).
- After pairing: full sync, then the `POST_NOTIFICATIONS` request (AC-2.9); partner pairing goes through S-2 before Home (AC-2.10).
@@ -32,20 +35,20 @@ From SPEC §14.7, verbatim: `android/adapter/fcm/**`, `android/app/**` (after th
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator.
Note: network security config (§8.5), manifest hardening (§8.8) and release signing and version properties (§10.6) are not named in §11.2's M8 row, but they live in `:app`, which only M8 edits (§11.3); they are listed as deliverables here. The orchestrator confirms or reassigns them in the start prompt.
Network security config (§8.5), manifest hardening (§8.8), release signing and version properties (§10.6) are M8's (§11.2 row M8, v0.3). Never edit `SPEC.md` or `docs/decisions.md`: put proposed decision entries in the report.
## 5. Deliverables
- `android/app/build.gradle.kts`: dependencies on every module, Hilt, Firebase (via `google-services.json`), version properties, release signing config read from `local.properties`.
- `android/app/src/main/AndroidManifest.xml`: `MainActivity` with launcher and deep-link filters, FCM service from `:adapter:fcm`, `POST_NOTIFICATIONS`, `REQUEST_INSTALL_PACKAGES`, backup disabled, data extraction rules.
- `android/app/src/main/res/xml/network_security_config.xml` (release) and the debug variant; `data_extraction_rules.xml`.
- `android/app/src/main/kotlin/se/hagstedt/kvitt/**`: `Application` (Hilt), `MainActivity`, navigation graph, deep-link parsing, Hilt modules binding interfaces to `:data`, push event handling and notification building, revoke and `Unrecognised` routing, notification-permission request after pairing.
- `android/app/src/main/res/values/strings.xml`, `values-sv/strings.xml`: notification copy and channel name from `docs/ux/strings.csv`.
- `android/app/src/main/res/values/strings.xml`, `values-sv/strings.xml`: the app name only (from M2b's shell); notification text is built from `:core:design` strings.
- `android/adapter/fcm/**`: `FirebaseMessagingService`, token registration, mapping to `PushEvent`, unit tests for the mapping.
- Unit tests in `:app`: deep-link parsing, notification text building for each §14.6 type and the generic fallback, notification ids.
- An end-to-end run report in the plan report (no file): pairing and the offline test on the phone.
## 6. Steps
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m8 -b agent-m8 main`, then in `.claude/worktrees/agent-m8`: `git reset --hard main`. Copy `android/local.properties` and `android/app/google-services.json` from the main checkout (never commit them; add `sdk.dir` to `local.properties` if missing). Run `tools/install-git-hooks.sh`.
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m8 -b agent-m8 main`, then in `.claude/worktrees/agent-m8`: `git reset --hard main`. Copy `android/local.properties` from the main checkout, then run `infra/dev/copy-google-services.sh /Users/jhag/projects/kvitt` to place `android/app/google-services.json` from the main checkout's `infra/secrets/` (never commit either; add `sdk.dir` to `local.properties` if missing). Run `tools/install-git-hooks.sh`.
2. Read §2 in order. Confirm M3, M4, M5, M7a to M7f and M2c-dev have landed; if not, stop and report.
3. Run `python3 tools/dependency-report/latest.py`; record the lines. Versions: Firebase BoM and the Google services Gradle plugin at latest stable (firebase.google.com release notes); Hilt navigation and Navigation Compose at latest stable; confirm current FCM Android and `PackageInstaller` usage from official docs; record each.
4. Hilt modules and the navigation graph; wire every feature screen; deep links.
@@ -53,7 +56,7 @@ Note: network security config (§8.5), manifest hardening (§8.8) and release si
6. Revoke wipe, `Unrecognised` routing, notification permission after pairing, update banner wiring.
7. Manifest hardening, network security configs, version properties, release signing config (no keystore needed for debug).
8. Run the gate's build part: `PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ../tools/gradle-serial.sh check` from `android/`.
9. Development server: start it per `infra/dev/README.md` with an image built from current `main` (no Gradle build running while Docker builds), reset its dev data, run `kvitt-server bootstrap`, note the code (never log it to a committed file).
9. Development server: start it per `infra/README.md` (dev stack section) and the scripts in `infra/dev/` with an image built from current `main` (no Gradle build running while Docker builds), reset its dev data, run `kvitt-server bootstrap`, note the code (never log it to a committed file).
10. Phone (only if the start prompt names this plan as the one on the phone): `/opt/homebrew/share/android-commandlinetools/platform-tools/adb shell pm list packages | grep se.hagstedt.kvitt` to confirm ids; `PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH tools/gradle-serial.sh installDebug`. The debug build must not replace a build holding the owner's real data without asking.
11. End-to-end test with the owner (HUMAN, §8): pairing with the bootstrap code and the Mac's LAN URL; the agent then creates a partner invite and accepts it with `curl` as a scripted partner device on the dev server, so the household is complete (FR-5); the owner adds an expense offline, reconnects; the agent confirms the expense reached the server (pull with the scripted partner's token) and pulls the app's sync state over USB (`adb shell run-as se.hagstedt.kvitt.debug …`) to show pending count 0. If push is configured, the agent also posts an expense as the scripted partner and asks the owner whether a notification appeared.
12. Commit on `agent-m8`, `git rebase main`, rerun the gate. Never merge, push or remove the worktree.
@@ -77,9 +80,8 @@ Where in the flow: step 11, after the debug build is installed and the dev serve
- Branch `agent-m8`, head commit hash after rebasing on `main`.
- Gate output: BUILD line, actionable tasks line, test counts from fresh XML; the end-to-end result (pair, offline pending, synced, pending count 0), with what the agent checked itself.
- Versions chosen with sources; dependency-report lines.
- Whether the start prompt confirmed the network security config, hardening and signing deliverables.
- Dev server state left behind (running or stopped; scripted partner device name).
- Numbering used or `TODO(spec)` occurrences.
- Proposed decision entries (never written to `SPEC.md` or `docs/decisions.md` by this agent) or `TODO(spec)` occurrences.
- HUMAN steps done and outstanding, worded for relay.
- Contract requests, open questions, spec gaps (with section numbers).