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:
+30
-19
@@ -11,11 +11,12 @@ Create the Gradle project in `android/` with every module of §6.2, the version
|
||||
5. SPEC §4 FR-14 Analytics (AC-14.2, AC-14.7, AC-14.10) and FR-19 Localisation and formatting.
|
||||
6. SPEC §14.2 Room entities (only to know what `:data` will hold; M5 writes it).
|
||||
7. SPEC §11.2 (row M2b), §11.3 (notes M2b, M7x, M8), §14.7 Module layout and ownership map, §14.8.
|
||||
8. Files: `contracts/test-vectors/*.json`, `android/gradle.properties`, `android/gradle/gradle-daemon-jvm.properties`, `tools/gradle-serial.sh`, `tools/dependency-report/latest.py`, `tools/git-hooks/pre-commit`, `CLAUDE.md` (BUILD AND VERIFY, ARCHITECTURE RULES, ANTI-PATTERNS, THE PHONE).
|
||||
8. SPEC §6.2 "Feature convention (D-017)" and "Fakes" paragraphs, §14.5.4 (analytics package, `resolveRange` exception), §14.7 "Module shells" and "Strings" paragraphs; `docs/decisions.md` D-004, D-017, D-018, D-020.
|
||||
9. Files: `contracts/test-vectors/*.json`, `android/gradle.properties`, `android/gradle/gradle-daemon-jvm.properties`, `tools/gradle-serial.sh`, `tools/dependency-report/latest.py`, `tools/git-hooks/pre-commit`, `CLAUDE.md` (BUILD AND VERIFY, ARCHITECTURE RULES, ANTI-PATTERNS, THE PHONE).
|
||||
|
||||
## 3. Already established
|
||||
- Kotlin 2.x latest stable; Compose with Material 3; `minSdk` 36; `targetSdk` and `compileSdk` the latest stable API; Room with KSP; Hilt; Ktor client (OkHttp engine) with kotlinx.serialization; WorkManager; kotlinx-datetime; Coil; Vico or Canvas charts (decided by M6); FCM only in `:adapter:fcm`; version catalog `android/gradle/libs.versions.toml`; Gradle daemon on JDK 25 (pinned, existing files); bytecode target Java 17 (§6.2).
|
||||
- Screenshot tests use Roborazzi or AGP Compose Preview Screenshot Testing, whichever is stable at implementation time, recorded in decisions (§6.2).
|
||||
- Screenshot tests use Roborazzi or AGP Compose Preview Screenshot Testing, whichever is stable at implementation time; M2b chooses, wires it into `check` and reports the choice with its reasons; the orchestrator records it as D-010 (§6.2).
|
||||
- Modules and allowed edges exactly as the §6.2 table; features never depend on `:data` or `:domain`; `:core:fakes` only for tests, screenshot tests and debug previews (§6.2, CLAUDE.md Layering).
|
||||
- The architecture check is a Gradle task in `check`, owned by M2b; it fails on any other dependency edge and on `java.time`, `java.io` or `android.*` imports in `:core:model` and `:domain` (§6.2).
|
||||
- `:core:model` package `se.hagstedt.kvitt.model` holds the §14.2.4 types and interfaces verbatim; domain input/output types in `se.hagstedt.kvitt.model.money` (§14.5.1); `:domain` package `se.hagstedt.kvitt.domain` with the §14.5.2 functions and §14.5.4 analytics (§14.5).
|
||||
@@ -24,19 +25,29 @@ Create the Gradle project in `android/` with every module of §6.2, the version
|
||||
- Strings live in `values/strings.xml` (English fallback) and `values-sv/strings.xml`; a lint or test in the gate fails on a key missing from either file (AC-19.1).
|
||||
- Application ids: release `se.hagstedt.kvitt`, debug `se.hagstedt.kvitt.debug`, instrumented tests `se.hagstedt.kvitt.<module>.test` (CLAUDE.md THE PHONE).
|
||||
- No mocking libraries, no `!!`, no `@Suppress`, no lint-baseline additions (CLAUDE.md).
|
||||
- M1 has landed (`c6df2ab`); its readings are explicit in SPEC v0.2: domain functions receive only non-deleted rows; analytics vector keys per §14.5.5; `bp_out_of_range` means above 10000; `resolveRange` edge cases per §14.5.4 (how `CUSTOM` with a null date signals `validation_failed` is for M2b to fix and report).
|
||||
- M1 has landed (`c6df2ab`); its readings are explicit in SPEC v0.2: domain functions receive only non-deleted rows; analytics vector keys per §14.5.5; `bp_out_of_range` means above 10000; `resolveRange` edge cases per §14.5.4 (`CUSTOM` with a null date throws a typed `IllegalArgumentException` subclass declared in `:domain`, named by M2b, D-018).
|
||||
- `:core:model/**` and `:domain/**` become contract files once landed (§14.8).
|
||||
|
||||
## 4. Owned paths
|
||||
From SPEC §14.7, verbatim:
|
||||
- `android/settings.gradle.kts`, `android/build.gradle.kts`, `android/gradle.properties`, `android/gradle/**`, `android/build-logic/**` (owner M2b; after landing: "later plans may add catalog entries and module includes (rebase resolves)").
|
||||
- `android/core/model/**`, `android/domain/**` (owner M2b; after landing: "contract-change plans only").
|
||||
- `android/core/fakes/**` (owner M2b; after landing: "M7x add files only under `fakes/<feature>/`").
|
||||
- `android/app/**` up to and including "the M2b shell" (the row `android/adapter/fcm/**`, `android/app/**` (after the M2b shell) | M8).
|
||||
From SPEC §14.7 (v0.3):
|
||||
- `android/settings.gradle.kts`, `android/build.gradle.kts`, `android/gradle.properties`, `android/gradle/**`, `android/build-logic/**` (after landing: "later plans may add catalog entries and module includes (rebase resolves)").
|
||||
- `android/core/model/**`, `android/domain/**` (after landing: "contract-change plans only").
|
||||
- `android/core/fakes/**` (after landing: "M7x add files only under `fakes/<feature>/`").
|
||||
- `android/app/**` up to and including "the M2b shell" (afterwards M8).
|
||||
- Module shells (D-004), which pass to their owners as soon as M2b lands: build file, manifest and empty string files of `:core:design` (M6), `:data` (M5), `:adapter:fcm` (M8), and of every `:feature:*` (M7a to M7f) with one empty entry composable per feature.
|
||||
|
||||
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator.
|
||||
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator. Never edit `SPEC.md` or `docs/decisions.md`: put proposed decision entries in the report (CLAUDE.md › Orchestration).
|
||||
|
||||
Open points the orchestrator must settle in the start prompt's notes (stop and ask if they are not settled): (a) "every module of §6.2" means build files in `android/core/design/`, `android/data/`, `android/adapter/fcm/` and `android/feature/*/`, which §14.7 gives to M6, M5, M8 and M7x: whether M2b creates them as empty module shells that ownership then passes on; (b) where the theme of the "empty themed screen" lives, given `:core:design` belongs to M6; (c) which module holds the "string resource files for both languages" M2b creates; (d) which screenshot-test tool, and whether M2b wires it into `check` (needs a decision id); (e) whether feature modules use Hilt (`hiltViewModel`) or receive repositories as parameters, and how cross-feature navigation is expressed, since the feature convention plugin in `build-logic` fixes it for M7a to M7f; (f) the package of the §14.5.4 analytics types (`:core:model` money package or `:domain`); (g) what an "empty scenario" is for repositories whose state has required fields (for example `HomeState.me`).
|
||||
Resolved open points (SPEC v0.3; the start prompt may repeat them):
|
||||
- (a) M2b creates the module shells above, including one empty entry composable per feature module (D-004).
|
||||
- (b) The empty themed screen uses `MaterialTheme` with Material 3 defaults inside `:app`'s shell; `KvittTheme` in `:core:design` is M6's.
|
||||
- (c) Strings: `:app` holds only the app name (`values/strings.xml` and `values-sv/strings.xml`); `:core:design` will hold shared strings, each feature its own; lint `MissingTranslation` is an error in every module, set once in the convention plugins (D-017).
|
||||
- (d) Screenshot tool: M2b chooses Roborazzi or AGP Compose Preview Screenshot Testing, wires it into `check`, and reports the choice with reasons; the orchestrator records it as D-010.
|
||||
- (e) Feature convention (D-017, §6.2): one composable entry point per screen taking a `hiltViewModel()`-provided ViewModel and navigation as plain lambdas; features never reference other features or the `NavHost`; `:app` owns the `NavHost`; repositories are injected through the `:core:model` interfaces, bound in `:app` to `:data` (fakes only in test and debug-preview source sets). The feature convention plugin applies Hilt and the Hilt navigation Compose dependency.
|
||||
- (f) The §14.5.4 analytics types live in `:core:model`, package `se.hagstedt.kvitt.model.analytics`; the functions in `:domain` (D-017).
|
||||
- (g) Empty scenario (D-018): household complete (two named users, no pictures), no accounts, exactly the ten default categories of AC-7.2, no expenses, settlements, templates, rejections or pending changes.
|
||||
- Also (D-018): `resolveRange` with `CUSTOM` and a null start or end throws a typed subclass of `IllegalArgumentException` declared in `:domain`; callers map it to `validation_failed`. Name the class in the report.
|
||||
- Also (D-020): no font is bundled; the app uses the Material 3 default typeface.
|
||||
|
||||
## 5. Deliverables
|
||||
- `android/settings.gradle.kts`, `android/build.gradle.kts`: the project with every module of §6.2.
|
||||
@@ -46,17 +57,17 @@ Open points the orchestrator must settle in the start prompt's notes (stop and a
|
||||
- `android/build-logic/**`: convention plugins (pure JVM module, Android library, feature module, application) and the architecture check task wired into `check`.
|
||||
- `android/core/model/**`: §14.2.4 and §14.5.1 types and interfaces, verbatim, plus unit tests for enum wire values and `CategoryIcons.ALL`.
|
||||
- `android/domain/**`: §14.5.2 functions and §14.5.4 analytics; a vector test runner reading `../contracts/test-vectors/*.json` for all seven files.
|
||||
- `android/core/fakes/**`: one fake per repository interface of §14.2.4 driven by a scenario type, with an empty scenario each; fakes compute derived values through `:domain`.
|
||||
- `android/core/fakes/**`: one fake per repository interface of §14.2.4 driven by a scenario type, with the empty scenario of resolved point (g) each; fakes compute derived values through `:domain`.
|
||||
- `android/app/**`: `Application` class, `MainActivity` showing an empty themed screen, debug `applicationIdSuffix` `.debug`, manifest.
|
||||
- Module shells for `:core:design`, `:data`, `:adapter:fcm`, `:feature:{onboarding,home,account,expense,settle,analytics,settings}` (per open point a).
|
||||
- `values/strings.xml` and `values-sv/strings.xml` with the app name (per open point c), and the missing-translation check (Android lint `MissingTranslation` as an error, or a test) in `check`.
|
||||
- Screenshot-test tooling wired into `check` (per open point d) with one screenshot of the empty screen in light and dark.
|
||||
- `docs/decisions.md` entry for the screenshot tool only if the start prompt grants a decision id and the path (the file is not owned by M2b; otherwise put the text in the report as a contract request).
|
||||
- Module shells for `:core:design`, `:data`, `:adapter:fcm`, `:feature:{onboarding,home,account,expense,settle,analytics,settings}`, one empty entry composable per feature (D-004).
|
||||
- `android/app/src/main/res/values/strings.xml` and `values-sv/strings.xml` with the app name only, and lint `MissingTranslation` as an error in every module, in `check` (D-017).
|
||||
- Screenshot-test tooling wired into `check` (resolved point d) with one screenshot of the empty screen in light and dark.
|
||||
- The proposed D-010 text for the screenshot tool, in the report (the orchestrator has it recorded).
|
||||
|
||||
## 6. Steps
|
||||
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m2b -b agent-m2b main`, then in `.claude/worktrees/agent-m2b`: `git reset --hard main`. Copy `android/local.properties` from the main checkout if it exists (`cp /Users/jhag/projects/kvitt/android/local.properties android/`); if it does not, create it with the single line `sdk.dir=/opt/homebrew/share/android-commandlinetools`. Never overwrite an existing one, never commit it. Run `tools/install-git-hooks.sh`.
|
||||
2. Read §2 in order. Confirm `contracts/test-vectors/` exists on `main`; if not, stop and report.
|
||||
3. Versions ("latest stable" in §6.2): resolve from official sources and record each with its source: Kotlin (kotlinlang.org releases), Android Gradle Plugin and the latest stable `compileSdk`/`targetSdk` API level (developer.android.com release notes), Gradle wrapper (gradle.org releases; must support JDK 25 daemon and the AGP chosen), Compose BOM, Material 3 (developer.android.com/jetpack/androidx/releases), KSP matching the Kotlin version, Room, Hilt, Ktor, kotlinx.serialization, kotlinx.coroutines, kotlinx-datetime, WorkManager, DataStore, Coil, Roborazzi or the AGP screenshot plugin. Check whether the AGP Compose Preview Screenshot Testing plugin is stable; report the finding for open point (d).
|
||||
3. Versions ("latest stable" in §6.2): resolve from official sources and record each with its source: Kotlin (kotlinlang.org releases), Android Gradle Plugin and the latest stable `compileSdk`/`targetSdk` API level (developer.android.com release notes), Gradle wrapper (gradle.org releases; must support JDK 25 daemon and the AGP chosen), Compose BOM, Material 3 (developer.android.com/jetpack/androidx/releases), KSP matching the Kotlin version, Room, Hilt, Ktor, kotlinx.serialization, kotlinx.coroutines, kotlinx-datetime, WorkManager, DataStore, Coil, Roborazzi or the AGP screenshot plugin. Check whether the AGP Compose Preview Screenshot Testing plugin is stable; report the finding for resolved point (d).
|
||||
4. Generate the Gradle wrapper with the chosen version; keep `android/gradle.properties` and `android/gradle/gradle-daemon-jvm.properties` content (JDK 25 pin, auto-download off).
|
||||
5. Write `build-logic` convention plugins and the architecture check (allowed edges per the §6.2 table; forbidden imports `java.time`, `java.io`, `android.*` in `:core:model` and `:domain`); add a failing-case test of the check if `build-logic` supports it.
|
||||
6. Write `:core:model` exactly from §14.2.4 and §14.5.1 (no added fields). Write `:domain` test-first against all seven vector files, then implement §14.5.2 and §14.5.4.
|
||||
@@ -87,10 +98,10 @@ Where in the flow: after the gate is green (step 10). The orchestrator relays th
|
||||
- Branch `agent-m2b`, head commit hash after rebasing on `main`.
|
||||
- Gate output: BUILD line, actionable tasks line, the names of the architecture-check, lint and screenshot tasks that ran, unit test counts from fresh XML (per module).
|
||||
- Versions chosen (Kotlin, AGP, Gradle, compile/target SDK, every catalog entry) with the official source; dependency-report lines.
|
||||
- Screenshot tool chosen and why (and the decisions text if no decision id was granted).
|
||||
- How each open point (a) to (g) was resolved, as given in the start prompt.
|
||||
- Screenshot tool chosen and why, as the proposed D-010 text.
|
||||
- How each resolved point (a) to (g) was applied; the name of the `resolveRange` exception class.
|
||||
- `installDebug` result on the phone, or "outstanding".
|
||||
- Numbering used (decision ids, spec version) or `TODO(spec)` occurrences.
|
||||
- Proposed decision entries and spec text, never written to `SPEC.md` or `docs/decisions.md` by this agent; or `TODO(spec)` occurrences.
|
||||
- Contract requests, open questions, spec gaps (with section numbers).
|
||||
|
||||
## 11. Numbering
|
||||
|
||||
Reference in New Issue
Block a user