481889a9dc
D-119 (landed96e4cbd, security review L-3): E-6 at most 10 requests per minute per client IP in its own window, not counted against the 60 of the other unauthenticated endpoints; SPEC §8.6, §10.6, E-6. M-1 closed by the owner as an accepted risk (§8.1, §8.7); the D-114 limits confirmed (§11.4). HANDOFF: main and origin at96e4cbd, NPM rework (D-120) in flight, owner steps and questions rewritten, numbering next D-121 and v0.21. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
77 lines
16 KiB
Markdown
77 lines
16 KiB
Markdown
# Handoff: state of the work
|
||
|
||
Living document, rewritten in place (never a second copy). A new orchestrator session reads CLAUDE.md, then this.
|
||
|
||
## 1. Where things stand
|
||
- `main` at `96e4cbd` (the APK download rate limit, D-119, landed after the SPEC v0.19 docs commit `3ef2c15`) plus the SPEC v0.20 docs commit from `agent-docs-v020` once it lands. `origin/main` (Gitea) at `96e4cbd`: everything up to the rate limit is pushed.
|
||
- History rewrite and first push, 2026-10-18 (owner authorised): superseded PNG versions were dropped from the whole history (no LFS), the current goldens and UX mock-ups restored in `18c527f`; pack 45 MiB; `origin/main` pushed at `18c527f`. A bundle of the pre-rewrite history (`kvitt-pre-rewrite.bundle`) is kept outside the repository, in the orchestrator's scratchpad. Every commit from M2b on got a new hash; the oxipng pass (`7c453d8` before the rewrite) no longer exists as a commit (it only touched PNGs). SPEC, `docs/decisions.md`, this file and `prompts/README.md` cite the rewritten hashes since v0.18 (`7c453d8` stays as the oxipng pass's name; `docs/validation/ac-coverage.md` keeps its pre-rewrite hashes verbatim); since v0.19 the plans cite rewritten hashes too (eight pre-rewrite hashes in `PLAN-*.md` replaced by the commit with the same subject); `docs/spikes/pi-image-arm64.md` and the READMEs had none stale. For any other old hash, find the commit by its subject with `git log --oneline --grep`.
|
||
- History (rewritten hashes): `5e1be38` plans and prompts, `c885aba` M2c-dev, `76ee957` SPEC v0.3, `c277ee3` M2b, `06f48ef` M2a, `6dce4c7` SPEC v0.4, `7869ec4` M3, `8f0e156` M5, `853c038` SPEC v0.5, `15056dc` M4, `d9dd584` SPEC v0.6, `50339ae` M9, `86e08a1` SPEC v0.7, `aa765bb` M12a, `d39eb2c` SPEC v0.8, `b09f322` M12c, `6eaccec` M12b, `0006bd7` UX Kalas categories and tokens, `6da6b1b` UX sprint U2, `e676812` SPEC v0.9, `557af7e` M12d, `7730a31` M6, `9ee67d4` SPEC v0.10, `a41f240` and `6453932` app wiring before M8, `1ea752c` M7a, `ea7d494` M7e, `7380e50` M7d, `0e1d99d` M7b, `0b140aa` M7c, `c4ab35f` M7f, `b24c8b5` M6b, `f4dc10c` SPEC v0.11, `3dda414` M7g, `cb832a7` and `3d9eea8` M8, `6159e18` SPEC v0.12, `8c60715` M12e, `b7b7483` M12e launcher icon, `51d6ff8` SPEC v0.13, `a312758` Vico removal, `f56dcbe` FCM `fid`, `3e6c337` Pi image arm64 check, `e2dba84` SPEC v0.14, `328c466` M8b, `6eb3f85` `FcmMessageBody.fid`, `bfde12c` SPEC v0.15, `75b549f` M8c goldens, `a0c25b0` M8c licences row, `c30a49e` R8 release build, `bacbd18` M8d, `bfc3acb` SPEC v0.16, `2476667` M2c-pi bundle, `a59abeb` M8e, `b4d9467` Dockerfile `CARGO_BUILD_JOBS`, `956908a` M8f, `1328afe` chips cleanup, `400eac9` SPEC v0.17, `18c527f` goldens restored after the rewrite; then `2fac1ec` to `e70fe0d` infra overhaul, `8660b7f` server gap pass, `7fb86f5` infra security, `dc7a296` to `a0259ab` M12f, `8103dae` to `b985ce9` server overhaul, `48fe119` SPEC v0.18, `e4e6e5d` to `c582363` Android overhaul, `3ef2c15` SPEC v0.19, `96e4cbd` APK download rate limit (D-119).
|
||
- Landed since SPEC v0.19 (recorded in v0.20):
|
||
- APK download rate limit (`96e4cbd`, D-119, security review L-3): E-6 `GET /apk/files/{file_name}` at most 10 requests per minute per client IP in its own window, not counted against the 60 of the other unauthenticated endpoints; a 404 counts; 429 `rate_limited` with `Retry-After`; E-5 and E-4 stay on the 60; unit and integration tests.
|
||
- Landed since SPEC v0.18 (recorded in v0.19):
|
||
- Android overhaul (`e4e6e5d`, `8229ed6`, `515b60f`, `e08aaf0`, `0714242`, `a92c236`, `74b5fbd`, `c582363`; D-118): shared test support in `:core:design`'s test fixtures (`se.hagstedt.kvitt.design.testing`), `kvitt.android.screenshots` wires Roborazzi and the test libraries; `format/Failures.kt`, `format/Limits.kt`, `savedSplitLabel` in `:core:design`; dead code removed (`KvittMotion` only the seesaw spring, `KvittIcon.Description`, unused dimens and shapes); four catalog entries removed; one `CopyCategoryKeywords` task; one `configureAndroidModule`; S-7's state types in `ExpenseEditorState.kt`. Gate at landing: 941 tests, no golden changed. Release APK 10.46 MB.
|
||
- Landed since SPEC v0.17 (recorded in v0.18):
|
||
- Infra overhaul (`2fac1ec`, `61f92c8`, `fc6126c`, `b647faf`, `e70fe0d`; no decision): `tools/gradle-lock.sh` shared by `gradle-serial.sh`, `clean-builds.sh` and `infra/build-images.sh`; the pre-commit hook runs through `gradle-serial.sh` with JDK 25 on `PATH` (hooks reinstalled in `main` after `e70fe0d`); `install.sh` keeps the deployed `KVITT_TAG`; `--provenance=false` on buildx pushes, the workflow token through env; `RCLONE_REMOTE` dropped from the dev env.
|
||
- Server gap pass (`8660b7f`, D-113): `kvitt-server invite-partner` (0 ok, 2 household full, 3 no admin), so a scripted partner needs no second admin device; `push sent` info line; CLI tests (`invite-partner`, `migrate`, `healthcheck`, SIGTERM drain); `tools/contracts-check.py` 134 checks.
|
||
- Infra security (`7fb86f5`, D-117): Caddy log redaction of `/i/<code>` and query strings, CSP and three more response headers, Caddy non-root (uid 10002, read-only, caps dropped), `/certs` 0750, dev stack mounts only the FCM key, `/opt/kvitt/deployed-digests` with a WARNING on change, `kvitt_cicd` with `write:package` only, the plain root key (old variant B) not acceptable.
|
||
- M12f (`dc7a296`, `5b2ed37`, `2bd6b58`, `c366fbd`, `a0259ab`; D-109 to D-112, D-116): S-16 lost version read-only, S-22 "<version> (schema <n>)" from `/health`, `SettlementRepository` returns null instead of throwing, E-17 retry after each successful sync, invite links dropped while paired, private notifications on the lock screen, `localeFilters` en/sv, `:data` tests (429, D-040, WorkManager via `work-testing`), the home/account screenshot harness fix (stale `sv_dark` goldens re-recorded), 19 unused string keys removed (`strings.csv` 562 rows). Gate at landing: 941 tests. Release APK 10.0 MB.
|
||
- Validation reports (SPEC §11.4): `docs/validation/ac-coverage.md` (taken at `956908a`, `ed1b130` before the rewrite): 270 rows, 260 Done, 5 Partial, 1 Missing, 4 device-only; feature complete in code, not yet against §4's acceptance rule; its S-16 and S-22 mismatches are fixed by M12f. `docs/validation/security-review.md` (taken at `18c527f`): Critical 0, High 1, Medium 3, Low 10, Info 8; L-6 kept (D-116), L-7 fixed (D-116), L-1, L-2, L-5, L-9, M-2 fixed (D-117), L-8 fixed (`e70fe0d`); H-1 (`725901c`, D-115), M-3 (`baa5806`) and L-4 (`b985ce9`, D-114) fixed by the server overhaul; L-10 left as the spec says, decision pending (§4). Owner answers of 2026-10-19: M-1 no (backups stay unencrypted, rclone keeps the `drive` scope; accepted risk in SPEC §8.1 and §8.7), L-3 accepted and fixed (`96e4cbd`, D-119), the 16 MP and 512-character limits (D-114) confirmed; M-2's remaining question (the Home Assistant SSH key form) is moot with the NPM rework (D-120) and closed. H-1 must be fixed before port 8443 is opened.
|
||
- Server overhaul (landed on `main` `8103dae` to `b985ce9`): shared helpers and one test harness, clippy pedantic wins; H-1 rate limiter (`725901c`, D-115), pictures at most 16 MP and 64 MB decoded (`baa5806`, D-114), `fcm_token` at most 512 characters (`b985ce9`, D-114); shared row inserts, invite creation and CLI helpers, `tests/common/` harness, synchronous `tasks::start`; 268 server tests; all crates at their latest versions. Recorded in SPEC v0.18.
|
||
- In flight:
|
||
- NPM rework (D-120 reserved, agent running): the owner runs Nginx Proxy Manager on the Home Assistant Pi, and NPM renews the Let's Encrypt certificate for `icehack.asuscomm.com`. Caddy and cert-sync are replaced by an NPM proxy host that forwards to the Kvitt Pi's server on LAN port 8080, so TLS terminates in NPM, the Kvitt Pi's compose stack loses `caddy` and `cert-sync`, and the `infra/pi/` bundle and its owner checklist change. Security review M-2 (the HA SSH key form for cert-sync) becomes moot and is closed. SPEC §10.5 and the other Caddy and cert-sync text are rewritten when D-120 lands (a docs pass records it; next free decision D-121). Open assumptions are §3 item 3.
|
||
- This docs pass (`agent-docs-v020`: SPEC v0.20, D-119, HANDOFF, `prompts/README.md`).
|
||
- `cargo-audit` in the server gate: the orchestrator is installing `cargo-audit` on the Mac; an agent adds it to the gate after the install.
|
||
- Earlier outcomes (details in SPEC §11.2, §11.3 and `docs/decisions.md`): M8 end-to-end test passed on the owner's S26 against the dev server on 2026-10-18 (pairing, scripted partner, offline expense synced, push by `fid` delivered); M8b to M8f UI fixes; the M2c-pi bundle `infra/pi/` (D-103 to D-106, owner checklist in `infra/README.md` "Pi: what the owner does"); release builds R8-shrunk and signed (alias `kvitt`, RSA 4096, v2).
|
||
- Compatibility: an APK from M12e on cannot pair or list devices against a pre-M12e server, so the server is rebuilt and redeployed before a new APK is installed (SPEC §14.8). The dev server already runs schema 3.
|
||
- Remote: `origin` on Gitea, SSH key works; pushed at `96e4cbd`. The owner created the Gitea secret `kvitt_cicd` and the Drive token (`infra/secrets/rclone.conf`, `[gdrive]`; folder id in `.env` as `RCLONE_FOLDER_ID`); the cert-sync key `infra/secrets/ha_ssh_key` exists (all git-ignored). The first push triggered the Gitea Actions workflow; its result is the owner's §3 item 1.
|
||
- Both gates exist: Android `tools/gradle-serial.sh check` (from `android/`), server `cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test` (from `server/`). Both green on `main` as far as reported.
|
||
- Importer: the dry run of 2026-10-18 reconciled on both real files (Vardag 1746 rows, Hemma 37 rows, 0 unmapped). The CSV files stay in `export/` (git-ignored).
|
||
- Development stack (M2c-dev): running on the Mac at `http://192.168.1.210:8080`, database at schema 3, data kept; backups `infra/dev/backups/kvitt-20261018T093143Z.sqlite.gz` and `kvitt-20261018T130443Z.sqlite.gz` (git-ignored). On this Podman setup run `infra/dev/up.sh --down`, then `infra/dev/up.sh`, to swap in a rebuilt image. Since `7fb86f5` `up.sh` copies only `fcm-service-account.json` into `infra/dev/secrets/` (git-ignored). The owner's phone is paired with it; a scripted partner exists (tokens held by the orchestrator outside the repo). Firebase project `kvitt-1337` with apps `se.hagstedt.kvitt` and `se.hagstedt.kvitt.debug`; service account `kvitt-fcm`.
|
||
- Installed on the owner's phone (SM-S942B): the debug app `se.hagstedt.kvitt.debug`, paired against the dev server, with the build whose checks are §3 item 2; the release app `se.hagstedt.kvitt`, installed 2026-10-19, not yet paired (its checks are §3 item 2 as well).
|
||
- Disk: the MacBook had about 72 GB free during the v0.20 docs pass (2026-10-19); run `tools/clean-builds.sh` when it drops under about 5 GB.
|
||
|
||
## 2. Open work (one line each, with who owns it: agent or owner)
|
||
- Land `agent-docs-v020` (orchestrator): SPEC v0.20, D-119, HANDOFF, `prompts/README.md`.
|
||
- Restore drill AC-18.2 (`docs/validation/restore-drill.md`, M10; the one Missing row of the AC-coverage audit).
|
||
- AC-18.1, AC-20.1 and AC-16.3 shown on the Pi and the phones (M2c-pi run, M10).
|
||
- A `KvittWorkerFactory.createWorker` test (Android agent, no plan yet).
|
||
- Security review L-6: an S-1 design state if the owner wants a confirm step for an unusual server (OQ-6, default not in v1; owner, §4).
|
||
- Security review L-10: `forbidden` or `not_found` for the other user's device id (owner decision, §4).
|
||
- Tooling: `cargo-audit` in the server gate (agent, after the orchestrator's install on the Mac, in flight).
|
||
- State-class naming: `*Ui`, `*UiState` and `*State` are mixed across features; needs a decision if a convention is wanted (owner; no plan).
|
||
- Unverified risk: the workflow pushes a git tag; whether that re-triggers the path-filtered workflow is not known (watch the first runs).
|
||
- Land the NPM rework (D-120) when its agent reports, then a docs pass records D-120 and rewrites SPEC §10.5 and the Caddy and cert-sync text (orchestrator).
|
||
- The owner's answers to the phone checks of §3 item 2 (debug app checks; release app pairing with a device invite the orchestrator provides).
|
||
- Owner runs the Pi bundle on the Kvitt Pi: deferred by the owner on 2026-10-19 ("when it makes sense to move over to the Pi"); the bundle and its checklist are rewritten by D-120 first; record the router-forward answer in `infra/README.md` then (the cert-sync key-form question is closed).
|
||
- Plans (SPEC §11.2 v0.20), in dependency order; prompt `prompts/<id>.md`:
|
||
- M1, M2a, M2b, M2c-dev, M3, M4, M5, M6, M6b, M7a to M7g, M8 (phone test passed), M8b to M8f, M9, M12a to M12f, U1, U2, R8 release build, M2c-pi bundle, server gap pass, infra overhaul, infra security, the server overhaul and the Android overhaul: landed.
|
||
- Next: a server image with the H-1 fix before the router forward; the NPM rework (D-120) reshapes the Pi bundle; the owner runs it on the Kvitt Pi when he moves over (D-100, deferred); M10 First release after it (keystore per §10.6, D-101); M11 Import history after M9 and M10 (procedure §10.8 "Production run", D-025; each run must reconcile, D-045).
|
||
|
||
## 3. HUMAN steps pending (relay in chat)
|
||
1. Gitea, the repository, Actions: did the run "Build Kvitt images" triggered by the first push succeed (yes/no), and how long did it take? Parked by the owner ("come back to it"). If the run's tag push failed with `kvitt_cicd` limited to `write:package`: may `write:repository` be added (yes/no)?
|
||
2. Phone checks, answers pending (yes/no each):
|
||
1. Debug app (`se.hagstedt.kvitt.debug`): screen switches are instant, with no fade or slide; Inställningar, Om Kvitt shows the server version as "… (schema 3)"; the recurring editor shows Konto, Betalt av, Delning as three fixed rows.
|
||
2. Release app (`se.hagstedt.kvitt`, installed 2026-10-19): it opens to "Para ihop" without a crash; then pair it with the device invite the orchestrator provides; Home shows "Test"; add one expense and watch it sync.
|
||
3. NPM questions (for D-120; the agent proceeds on the assumptions): is `icehack.asuscomm.com` already a proxy host in Nginx Proxy Manager for another service (assumed free)? Does the router forward external 8443 to NPM's 443 or to a dedicated NPM port (assumed 443)?
|
||
4. Pi checklist, later: deferred by the owner ("when it makes sense to move over to the Pi"). The steps of `infra/README.md` "Pi: what the owner does" are rewritten by D-120 (no cert-sync key, the router forward goes to NPM) and relayed in chat when the owner moves over.
|
||
|
||
## 4. Owner questions (asked, unanswered)
|
||
- §3 item 1: the workflow result and duration (parked), and `write:repository` if the tag push failed.
|
||
- §3 item 2: the debug and release app checks.
|
||
- §3 item 3: the two NPM questions (assumed: `icehack.asuscomm.com` free in NPM; external 8443 to NPM 443).
|
||
- OQ-6 (SPEC §12): a confirm step on S-1 for an unusual server; default "not in v1".
|
||
- Security review L-10: should a member get `not_found` instead of `forbidden` for a device id of the other user (hides whose it is)? As built and as the spec says: `forbidden`.
|
||
- State-class naming (`*Ui`, `*UiState`, `*State` mixed): fix one convention, or leave as built?
|
||
|
||
## 5. Paused (waiting on something outside the repo)
|
||
- Nothing.
|
||
|
||
## 6. Numbering
|
||
- Decisions: D-001 to D-119 used, except D-008 and D-009 (never used); D-120 reserved by the running NPM agent. Next free: **D-121**.
|
||
- Spec version: v0.20 written; next **v0.21**.
|
||
- Server schema version **3** (migration 0003); next **4**. Room schema version **1** (`android/data/schemas/se.hagstedt.kvitt.data.db.KvittDatabase/1.json`); next **2**. Both unchanged by v0.20.
|
||
- Sync protocol **1**, push payload **1**.
|
||
|
||
## 7. How the work is run
|
||
See CLAUDE.md › Orchestration. HUMAN steps in reports are relayed in chat by the orchestrator; the owner does not read them in files.
|