SPEC v0.6: M4 decisions (D-035 to D-039), HANDOFF rewritten
- D-035 unusable mutation_id/entity_type/entity.id fails the whole push with 400; other shape errors per mutation.
- D-036 0 mutations is batch_too_large ("outside 1 to 100").
- D-037 never-synced delete runs full step-8 validation; current on every rejection; created_at kept; canonical ids.
- D-038 FCM retries: one send plus three retries (1/5/25 s), 401 drops the token, other 4xx not retried.
- D-039 pruning strictly older than 90 days; tasks::start returns AppState with the push queue, SqlAuditWriter default.
- Clarifications: step 8 order, negative since, pull read transaction, FCM JWT kid and cache, token clearing only if
unchanged, unusable credentials, scheduler clock jumps, rotate-epoch exit codes, /i/ empty code, image build needs
a C toolchain and CA roots (dev image rebuilt 2026-10-18).
- PLAN-M4 pruning wording, prompts/README status, HANDOFF at bd6f10a.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+18
-25
@@ -3,38 +3,31 @@
|
||||
Living document, rewritten in place (never a second copy). A new orchestrator session reads CLAUDE.md, then this.
|
||||
|
||||
## 1. Where things stand
|
||||
- `main` at `c28c52b` (M5) plus the SPEC v0.5 docs commit from `agent-spec5` once it lands. History: `5e1be38` plans and prompts, `c885aba` M2c-dev, `76ee957` SPEC v0.3, `fad6fc9` M2b, `fdf2093` M2a, `040bf41` SPEC v0.4, `503cee5` M3, `c28c52b` M5.
|
||||
- Landed: M0 spec, M1 contracts and vectors (`c6df2ab`), M2a server skeleton and `kvitt-domain` (`fdf2093`), M2b Android skeleton and `:domain` (`fad6fc9`), M2c-dev infra for development (`c885aba`), SPEC v0.4 (`040bf41`), M3 server core API (`503cee5`), M5 Android data layer (`c28c52b`). With `agent-spec5`: SPEC v0.5 with M3's D-030 to D-034 and M5's D-026 to D-029 and D-040 to D-043 (M5's proposals D-030 to D-033 renumbered to D-040 to D-043; D-035 to D-039 reserved for M4).
|
||||
- `main` at `bd6f10a` (M4) plus the SPEC v0.6 docs commit from `agent-spec6` once it lands. History: `5e1be38` plans and prompts, `c885aba` M2c-dev, `76ee957` SPEC v0.3, `fad6fc9` M2b, `fdf2093` M2a, `040bf41` SPEC v0.4, `503cee5` M3, `c28c52b` M5, `982451d` SPEC v0.5, `bd6f10a` M4.
|
||||
- Landed: M0 spec, M1 contracts and vectors (`c6df2ab`), M2a server skeleton and `kvitt-domain` (`fdf2093`), M2b Android skeleton and `:domain` (`fad6fc9`), M2c-dev infra for development (`c885aba`), M3 server core API (`503cee5`), M5 Android data layer (`c28c52b`), SPEC v0.5 (`982451d`), M4 server sync, audit, push, scheduler (`bd6f10a`). With `agent-spec6`: SPEC v0.6 with M4's D-035 to D-039.
|
||||
- Both gates exist: Android `tools/gradle-serial.sh check` (from `android/`), server `cargo fmt --check && cargo clippy -- -D warnings && cargo test` (from `server/`).
|
||||
- Worktrees in flight under `.claude/worktrees/`: `agent-m4` (M4 server sync, audit, push, scheduler), `agent-spec5` (this docs change: SPEC v0.5).
|
||||
- The owner runs the UX sprint (U1, then U2) in a separate Claude session in `~/orca/workspaces/kvitt/UX-sprint` on branch `UX-sprint`; it commits only under `docs/ux/` and rebases on `main` before landing.
|
||||
- Development stack (M2c-dev): running on the Mac, reachable at `http://192.168.1.210:8080`; the owner confirmed `/health` from the phone on the home Wi-Fi on 2026-10-17. Firebase project `kvitt-1337` with apps `se.hagstedt.kvitt` and `se.hagstedt.kvitt.debug`; service account `kvitt-fcm`; `infra/secrets/fcm-service-account.json` and `infra/secrets/google-services.json` in the main checkout (git-ignored).
|
||||
- Installed on the owner's phone: nothing yet (M2b's `installDebug` is pending).
|
||||
- Worktrees in flight under `.claude/worktrees/`: `agent-m9` (M9 Splitwise importer; decisions D-044 to D-049 reserved for it), `agent-spec6` (this docs change: SPEC v0.6).
|
||||
- The owner runs the UX sprint in a separate Claude session in `~/orca/workspaces/kvitt/UX-sprint` on branch `UX-sprint`; it commits only under `docs/ux/` and rebases on `main` before landing. U1 is committed on `UX-sprint` at `210ab5c` (`docs/ux/`: directions, ia, screens, flows, components, tokens, `strings.csv`); the owner has not yet picked a direction.
|
||||
- Development stack (M2c-dev): running on the Mac with the M4 image, reachable at `http://192.168.1.210:8080`; the owner confirmed `/health` from the phone on the home Wi-Fi on 2026-10-17. The dev image was rebuilt with M4's new crates (reqwest on rustls/aws-lc-rs, jsonwebtoken) on 2026-10-18 and built cleanly. Firebase project `kvitt-1337` with apps `se.hagstedt.kvitt` and `se.hagstedt.kvitt.debug`; service account `kvitt-fcm`; `infra/secrets/fcm-service-account.json` and `infra/secrets/google-services.json` in the main checkout (git-ignored).
|
||||
- Installed on the owner's phone (SM-S942B): the debug app `se.hagstedt.kvitt.debug` built from `main` at `c28c52b`, as of 2026-10-18 (M2b's `installDebug` check is done).
|
||||
|
||||
## 2. Open work (one line each, with who owns it: agent or owner)
|
||||
- Land `agent-spec5` (orchestrator): SPEC v0.5, decisions D-026 to D-034 and D-040 to D-043, PLAN-M8 (M5's contract requests, D-027 follow-up), PLAN-M6 (picture size note), PLAN-M4 (`into_make_service_with_connect_info`), prompts/README status.
|
||||
- Tell `agent-m4` before it lands (orchestrator): D-035 to D-039 are its range; v0.5 asks M4 to add `into_make_service_with_connect_info::<SocketAddr>()` to `serve` (D-032, §11.3); M3's tests are in `tests/core_api/`; generation sets the template's `updated_at` (§9.5); D-033 resume rule.
|
||||
- Land `agent-spec6` (orchestrator): SPEC v0.6, decisions D-035 to D-039, PLAN-M4 pruning wording, prompts/README status.
|
||||
- Image build for the Pi (M2c-pi or M10): since M4 the arm64 server image needs a C toolchain in the build stage (aws-lc-rs) and CA roots in the distroless image (§10.3); confirm both and one FCM send from the shipped image.
|
||||
- D-027 re-stamp (orchestrator assigns: M8 as a contract request on `:data`, or a small `:data` agent): on the first sync attempt after a `clock_skew` rejection the phone re-stamps `updated_at` of each such pending row to its current clock before pushing. Not in `:data` at `c28c52b`.
|
||||
- Picture size (M6, if needed): `:data` always fetches E-11 at 512; a size parameter for the 128 variant is a contract request on `:core:model` and `:data`.
|
||||
- `server/README.md` (server-owned, next server agent): still says M3's tests go in `tests/core_*.rs`; v0.5 §14.7 records the single binary `tests/core_api/`.
|
||||
- Plans (SPEC §11.2 v0.5), in dependency order; prompt `prompts/<id>.md`:
|
||||
- M1 Contracts and vectors: landed (`c6df2ab`).
|
||||
- M2a Server skeleton and `kvitt-domain`: landed (`fdf2093`).
|
||||
- M2b Android skeleton and `:domain`: landed (`fad6fc9`); outstanding: `installDebug` on the phone (owner plugs it in).
|
||||
- M2c-dev Infra for development: landed (`c885aba`); phone `/health` check done (owner, 2026-10-17).
|
||||
- U1 UX directions: owner's session (`UX-sprint`), in progress; owner then picks a direction.
|
||||
- U2 UX design spec: owner's session, after U1 and the pick; owner approves; then the §9.7 colour change goes into the spec (docs agent) before M6.
|
||||
- M3 Server core API: landed (`503cee5`).
|
||||
- M4 Server sync, audit, push, scheduler: agent in flight (`agent-m4`); lands after M3 (done).
|
||||
- M5 Android data layer: landed (`c28c52b`).
|
||||
- Next waves: M9 Splitwise importer after M4; M6 Design system after U2 approved and the §9.7 colour change; M7a to M7f Features after M6, in parallel; M8 App integration after M7 (and M3, M4, M5).
|
||||
- Plans (SPEC §11.2 v0.6), in dependency order; prompt `prompts/<id>.md`:
|
||||
- M1, M2a, M2b, M2c-dev, M3, M4, M5: landed.
|
||||
- U1 UX directions: committed on `UX-sprint` (`210ab5c`); waiting for the owner's pick.
|
||||
- U2 UX design spec: owner's session, after the pick; owner approves; then the §9.7 colour change goes into the spec (docs agent) before M6.
|
||||
- M9 Splitwise importer: agent in flight (`agent-m9`).
|
||||
- Next waves: M6 Design system after U2 approved and the §9.7 colour change; M7a to M7f Features after M6, in parallel; M8 App integration after M7 (and M3, M4, M5).
|
||||
- M2c-pi Infra on the Pi: after M8, before M10 (owner: answer OQ-3, grant the Pi, Home Assistant Pi, Gitea and Drive allowances at the start, D-023).
|
||||
- M10 First release: after M2c-pi, M3, M4, M8 (owner: keystore per §10.6, D-024).
|
||||
- M11 Import history: after M9 and M10.
|
||||
|
||||
## 3. HUMAN steps pending (relay in chat)
|
||||
- Plug the phone in over USB so an agent can run `installDebug` of the debug app (expected: the Kvitt debug app opens to an empty themed screen).
|
||||
- UX direction pick from U1's directions, with comments (owner's UX-sprint session).
|
||||
- UX direction pick from U1's directions in `docs/ux/` on `UX-sprint`, with comments (owner's UX-sprint session).
|
||||
|
||||
## 4. Owner questions (asked, unanswered)
|
||||
- OQ-3 LAN addresses and SSH users of the Kvitt Pi and the Home Assistant Pi; SSH port of the HA Pi; certificate directory there; which SSH add-on (SPEC §12). Needed by M2c-pi only. The only open question (OQ-1, OQ-2, OQ-4 answered: D-001, D-003, §10.9).
|
||||
@@ -43,9 +36,9 @@ Living document, rewritten in place (never a second copy). A new orchestrator se
|
||||
- Nothing.
|
||||
|
||||
## 6. Numbering
|
||||
- Decisions: D-001 to D-034 and D-040 to D-043 used (D-008 and D-009 unused). Reserved: **D-035 to D-039 for M4** (in flight). Next free for anything else: **D-044**.
|
||||
- Spec version: v0.5 written; next **v0.6**.
|
||||
- Server schema version **1**; next **2**. Room schema version **1** (`android/data/schemas/se.hagstedt.kvitt.data.db.KvittDatabase/1.json`, unchanged by M5); next **2**.
|
||||
- Decisions: D-001 to D-043 used (D-008 and D-009 unused). Reserved: **D-044 to D-049 for M9** (in flight). Next free for anything else: **D-050**.
|
||||
- Spec version: v0.6 written; next **v0.7**.
|
||||
- Server schema version **1**; next **2**. Room schema version **1** (`android/data/schemas/se.hagstedt.kvitt.data.db.KvittDatabase/1.json`); next **2**.
|
||||
- Sync protocol **1**, push payload **1**.
|
||||
|
||||
## 7. How the work is run
|
||||
|
||||
+5
-5
@@ -39,11 +39,11 @@ entry and the old one a strike-through plus a pointer. The spec is updated in th
|
||||
| D-032 | 2026-10-17 | Rate-limit keying (§8.6): per device by the hash of the bearer token, no database lookup; a 401 also counts against the client IP; tokens never accepted are refused once the IP is over its limit; `KVITT_TRUSTED_PROXY` re-resolved at most every 60 s; the client IP is the last `X-Forwarded-For` entry from the trusted proxy; `serve` must use `into_make_service_with_connect_info` (being added by M4). | M3 report: keying and proxy handling were unspecified; the limiter must not cost a database query. | SPEC §8.6, §10.4, §11.3 |
|
||||
| D-033 | 2026-10-17 | Resume rule (§9.5): `last_generated_period` moves forward only when the latest past period is at or after `start_period`, so resuming a not-yet-started template keeps its start. | M3 report: the v0.4 rule would skip a future start on resume. | SPEC §9.5 |
|
||||
| D-034 | 2026-10-17 | Storage normalisation: strings stored trimmed, `notes` included; PNG and WebP transparency flattened onto white; `fcm_token` non-empty or null (`""` is `invalid_request`); revoking a device clears its `fcm_token`; archive or unarchive in the state already held returns the row unchanged with no new `server_version`; a new category's `sort_order` is the maximum over non-deleted categories plus 10. | M3 report: details the spec left open. | SPEC §8.6, §9.6, AC-3.5, AC-4.4, AC-7.3, E-17, E-20, E-21, E-25, E-26 |
|
||||
| D-035 | 2026-10-17 | Reserved for M4 (in flight). | | |
|
||||
| D-036 | 2026-10-17 | Reserved for M4 (in flight). | | |
|
||||
| D-037 | 2026-10-17 | Reserved for M4 (in flight). | | |
|
||||
| D-038 | 2026-10-17 | Reserved for M4 (in flight). | | |
|
||||
| D-039 | 2026-10-17 | Reserved for M4 (in flight). | | |
|
||||
| D-035 | 2026-10-18 | A mutation whose `mutation_id`, `entity_type` or `entity.id` is unusable (not a canonical lowercase hyphenated UUID, or an `entity_type` other than `expense` or `settlement`) fails the whole `POST /sync` with 400 `invalid_request`, checked before the transaction; every other shape error is a per-mutation `rejected` / `validation_failed` (§14.4.4 step 3). | M4 report (`bd6f10a`): without those keys no per-mutation result (keyed by `mutation_id`, `entity_type`, `entity_id`) can be formed or stored in `sync_mutations`. | SPEC §14.4.4 |
|
||||
| D-036 | 2026-10-18 | `POST /sync` with 0 mutations is 422 `batch_too_large`; the §14.3.2 meaning reads "Number of mutations in `POST /sync` outside 1 to 100". | M4 report (`bd6f10a`): §9.6 said 1 to 100 but §14.3.2 named only "more than 100". | SPEC §9.6, §14.3.2, §14.4.4 |
|
||||
| D-037 | 2026-10-18 | A delete of a never-synced row (§14.4.4 step 7, no stored row) runs the full step-8 validation (field rules, domain rules, references), so the stored tombstone satisfies the schema. Every rejection carries the stored row or `null` as `current`, at steps 3 and 4 too. Step 11 keeps the stored `created_at` (a pushed one is ignored on update). Pushed ids must be canonical lowercase hyphenated UUIDs; another spelling of a valid UUID is not accepted. | M4 report (`bd6f10a`): v0.5 validated only references for such a tombstone, left `current` open at steps 3 and 4, and did not say whether `created_at` may change. | SPEC §14.4.3, §14.4.4 |
|
||||
| D-038 | 2026-10-18 | FCM retry is one send plus up to three retries after 1 s, 5 s, 25 s (four sends at most) on 429, 5xx and network errors. On 401 the cached access token is dropped and the message is not retried; other 4xx are not retried. Retries run inside the single sender task, blocking it up to 31 s per device; accepted for two users. | M4 report (`bd6f10a`): "3 attempts with delays 1 s, 5 s, 25 s" was ambiguous (three delays need four sends) and 4xx handling was open. | SPEC §14.6 |
|
||||
| D-039 | 2026-10-18 | `sync_mutations` pruning deletes rows whose `received_at` is strictly older than 90 days (a row exactly 90 days old is kept); PLAN-M4's "90 days and older" corrected. Seam change to D-015: `tasks::start` returns the `AppState` with the real push queue installed and `serve` builds the router from it; `AppState::new` defaults to `SqlAuditWriter`; `NoopAuditWriter` is removed; `NoopPushQueue` stays for push disabled. | M4 report (`bd6f10a`): "older than 90 days" (D-021) and PLAN-M4 disagreed at the boundary; the push queue must exist before the router is built. | SPEC §6.3, §14.1, §14.4.9, §14.6; PLAN-M4; server/README.md |
|
||||
| D-040 | 2026-10-17 | A push refused as a whole (400 `invalid_request`, 413 `payload_too_large`, 409 `household_incomplete`, 422 `batch_too_large`) counts as a batch with no progress: the cycle goes on to pull and records `last_error_code`; 5xx and 429 retry with backoff; 401 and 426 stop. (Proposed by M5 as D-030, renumbered.) | M5 report: §14.4.5 covered only network errors, 5xx and 401. | SPEC §14.4.5 |
|
||||
| D-041 | 2026-10-17 | `:data`'s `DataModule` provides implementations only; the interface bindings live in `DataBindingsModule` (`@DisableInstallInCheck`), which `:app` includes for release so the debug fakes do not duplicate bindings. (M5's D-031, renumbered.) | M5 report: Hilt fails on duplicate bindings when debug binds fakes. | SPEC §6.2, §11.3 (M8); PLAN-M8 |
|
||||
| D-042 | 2026-10-17 | Room tests run on Robolectric with the framework SQLite driver; migration tests build from the exported schema JSON rather than `room3-testing`. (M5's D-032, renumbered.) | M5 report: no device needed for the gate. | SPEC §6.2 |
|
||||
|
||||
Reference in New Issue
Block a user