Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
8.8 KiB
PLAN-M12b: Category contract v2, server
1. Goal
Bring the landed server and importer to the category contract v2 (D-053, D-054): migration 0002 adds the four new default categories to every database and moves the server to schema version 2; the token lists, the default-key enum and the importer's category table follow the spec. Existing databases, the development stack included, upgrade in place without losing a row.
2. Read first
- SPEC §14.1 and §14.1.1 Migration 0002 (all of it), §14.8 (contract files, migration texts).
- SPEC AC-7.2 (fourteen defaults), §9.7 (33 icons, 14 colour tokens), §14.3.3 Category, §14.3.4 E-1.
- SPEC §10.8 Splitwise import (the schema check and the new category table), D-044, D-046, D-053.
- SPEC §8.9 (pragmas), §14.4.2 (pull: the new rows reach phones by
server_version). - SPEC §11.2 (row M12b), §11.3 (note M12), §14.7 (row M12b).
docs/decisions.mdD-053, D-054.- Files:
git show aa765bb(M12a's commit message lists what it held back),contracts/schema/migrations/0002_categories_v2.sqlandtools/contracts-check.py(from M12a),server/crates/kvitt-server/migrations/0001_init.sql,src/db/mod.rs,src/api/validate.rs,src/wire/category.rs,src/wire/mod.rs,src/openapi.rs,tests/skeleton/migration.rs,tests/skeleton/routes.rs(E-1schema_version),tests/openapi_contract.rs, the tests intests/core_api/andtests/sync_api/that count or list categories,server/crates/kvitt-import/src/categories.rs,data/splitwise_categories.csv,tests/import.rs(category_table_matches_the_spec),contracts/examples/E-31-sync-pull/response.json. CLAUDE.md(server gate, nounwrapoutside tests, every schema change gets a migration and a migration test).
3. Already established
- Migration 0002 is the SQL of §14.1.1 byte for byte;
server-v1.sqland0001_init.sqlstay unchanged (§14.1.1). - It rebuilds
categoriesunderPRAGMA defer_foreign_keys = ONinside sqlx's per-migration transaction (foreign keys stay ON), inserts four rows withserver_versionfromsync_sequence, setsmeta.schema_versionto2(§14.1.1). M12a's checker (aa765bb) loads it on a fresh and on an in-use v1 database with foreign keys on, the way sqlx does; the docs agent's own run of an equivalent text with the SQLite the server bundles (3.51.x, 3.51.3 throughlibsqlite3-sys0.37.0) also committed withforeign_key_checkempty. SCHEMA_VERSION= 2; E-1 reports it; the importer's check reads that constant, so it follows (§10.8, §14.1.1; D-046 revised by D-054).- Icons: the 30 of v1 plus
key,shield,account_balance; colours: the 12 of v1 plusslate,amber(§9.7). Hex values are irrelevant to the server. DefaultCategoryKeyhas the fourteen keys in bothcontracts/openapi.yamland the generated document. M12a (aa765bb) held back the contract side, so this plan editscontracts/openapi.yaml(that enum only) and adds the category examples M12a held back (see its commit message), then the OpenAPI match test compares the two.- Importer table per §10.8 (D-053): Rent/Mortgage/Hyra/Avbetalning/Amortering
housing; Insurance/Försäkringarinsurance; Childcare/Barnomsorgchildren; Taxes/Skatterfees; Swedish El, Värme/gas, Vatten, Sophämtning (utilities) and Husdjur (home) added.category_table_matches_the_specparses SPEC §10.8, so it fails onmainfrom SPEC v0.8 until this plan lands (the orchestrator lands them close together). - The E-31 example lists two categories, not the defaults; change it only if it enumerates the defaults.
4. Owned paths
From SPEC §14.7 (row M12b, a contract-change plan): contracts/openapi.yaml (DefaultCategoryKey only) and the category examples under contracts/examples/ that M12a held back (with python3 tools/contracts-check.py still passing); server/crates/kvitt-server/migrations/0002_categories_v2.sql, src/db/mod.rs (SCHEMA_VERSION), src/api/validate.rs (token lists), src/wire/category.rs and src/wire/mod.rs (key enum), and the tests of these (tests/skeleton/**, and the category-counting lines of tests/core_api/** and tests/sync_api/**); server/crates/kvitt-import/data/splitwise_categories.csv, src/categories.rs and its tests.
Edit nothing outside these paths; a needed change elsewhere (including contracts/**) is a contract request to the orchestrator.
5. Deliverables
server/crates/kvitt-server/migrations/0002_categories_v2.sql: byte-identical tocontracts/schema/migrations/0002_categories_v2.sql.- An identity test asserting that equality (next to the existing 0001 identity test).
SCHEMA_VERSION= 2 and the E-1 tests updated.- A seed test: on a fresh database, the fourteen default rows exactly per AC-7.2 and §14.1.1 (ids, keys, icons, colours, sort orders, null names,
server_version11 to 14 after the ten seeds); on a v1 database (built from0001_init.sqlalone, then data inserted: a custom category, a renamed and an archived default, expenses and a recurring template referencing categories) upgraded through the migrator: every earlier row unchanged, the four new rows withserver_versions above the old sequence value,PRAGMA foreign_key_checkempty,meta.schema_version2; a pull from cursor = the old sequence value returns exactly the four new categories. validate.rs: icon list of 33 and colour list of 14 (§9.7), with tests for the new tokens.contracts/openapi.yamlDefaultCategoryKeywith the fourteen keys and the held-back category examples; the default-key enum insrc/wire/with the fourteen keys; the OpenAPI match test andpython3 tools/contracts-check.pygreen.splitwise_categories.csvper §10.8;src/categories.rskey list of fourteen;category_table_matches_the_specgreen; an importer test that a Försäkringar / Insurance row maps toinsurance.- Existing tests that assumed ten defaults updated, each with the reason in the report.
6. Steps
- Worktree. From the main checkout
/Users/jhag/projects/kvitt:git worktree add .claude/worktrees/agent-m12b -b agent-m12b main, then in.claude/worktrees/agent-m12b:git reset --hard main. No git-ignored files are needed. Runtools/install-git-hooks.sh. - Read §2 in order. Confirm M12a has landed (
contracts/schema/migrations/0002_categories_v2.sqlexists andpython3 tools/contracts-check.pypasses onmain); if not, stop and report. - Copy the migration file; add the identity test; bump
SCHEMA_VERSION; run the server gate and list every failing test that counts categories or checks the schema version. - Write the seed and upgrade tests (§5), then fix the lists, the enum and the failing tests until green.
- Update the importer table and key list; run
category_table_matches_the_spec; add the insurance mapping test. - Run the gate (§7). Commit on
agent-m12b,git rebase main, rerun the gate. Never merge, push or remove the worktree. - Do not migrate the running development stack's database yourself: report the command for the orchestrator (
kvitt-server migrateruns at start, so a restart with the new image upgrades it; take a backup first with the dev backup script).
7. Gate
From SPEC §11.2, verbatim: "Server gate; python3 tools/contracts-check.py passes".
Command, from server/: cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test; and python3 tools/contracts-check.py from the repository root (this plan edits contracts/).
Green means: all three exit 0 in this session; the test result: lines show 0 failed and include the identity, seed, upgrade, OpenAPI match and importer parity tests.
8. HUMAN steps
From SPEC §11.2, verbatim: "none".
9. Out of scope
contracts/**beyond the OpenAPI enum and the held-back examples, Android (M12c),server-v1.sqland0001_init.sql(byte-locked).- Any change to sync, push or the audit writer; the four new rows get no audit entries and no pushes (they are seed data like the v1 seeds).
- Running the importer against the real exports, or migrating the development or production database.
10. Report
- Branch
agent-m12b, head commit hash after rebasing onmain. - Gate output: fmt and clippy exit status, every
test result:line. - Tests changed because they assumed ten defaults or schema 1, each with its reason.
- SQLite version of the bundled
libsqlite3-sysand confirmation that the migration ran under sqlx's transaction with foreign keys on. - The upgrade steps for the development stack, worded for the orchestrator.
- Proposed decision entries (never written to
SPEC.mdordocs/decisions.mdby this agent) orTODO(spec)occurrences. - Contract requests, open questions, spec gaps (with section numbers).
11. Numbering
Decision ids, spec version and schema version are assigned by the orchestrator in the start prompt; use TODO(spec) if none was given. This plan implements server schema version 2 (D-054); any further schema change would be 3.