SPEC v0.38: K1's report recorded (D-181 to D-188), K2 v0.39 and K3 v0.40

K1 landed 671c00d (card purchases on the server, schema 4, E-36 to E-44). Decisions: D-181
history counts today (6 windows for 180 days, revises CP-3 and CP-7); D-182 the server's CS-4
first token; D-183 Enable Banking errors to CP-14; D-184 callback (expired state stored as
expired, revises CP-2; atomic claim; session without accounts revoked); D-185 fetch details;
D-186 PSU headers; D-187 E-41, E-43, E-44 details, CardAttempt.failure a plain string; D-188
placement and the secrets/cards compose mount. D-189, D-190 unused.

SPEC: §9.12 CP-2 to CP-5, CP-7, CP-8, CP-14; §10.2; §11.1, §11.2 K1 row with the landing
gates, §11.3; §14.1.3; §14.3.3; §14.3.4 (E-1 schema_version 4, E-37, E-39, E-41, E-43, E-44);
§14.3.5; §14.7; change log. PLAN-K2, PLAN-K3 and prompts/README.md name v0.39 and v0.40.
HANDOFF: K1 landed, K2 and K3 running, the Android gate red until K2, K1 owner steps 1 to 4
pending, next free D-212 and v0.39.

Pre-commit hook bypassed (--no-verify): docs only; its Android fast tests fail only in
:domain VectorFilesTest, red on main by design until K2 lands (as for 671c00d).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
2026-10-24 16:50:04 +02:00
parent 671c00d0d8
commit 07df30d04d
6 changed files with 53 additions and 40 deletions
+9 -8
View File
@@ -3,8 +3,8 @@
Living document, rewritten in place (never a second copy). A new orchestrator session reads CLAUDE.md, then this.
## 1. Where things stand
- State as of 2026-10-24. `main` at `8b620c8` (the K0 report, SPEC v0.36), after `8fa9198` (`infra/build-images.sh` Podman detection), `f15fa23` (`infra/build-images.sh` passes `KVITT_VERSION_CODE`), `4339d01` (the owner's SJ Prio card artwork), `b5114ad` (SPEC v0.35, card purchases), `2ce317f` (the K0 card test app), `5d948f9` (SPEC v0.34) and `5305f37` (W0b web shared follow-ups); before them `07c36d2` (L-10, D-156) and `593c727` (SPEC v0.33, D-156, D-157). This pass: SPEC v0.37, D-211. **Deployed and verified 2026-10-24** (owner, D-211): image `v1.0.3` (git tag `v1.0.3` = `8fa9198`, built on the Mac) runs on the Kvitt Pi, `server` and `backup` healthy; the web client is live at `https://kvitt.suorra.duckdns.org/app/`, paired from the laptop browser with a device invite, and the owner reports "All seems to work" for Home and an account, add, edit and delete an expense, CSV export, record and delete a payment, analytics with category drill-down and Insights, the settings pages, revoking the browser from the phone, and the Android Insights section on S-11 against the new server. W0 to W7 are landed and verified (SPEC §11.2, §15). Release 140 (`kvitt-140`, deployed 2026-10-21) runs on both phones. Gates on `main`: server 300 tests, web 609 tests, contracts-check 136.
- **Card purchases programme** (owner request and answers 2026-10-24, SPEC v0.35, D-158 to D-176, FR-22, §9.12): each user connects their own SJ Prio Mastercard (SEB Kort) through Enable Banking's restricted production mode with BankID; the server alone holds the key and sessions, fetches purchases up to 4 times a day in the background plus "Uppdatera", and only the card holder sees them (not in `GET /sync`, audit, push or analytics); a fourth bottom destination "Kortköp" (Översikt, Kortköp, Statistik, Inställningar, revising D-048), "Anslut kort" in Settings with the SJ Prio artwork, "Skapa utgift" prefilled into S-7, one-tap links to matching expenses (same amount, ±3 days), "Från kortköp" on account detail; server schema 4 (migration 0004), Room 2, endpoints E-36 to E-44, screens S-23 to S-27, web W-21 to W-23. Plans `PLAN-K0.md` to `PLAN-K3.md`, prompts `prompts/K0.md` to `prompts/K3.md`, written by `agent-a3b7abf183f883261`, landed `b5114ad`. **K0 done** (2026-10-24): the test app (`2ce317f`) ran with the owner on his card; report [`docs/spikes/K0-enable-banking.md`](spikes/K0-enable-banking.md); its answers are SPEC v0.36 (D-177 to D-180). Main findings: the card returns at most 100 transactions per response and never a continuation key, so history is fetched in windows of at most 30 days, split in half at 100 rows (D-178); merchant text from `remittance_information`, the date `booking_date` else `value_date`, everything in SEK, `entry_reference` on pending rows too (D-179); consent 180 days; background fetch accepted; at least 150 days of history right after BankID. **Owner's assumptions** (2026-10-24, "let's just assume it all works now given your test", D-177), verified by K1's owner steps (PLAN-K1 §8 steps 5 to 7): the partner's card links to the owner's application (fallback: her own application, D-175), a pending purchase keeps its `entry_reference` when booked (fallback: CP-8's amount-and-date rule), the session is reused the next day without BankID. The Enable Banking application exists (restricted production, redirect URL `https://kvitt.suorra.duckdns.org/cards/callback`, the owner's card linked); its key file lives only in the main checkout's git-ignored `docs/spikes/k0/.local/<application id>.pem` and reaches the Pi through K1's owner steps (`~/kvitt/secrets/cards/enable-banking.pem`, D-175), never downloaded again. The artwork is committed for Android and the web, cropped to the card (D-180). **Next: K1** (server, contract) with **K2** (Android) and **K3** (web) starting in parallel on the spec and fakes; K1 lands first, K2 after it, K3 after K2; their proposed spec text goes into v0.37.
- State as of 2026-10-24. `main` at `671c00d` (**K1 landed**: card purchases on the server, schema 4, E-36 to E-44), after `ae73f58` (SPEC v0.37, D-211), `8b620c8` (the K0 report, SPEC v0.36), `8fa9198` (`infra/build-images.sh` Podman detection), `f15fa23` (`infra/build-images.sh` passes `KVITT_VERSION_CODE`), `4339d01` (the owner's SJ Prio card artwork), `b5114ad` (SPEC v0.35, card purchases), `2ce317f` (the K0 card test app), `5d948f9` (SPEC v0.34) and `5305f37` (W0b web shared follow-ups); before them `07c36d2` (L-10, D-156) and `593c727` (SPEC v0.33, D-156, D-157). This pass (`agent-docs-k1`): SPEC v0.38, K1's report as D-181 to D-188; `main` with this pass is pushed by the orchestrator after it lands. **Deployed and verified 2026-10-24** (owner, D-211): image `v1.0.3` (git tag `v1.0.3` = `8fa9198`, built on the Mac) runs on the Kvitt Pi, `server` and `backup` healthy; the web client is live at `https://kvitt.suorra.duckdns.org/app/`, paired from the laptop browser with a device invite, and the owner reports "All seems to work" for Home and an account, add, edit and delete an expense, CSV export, record and delete a payment, analytics with category drill-down and Insights, the settings pages, revoking the browser from the phone, and the Android Insights section on S-11 against the new server. W0 to W7 are landed and verified (SPEC §11.2, §15). Release 140 (`kvitt-140`, deployed 2026-10-21) runs on both phones. Gates on `main` at `671c00d`: server 356 tests, web 609 tests (`match_card_purchase` listed in `NOT_PORTED` until K3), contracts-check 157 (17 `match_card_purchase` cases). **The Android gate on `main` is red by design until K2 lands**: `:domain` `VectorFilesTest` and `:data` `ExampleRoundTripTest` pin the contract's vector and example sets, which K1 extended (contract-first landing; K1 was committed with the pre-commit hook bypassed for that reason). Until K2 lands, a commit on `main` that runs the hook needs the same bypass, decided by the orchestrator.
- **Card purchases programme** (owner request and answers 2026-10-24, SPEC v0.35, D-158 to D-176, FR-22, §9.12): each user connects their own SJ Prio Mastercard (SEB Kort) through Enable Banking's restricted production mode with BankID; the server alone holds the key and sessions, fetches purchases up to 4 times a day in the background plus "Uppdatera", and only the card holder sees them (not in `GET /sync`, audit, push or analytics); a fourth bottom destination "Kortköp" (Översikt, Kortköp, Statistik, Inställningar, revising D-048), "Anslut kort" in Settings with the SJ Prio artwork, "Skapa utgift" prefilled into S-7, one-tap links to matching expenses (same amount, ±3 days), "Från kortköp" on account detail; server schema 4 (migration 0004), Room 2, endpoints E-36 to E-44, screens S-23 to S-27, web W-21 to W-23. Plans `PLAN-K0.md` to `PLAN-K3.md`, prompts `prompts/K0.md` to `prompts/K3.md`, written by `agent-a3b7abf183f883261`, landed `b5114ad`. **K0 done** (2026-10-24): the test app (`2ce317f`) ran with the owner on his card; report [`docs/spikes/K0-enable-banking.md`](spikes/K0-enable-banking.md); its answers are SPEC v0.36 (D-177 to D-180). Main findings: the card returns at most 100 transactions per response and never a continuation key, so history is fetched in windows of at most 30 days, split in half at 100 rows (D-178); merchant text from `remittance_information`, the date `booking_date` else `value_date`, everything in SEK, `entry_reference` on pending rows too (D-179); consent 180 days; background fetch accepted; at least 150 days of history right after BankID. **Owner's assumptions** (2026-10-24, "let's just assume it all works now given your test", D-177), verified by K1's owner steps (PLAN-K1 §8 steps 5 to 7): the partner's card links to the owner's application (fallback: her own application, D-175), a pending purchase keeps its `entry_reference` when booked (fallback: CP-8's amount-and-date rule), the session is reused the next day without BankID. The Enable Banking application exists (restricted production, redirect URL `https://kvitt.suorra.duckdns.org/cards/callback`, the owner's card linked); its key file lives only in the main checkout's git-ignored `docs/spikes/k0/.local/<application id>.pem` and reaches the Pi through K1's owner steps (`~/kvitt/secrets/cards/enable-banking.pem`, D-175), never downloaded again. The artwork is committed for Android and the web, cropped to the card (D-180). **K1 landed** `671c00d` (2026-10-24): migration 0004 (byte-identical contract and server copies), `src/cards/` with the registry, the Enable Banking adapter, the windowed fetch, the poller and the callback page, E-36 to E-44 with four new error codes and a 502 response, examples, `match_card_purchase.json` (17 cases) with its Python reference, `tests/cards_api/` (34 tests against a local Enable Banking stub; the real API is never called), the Pi compose mount of `secrets/cards/` and the NPM `/cards/callback` block; its report is SPEC v0.38 (D-181 to D-188). **Running: K2** (Android, its text into v0.39) and **K3** (web, v0.40), decisions from D-191 to D-210 in disjoint ranges; K2 lands next, K3 after K2. The key is staged at `infra/secrets/cards/enable-banking.pem` in the main checkout (git-ignored); the application id is known to the orchestrator (not written here, CP-13).
- **Verified 2026-10-24** (owner, D-156): Samsung Auto Blocker was the cause of the silent update failure; turning it off let the update install, and the owner turned it back on, so every in-app update on the Samsung phones needs it off (the D-155 blocked text names it; SPEC AC-16.3, `infra/README.md`). The editor keyboard fix (S-19, S-15, S-6, `05ec3f3`) works on the device. The first backup on the Kvitt Pi ran and a snapshot pair is in the Drive folder (AC-18.1). The runner Pi's disk reached 11 of 15 GB during image builds; the owner runs `docker builder prune -af` between builds (SPEC §10.3).
- **Kvitt host name** (owner decision 2026-10-19): `kvitt.suorra.duckdns.org`, a subdomain of the owner's DuckDNS family domain `suorra.duckdns.org`; NPM issues its Let's Encrypt certificate; `KVITT_PUBLIC_HOST=kvitt.suorra.duckdns.org`, so `KVITT_PUBLIC_URL=https://kvitt.suorra.duckdns.org` (port 443, D-124). It is in `infra/pi/.env.example` and `infra/pi/.env` (`KVITT_PUBLIC_HOST`) and `android/local.properties` (`kvitt.publicHost`, git-ignored). SPEC §10.1 and §10.5.1 point here.
- **NPM proxy host** for `kvitt.suorra.duckdns.org` is Online with a Let's Encrypt certificate (owner, 2026-10-19). The router needs no new rule: external TCP 443 already forwards to the Home Assistant Pi (owner confirmed, D-124).
@@ -67,7 +67,8 @@ Living document, rewritten in place (never a second copy). A new orchestrator se
- 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 answered 2026-10-24: `not_found` for the other user's device (D-156), fixed in `07c36d2`. 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; D-120 also moves L-1 and the headers to NPM, makes L-2 moot, reverses the L-8 fix and replaces I-5's check with a conntrack check (dated notes in the review, SPEC §11.4). H-1 is fixed (`725901c`).
- 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:
- This docs pass (`agent-docs-v35`): SPEC v0.37, `docs/decisions.md` D-211 (Mac image builds, `v1.0.3` deployed, the web client verified), `infra/README.md`, this file.
- This docs pass (`agent-docs-k1`): SPEC v0.38, `docs/decisions.md` D-181 to D-188 (D-189, D-190 unused), `PLAN-K2.md`, `PLAN-K3.md`, `prompts/README.md` (v0.39 and v0.40), this file.
- K2 (Android) and K3 (web), Opus agents in their own worktrees.
- 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 `c13be3b` (2026-10-24). The Gitea secret `kvitt_cicd` (a user-level secret of `jacobh2`, renamed by the owner 2026-10-19) works; the Drive token is `infra/secrets/rclone.conf` (`[gdrive]`; folder id in `.env` as `RCLONE_FOLDER_ID`). Unused on the Mac since D-120: `infra/secrets/ha_ssh_key` and `ha_ssh_key.pub` (git-ignored; may be deleted).
@@ -78,7 +79,7 @@ Living document, rewritten in place (never a second copy). A new orchestrator se
- 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)
- K1, K2, K3 (Opus agents, once SPEC v0.36 is on `main`; decisions from D-181 in disjoint ranges, spec version v0.37 for their proposed text): K1 lands first, K2 after K1, K3 after K2. After deploy, K1's owner steps 5 to 7 verify K0's three assumptions (D-177).
- K1 landed `671c00d` (recorded in v0.38, D-181 to D-188). K2 and K3 running (agents; decisions from D-191 to D-210 in disjoint ranges, spec text into v0.39 for K2 and v0.40 for K3): K2 lands next and turns the Android gate green again, K3 after K2. Next: build the image with K1 (`infra/build-images.sh v1.0.N`, orchestrator), then K1's owner steps 1 to 4 (§3). After a client with the card screens is deployed, K1's owner steps 5 to 7 verify K0's three assumptions (D-177).
- K0 test app session in `docs/spikes/k0/.local/state.json` (owner): keep it or press "9 Koppla från" in the test app (PLAN-K0 §6 step 7); K1 does not use it. The key file stays in `.local/` either way.
- Card design-spec sections S-23 to S-27 for `docs/ux/design-spec.md` (docs agent, from K2's report).
- Closed by `f15fa23` and `8fa9198`: `infra/build-images.sh` passes `--build-arg KVITT_VERSION_CODE` to the server build as the workflow does (v0.34), and detects Podman without a pipe.
@@ -112,7 +113,7 @@ Done 2026-10-21: the Pi install (stack running, `v1.0.1`, both containers health
Standing note for every in-app update on the Samsung phones: turn Auto Blocker off before tapping "Uppdatera", back on afterwards (D-156). Images are built on the Mac (D-211); only if the fallback workflow runs: `docker builder prune -af` on the runner Pi between two of its builds.
1. Gate (step 4a items 4 and 5), both phones run the keyed APK (release 140): NPM, Hosts, Proxy Hosts, Kvitt row, Edit, Advanced: paste the step 4 block with the key in place of `KVITT_APP_KEY`, Save (row Online, yes/no). Acceptance test: on home Wi-Fi Chrome opens `https://kvitt.suorra.duckdns.org/i/TEST` and the invite page shows (yes/no); with Wi-Fi off the same URL gives a connection error (yes/no); with Wi-Fi off Kvitt's About screen shows the server version (yes/no).
2. Web client gate lines (D-130, SPEC v0.28): when the NPM block is pasted (item 1, or again if the D-126 block is already in NPM), paste the current `infra/npm/kvitt.conf` content (step 4), which also lets the web client through and sets its Content-Security-Policy; Save (row Online, yes/no). The image with the web client is deployed (`v1.0.3`, D-211); acceptance test with Wi-Fi off on the phone: `https://kvitt.suorra.duckdns.org/app/` shows the Kvitt login page (yes/no) and `https://kvitt.suorra.duckdns.org/health` still gives a connection error (yes/no) (step 4a item 5 d and e).
3. Card purchases: K0's owner steps are done (2026-10-24: Enable Banking account and Terms of Service, the application, the key in `docs/spikes/k0/.local/`, the owner's card linked, the test app run); the artwork is committed (D-180). The partner's card and the next-day check moved to K1's owner steps (PLAN-K1 §8 steps 5 to 7, after K1 and a client with the card screens are deployed); K1's steps 2 to 4 (key to the Pi, `.env`, NPM block, deploy) come first.
3. Card purchases: K0's owner steps are done (2026-10-24: Enable Banking account and Terms of Service, the application, the key in `docs/spikes/k0/.local/`, the owner's card linked, the test app run); the artwork is committed (D-180). **K1's owner steps 1 to 4 are pending** (PLAN-K1 §8), relayed by the orchestrator in chat after the image with K1 is built: step 1 is done by the orchestrator (the key staged at `infra/secrets/cards/enable-banking.pem` in the main checkout, git-ignored, mode 0600); step 2 on the Pi in `~/kvitt`: `mkdir -p secrets/cards && chmod 700 secrets/cards` before anything else (the new `compose.yaml` refuses to start without that folder, even with no card configured, D-188), `scp` the key, `chmod 600 secrets/cards/*`, copy the new `compose.yaml`, set `KVITT_EB_APP_ID=` in `.env` (the orchestrator gives the id in chat) and leave `KVITT_EB_MEMBER_APP_ID=` empty; step 3 paste the current `infra/npm/kvitt.conf` into NPM (row Online, yes/no); step 4 backup, `KVITT_TAG`, `docker compose pull && docker compose up -d` (`server` healthy, `/health` shows `"schema_version":4`, yes/no). The partner's card, the next-day check and pending to booked (PLAN-K1 §8 steps 5 to 7) follow once a client with the card screens is deployed.
## 4. Owner questions (asked, unanswered)
- OQ-7 (SPEC §12): a notification for new card purchases? Default: none in this version (D-171).
@@ -125,9 +126,9 @@ Standing note for every in-app update on the Samsung phones: turn Auto Blocker o
- Nothing.
## 6. Numbering
- Decisions: D-001 to D-180 and D-211 used, except D-008, D-009 and D-150 (never used); D-158 to D-176 are the card purchases (v0.35), D-177 to D-180 K0's results and the artwork (v0.36), D-211 the Mac image builds and the verified `v1.0.3` deploy (v0.37). **D-181 to D-210 are reserved for K1 to K3**; next free after them: **D-212**.
- Spec version: v0.37 written (D-211: Mac image builds, `v1.0.3` deployed, the web client verified). Next **v0.38**. The K plans, `prompts/README.md`, §2's K item and SPEC §11.1 and §11.3 still name v0.37 for the K1 to K3 text (orchestrator: give them v0.38 in the briefs or have the references renamed).
- Server schema version **3** in code (migration 0003); **4** specified (migration 0004, §14.1.3) for K1. Room schema version **1** in code; **2** specified (§14.2.3) for K2.
- Decisions: D-001 to D-190 and D-211 used, except D-008, D-009, D-150, D-189 and D-190 (never used); D-158 to D-176 are the card purchases (v0.35), D-177 to D-180 K0's results and the artwork (v0.36), D-211 the Mac image builds and the verified `v1.0.3` deploy (v0.37), D-181 to D-188 K1's report (v0.38). **D-191 to D-210 are reserved for K2 and K3**; next free after them: **D-212**.
- Spec version: v0.38 written (K1's report, D-181 to D-188). Next **v0.39** (K2's text), then **v0.40** (K3's); `PLAN-K2.md`, `PLAN-K3.md`, `prompts/README.md` and SPEC §11.1 and §11.3 name them.
- Server schema version **4** in code (migration 0004, K1 `671c00d`); the Kvitt Pi and the dev stack still run schema 3 until their next deploy. Room schema version **1** in code; **2** specified (§14.2.3) for K2.
- New ids in v0.35: FR-22 (AC-22.1 to AC-22.15), US-18, US-19, CP-1 to CP-14 (§9.12), S-23 to S-27, W-21 to W-23, E-36 to E-44, OQ-7, OQ-8; plans K0 to K3.
- Sync protocol **1**, push payload **1**.