K0 report, SPEC v0.36: K0 results, windowed history fetch (D-177 to D-180), card artwork cropped
Build Kvitt images / build (push) Failing after 3h10m35s

K0 ran on the owner's SJ Prio Mastercard on 2026-10-24; the report
docs/spikes/K0-enable-banking.md holds aggregates only. SPEC v0.36 answers
every K0 TODO(spec) of §9.12: D-177 K0's outcome and the owner's
assumptions (partner card, pending->booked, next-day session; verified by
K1's owner steps), D-178 the windowed history fetch (100-row cap, no
continuation key: 30-day windows split at 100 rows), D-179 the transaction
mapping (merchant from remittance_information, booking date else value
date, FEE debits to handle, debtor/CPAN fields never stored), D-180 the
artwork cropped to the card (1313 x 829) and committed for Android and the
web. K plans, prompts/README and HANDOFF follow; K1 to K3 spec text goes
into v0.37.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 12:31:06 +02:00
parent 8fa91986f0
commit 8b620c8c6a
11 changed files with 224 additions and 78 deletions
+12 -14
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 `2ce317f` (the K0 card test app), after `5d948f9` (SPEC v0.34) and `5305f37` (W0b web shared follow-ups, pushed to `origin` 2026-10-24); before them `07c36d2` (L-10, D-156) and `593c727` (SPEC v0.33, D-156, D-157). Release 140 (`kvitt-140`, deployed 2026-10-21) runs on both phones (verified 2026-10-24). Next: the Pi image build of the pushed `main` with the complete web app (the Gitea workflow on the Pi runner; this pass's `server/Dockerfile` and workflow change triggers another run when pushed), then the owner deploys it (§3 step 3), logs in from the laptop browser (the web client's first use, D-156) and runs the browser checks (§3 step 4). This docs pass (`agent-docs-v34`) records L-10 as implemented and W0b (SPEC v0.34, no new decision) and passes the web's version code into the image build (`ARG KVITT_VERSION_CODE` in `server/Dockerfile`, `--build-arg KVITT_VERSION_CODE=<git rev-list --count HEAD>` in the workflow). 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`. **Next: K0** — the test app landed `2ce317f` (`docs/spikes/k0/app.py`, one command, key and session in the git-ignored `docs/spikes/k0/.local/`); what remains is running it with the owner (and the partner) and the report `docs/spikes/K0-enable-banking.md`, whose answers to the K0 `TODO(spec)` items of §9.12 go into SPEC v0.36 before K1 starts. Then K1 (server, contract) lands first, K2 (Android) after it, K3 (web) after K2; they may run in parallel. K0 waits on the owner steps of §3 (Enable Banking account and ToS, the application, the key, linking both cards).
- State as of 2026-10-24. `main` at `8fa9198` (`infra/build-images.sh` Podman detection) and `f15fa23` (`infra/build-images.sh` passes `KVITT_VERSION_CODE`), after `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, pushed to `origin` 2026-10-24); before them `07c36d2` (L-10, D-156) and `593c727` (SPEC v0.33, D-156, D-157). Release 140 (`kvitt-140`, deployed 2026-10-21) runs on both phones (verified 2026-10-24). Next: the Pi image build of the pushed `main` with the complete web app (the Gitea workflow on the Pi runner; this pass's `server/Dockerfile` and workflow change triggers another run when pushed), then the owner deploys it (§3 step 3), logs in from the laptop browser (the web client's first use, D-156) and runs the browser checks (§3 step 4). SPEC v0.34 (`5d948f9`) recorded L-10 as implemented and W0b and passes the web's version code into the image build (`ARG KVITT_VERSION_CODE` in `server/Dockerfile`, `--build-arg KVITT_VERSION_CODE=<git rev-list --count HEAD>` in the workflow). 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.
- **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,7 @@ 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-v34`: SPEC v0.34, `docs/decisions.md` D-156, the security review's L-10 note, `server/Dockerfile`, `.gitea/workflows/build-images.yaml`, this file).
- This docs pass (K0 close-out: SPEC v0.36, `docs/decisions.md` D-177 to D-180, `docs/spikes/K0-enable-banking.md`, the cropped artwork in `android/feature/cards/` and `web/src/features/cards/assets/`, `PLAN-K0.md` to `PLAN-K3.md`, `prompts/README.md`, this file).
- The Pi image build of the pushed `main`, then the owner's deploy and browser checks (§3 steps 3 and 4).
- 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.
@@ -79,13 +79,12 @@ 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)
- Land the card-purchases docs pass (orchestrator): SPEC v0.35, D-158 to D-176, `PLAN-K0.md` to `PLAN-K3.md`, `prompts/K0.md` to `K3.md`, `prompts/README.md`, this file; gate `python3 tools/contracts-check.py` (no contract file changed).
- K0 run and report (Opus agent with the owner, `prompts/K0.md`): after the owner steps of §3 item 5; then a docs agent records K0's answers as SPEC v0.36 before K1.
- K1, K2, K3 (Opus agents, after SPEC v0.36; disjoint decision ranges): K1 lands first, K2 after K1, K3 after K2. K2's card artwork follow-up if the owner's PNG arrives after K2 (one mapping line and re-recorded goldens).
- Land the K0 close-out pass (orchestrator): SPEC v0.36, D-177 to D-180, the K0 report, the cropped artwork (two PNGs, referenced by no build yet), the K plans, `prompts/README.md`, this file; gate `python3 tools/contracts-check.py` (no contract file changed).
- 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).
- 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).
- Land `agent-docs-v34` (orchestrator): SPEC v0.34, D-156 implemented, L-10 note, the version-code ARG in `server/Dockerfile` and the workflow, HANDOFF; gate `python3 tools/contracts-check.py`.
- `infra/build-images.sh` (agent, next infra pass): pass `--build-arg KVITT_VERSION_CODE=$(git rev-list --count HEAD)` to the server build as the workflow does (v0.34); without it the Mac-built image's web reports version code 1.
- Pi image with the complete web app (built from the push of `5305f37`, or of `agent-docs-v34` once landed and pushed): watch the workflow run, then the owner deploys it (§3 step 3) and checks the web client from the laptop browser (§3 step 4); HUMAN step 2 if the NPM block is not yet current. Release 140 (W6 Insights, D-155) runs on both phones; its Insights need this image.
- 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.
- Pi image with the complete web app (built from the push of `5305f37`, or of a later push of `main`): watch the workflow run, then the owner deploys it (§3 step 3) and checks the web client from the laptop browser (§3 step 4); HUMAN step 2 if the NPM block is not yet current. Release 140 (W6 Insights, D-155) runs on both phones; its Insights need this image.
- Closed by `28fa71b`: the `/opt/kvitt` leftovers in `.gitea/workflows/build-images.yaml` and `infra/build-images.sh` (compose deploy hint, `docker image inspect`). Still stale in the workflow header (agent, next infra pass): "NOT YET RUN" and "First-build duration on the Pi: unknown" (now 2 h 42 min).
- Pushed: `5305f37` (orchestrator, 2026-10-24).
- Closed by `2acfc34` (`agent-gate-docs`): `infra/README.md`'s stale `CARGO_BUILD_JOBS` text and its "Owner decisions (open)" (now answered).
@@ -108,7 +107,7 @@ Living document, rewritten in place (never a second copy). A new orchestrator se
### Web follow-ups
- Done in W0b (`5305f37`, SPEC §15.3 v0.34): shared pieces once in `components`, `lib`, `api`, `app`; `AmountField` per page language with `hideLabel` and `placeholder`; `ids.ts` typing; Vitest cache off; `/analytics` in the dev proxy and Recharts in its own chunk (vendor 516 → 132 kB); `routes.ts` builders (`links.ts` files deleted); `spend_series` and `resolve_range` in `vectors.test.ts`; W5's recurring editor on `components/editor`; a click wording for the paid-by toggle; the version code from the commit count. Testing Library's wait limit is 5 s (`web/src/test/setup.ts`, load on the Mac during parallel agents).
- Kept: `/app/settings/invites` as an alias of `/app/settings/invite` (the spec names it, D-153).
- The image's version code: `ARG KVITT_VERSION_CODE` in `server/Dockerfile` and the workflow's build argument (`agent-docs-v34`); `infra/build-images.sh` still to follow (§2).
- The image's version code: `ARG KVITT_VERSION_CODE` in `server/Dockerfile` and the workflow's build argument (`5d948f9`); `infra/build-images.sh` since `f15fa23`.
## 3. HUMAN steps pending (relay in chat)
Done 2026-10-21: the Pi install (stack running, `v1.0.1`, both containers healthy), the public health check through NPM, bootstrap, release pairing of both phones, the Splitwise import. Done 2026-10-24: both phones on release 140 (keyed, so the old step 1 is done), the first backup on Drive (AC-18.1).
@@ -118,8 +117,7 @@ Standing note for every in-app update on the Samsung phones: turn Auto Blocker o
3. Deploy the new image (the complete web app and E-34/E-35, SPEC §10.10), once the Gitea workflow run of the pushed `main` has succeeded: on the Kvitt Pi in `~/kvitt`, set `KVITT_TAG` in `.env` to the tag the run printed, then `docker compose pull && docker compose up -d`; `server` and `backup` healthy (yes/no). Release 140 runs on both phones; S-11's Insights work once this image runs.
4. Web client in a browser on the laptop (after step 3, D-156): on the phone, Inställningar › Para ihop en enhet › För mig, create a code; on the laptop open `https://kvitt.suorra.duckdns.org/app/`, "Logga in i Kvitt", type the code, log in: Home shows (yes/no). Then, each yes/no: Home and one account open; add, edit and delete an expense; Sök, export a CSV and open it in Numbers (columns and amounts right); Gör upp: record a payment and delete it; Statistik, open a category row (line chart), Insikter; each Settings page opens; on the phone Inställningar › Enheter shows `Web · <browser>`, "Ta bort åtkomst" on it, and the browser shows the login page with the removed-device message.
5. Card purchases, K0 (PLAN-K0 §8; relay one at a time, skip what is done; the application with the redirect URL `https://kvitt.suorra.duckdns.org/cards/callback` may already exist): (a) sign up at `https://enablebanking.com` (Control Panel), confirm the email, accept the Terms of Service (licence step, yes/no); (b) Control Panel › API applications › "Add a new application": Production, name "Kvitt", redirect URL exactly `https://kvitt.suorra.duckdns.org/cards/callback`, key generated in the browser, register: a `.pem` downloads (yes/no); (c) move the `.pem` into `/Users/jhag/projects/kvitt/docs/spikes/k0/.local/`, keeping its name; (d) recommended: add the `location = /cards/callback { … }` block of SPEC §10.5.2 to NPM's Advanced tab, Save, row Online (yes/no); (e) the application › "Activate by linking accounts": Sweden, SJ Prio Mastercard, BankID: the card is linked (yes/no); (f) with the partner: link her card with her BankID (yes/no, or the exact message); (g) the test app with the agent: connect, paste the address, "Uppdatera", "Bakgrundshämtning" (yes/no each); (h) the next day the same without BankID (yes/no).
6. Card artwork (when K2 runs): the SJ Prio Mastercard PNG (at least 1012 × 638 px) at `android/feature/cards/src/main/res/drawable-nodpi/card_sj_prio_mastercard.png`, and the same file at `web/src/features/cards/assets/card-sj-prio-mastercard.png` (owner cleared its use, D-173).
5. 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.
## 4. Owner questions (asked, unanswered)
- OQ-7 (SPEC §12): a notification for new card purchases? Default: none in this version (D-171).
@@ -133,8 +131,8 @@ Standing note for every in-app update on the Samsung phones: turn Auto Blocker o
- Nothing.
## 6. Numbering
- Decisions: D-001 to D-176 used, except D-008, D-009 and D-150 (never used); D-158 to D-176 are the card purchases (v0.35). Next free: **D-177**.
- Spec version: v0.35 written (card purchases, D-158 to D-176). Next **v0.36** (K0's answers, before K1).
- Decisions: D-001 to D-180 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). Next free: **D-181**.
- Spec version: v0.36 written (K0's results, D-177 to D-180). Next **v0.37** (the proposed spec text of K1, K2 and K3, recorded by a docs agent as they land).
- 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.
- 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**.