SPEC v0.3: orchestrator resolutions of the plans' open points
Records D-004 and D-015 to D-025: shells and ownership, server seams (router middleware, rate_limit layer, tasks::start, AuditWriter and PushQueue traits, next_occurrence_date in kvitt-domain, M3 owns recurring generation, M4 lands after M3), OpenAPI parsed-value equality, feature convention and strings, empty fake scenario and resolveRange exception, shared ledger rows and editor fields in :core:design, no bundled font, pruning at 04:00, development infrastructure as M2c-dev built it, M2c-pi allowances, release keystore, production import procedure. Plans and prompts close their open points; HANDOFF rewritten to the real state; CLAUDE.md: implementation agents never edit SPEC.md or docs/decisions.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+30
-19
@@ -14,7 +14,8 @@ Create the Rust Cargo workspace with its three crates, a running `kvitt-server`
|
||||
8. SPEC §10.3 Images, registry and build (the `server` image), §10.4 Server configuration and CLI.
|
||||
9. SPEC §4 FR-20 Health and AC-1.5 (setup status).
|
||||
10. SPEC §11.2 (row M2a), §11.3 (notes M1, M2a), §14.7 Module layout and ownership map, §14.8 Contract files and the change rule.
|
||||
11. Files: `contracts/openapi.yaml`, `contracts/schema/server-v1.sql`, `contracts/test-vectors/*.json`, `contracts/examples/**`, `CLAUDE.md` (BUILD AND VERIFY, ARCHITECTURE RULES, ANTI-PATTERNS).
|
||||
11. SPEC §6.3 "Server seams (D-015)", §9.5 Recurring generation (`next_occurrence_date`, clamping), §14.5.2 row "Recurring dates"; `docs/decisions.md` D-004, D-015, D-016.
|
||||
12. Files: `contracts/openapi.yaml`, `contracts/schema/server-v1.sql`, `contracts/test-vectors/*.json`, `contracts/examples/**`, `CLAUDE.md` (BUILD AND VERIFY, ARCHITECTURE RULES, ANTI-PATTERNS).
|
||||
|
||||
## 3. Already established
|
||||
- Rust stable, edition 2024; axum on tokio multi-thread runtime with 2 worker threads; SQLite via sqlx, WAL, migrations embedded with `sqlx::migrate!`; serde; utoipa; tracing JSON lines to stdout; chrono + chrono-tz; uuid v7; image crate; reqwest with rustls; jsonwebtoken (§6.3). M2a needs only the subset it uses; later plans add the rest.
|
||||
@@ -31,24 +32,33 @@ Create the Rust Cargo workspace with its three crates, a running `kvitt-server`
|
||||
- Logs never contain tokens, invite codes, names, descriptions, notes, amounts or picture bytes; they log route templates, not raw paths (§8.7).
|
||||
- No `unwrap()`/`expect()` outside `main` start-up, no `unsafe` (CLAUDE.md).
|
||||
- M1 has landed (commit `c6df2ab`): `contracts/openapi.yaml` exists with 33 operations and 60 schemas, and the utoipa document must match it; differences may only be representational. M1's readings are now explicit in SPEC v0.2 (§14.3.1, §14.3.2 `not_implemented` on no operation, §14.3.4 E-1/E-3/E-12/E-17/E-31/E-33, §14.4.3 result key sets, §14.5.1, §14.5.2, §14.5.5); do not re-decide them.
|
||||
- `contracts/openapi.yaml` may be changed by M2a only for representational differences that do not change meaning (key order, `$ref` naming), recorded in the report; anything semantic is a contract change (§11.3, §14.8).
|
||||
- `contracts/openapi.yaml` may be changed by M2a only for representational differences that do not change meaning (key order, `$ref` naming, description wording), listed in the report, with no spec bump; anything semantic is a contract change (§11.3, §14.8, D-016).
|
||||
|
||||
## 4. Owned paths
|
||||
From SPEC §14.7, verbatim:
|
||||
- `server/Cargo.toml`, `server/rust-toolchain.toml`, `server/Dockerfile`, `server/crates/kvitt-domain/**` (owner M2a; after landing: "contract-change plans only for `kvitt-domain`; M3, M4 and M9 may add a dependency line to a crate's `Cargo.toml` (second to land resolves `Cargo.lock` on rebase)").
|
||||
- `kvitt-server`: `migrations/`, `src/{main,lib,config,error,router,openapi,auth,time}.rs`, `src/db/**`, `src/wire/**`, `src/api/{mod,health,setup}.rs`, `src/cli/{mod,healthcheck}.rs` (owner M2a; after landing: "contract-change plans; M3/M4 may add one line per new module to `mod.rs` files they need").
|
||||
- Exception granted by §11.3: representational (non-semantic) edits to `contracts/openapi.yaml`.
|
||||
From SPEC §14.7 (v0.3):
|
||||
- `server/Cargo.toml`, `server/Cargo.lock`, every crate's `Cargo.toml`, `server/rust-toolchain.toml`, `server/Dockerfile`, `server/crates/kvitt-domain/**` (after landing: contract-change plans only for `kvitt-domain`; M3, M4 and M9 may add dependency lines to a crate's `Cargo.toml`).
|
||||
- `kvitt-server`: `migrations/`, `src/{main,lib,config,error,router,openapi,auth,time}.rs` (including `serve` and `migrate`), `src/db/**`, `src/wire/**`, `src/api/{mod,health,setup}.rs`, `src/cli/{mod,healthcheck}.rs`, and M2a's own tests (any `tests/*.rs` without the prefixes `core_`, `sync_`, `scheduler_`, `push_`).
|
||||
- Shell files in later milestones' paths (D-004), which pass to their owners as soon as M2a lands: the 501 handlers in `src/api/{invites,landing,me,pictures,devices,accounts,categories,recurring,proposal,apk,rate_limit,sync}.rs`, stub subcommands in `src/cli/{bootstrap,pair_device,rotate_epoch}.rs`, `src/tasks/mod.rs` (a `start` function that does nothing yet), `src/audit/` and `src/push/` (the `AuditWriter` and `PushQueue` traits with no-op implementations, D-015), and `server/crates/kvitt-import/` (a crate that builds, with a `main.rs` that prints usage and exits non-zero).
|
||||
- Exception granted by §11.3 and §14.8: representational (non-semantic) edits to `contracts/openapi.yaml` (D-016).
|
||||
|
||||
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator.
|
||||
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator. Never edit `SPEC.md` or `docs/decisions.md`: put proposed decision entries in the report (CLAUDE.md › Orchestration).
|
||||
|
||||
Open points the orchestrator must settle in the start prompt's notes (stop and ask if they are not settled): (a) the 501 stub handlers for E-3 to E-30, E-33 and E-31/E-32 belong in files §14.7 gives to M3 (`src/api/{invites,landing,me,pictures,devices,accounts,categories,recurring,proposal,apk}.rs`) and M4 (`src/api/sync.rs`), the subcommand stubs in `src/cli/{bootstrap,pair_device,rotate_epoch}.rs`, and the third crate in `server/crates/kvitt-import/**` (M9): whether M2a creates these files as stubs that ownership then passes on; (b) `server/Cargo.lock`, `server/crates/kvitt-server/Cargo.toml` and `server/crates/kvitt-import/Cargo.toml` are not in the ownership table; (c) names for M2a's own integration tests under `kvitt-server/tests/` (M3 has `core_*`, M4 `sync_*`, `scheduler_*`, `push_*`); (d) where the protocol-header check, body-size limits (§8.6) and the rate-limit layer that M3 writes in `src/ratelimit.rs` attach to `router.rs`; (e) how M4's scheduler and push sender get started by `serve` (`main.rs`/`lib.rs`) when M4 may only add `mod.rs` lines; (f) whether the OpenAPI equality test compares parsed values or text.
|
||||
Resolved open points (SPEC v0.3; the start prompt may repeat them):
|
||||
- (a) Stubs: M2a creates the shell files listed above (D-004); M3, M4 and M9 own their bodies.
|
||||
- (b) `server/Cargo.lock` and every `Cargo.toml` are M2a's; later milestones add dependency lines (§14.7).
|
||||
- (c) M2a's own integration tests use any name without the prefixes `core_`, `sync_`, `scheduler_`, `push_`; list the names in the report.
|
||||
- (d) The protocol-header check (426), `X-Kvitt-Server-Version` and the §8.6 body limits are layers in `router.rs`. M3's rate limit is a layer in `src/api/rate_limit.rs` attached at one line in `router.rs` that M2a marks with a comment (attach a pass-through layer from the shell until M3 fills it) (D-015).
|
||||
- (e) `serve` calls `tasks::start` from `src/tasks/mod.rs` once after migrations; M4 owns `src/tasks/**`. `AuditWriter` and `PushQueue` are traits with no-op implementations passed through the application state; M4 replaces the implementations, M3 calls the traits (D-015).
|
||||
- (f) The OpenAPI equality test compares parsed values (YAML parsed and compared as JSON values), not text (D-016).
|
||||
- Also (D-015): implement `next_occurrence_date` and the day clamping of §9.5 in `kvitt-domain` (`src/recurring.rs`, pure, Rust only, unit tests for every AC-9.2 clamping case and the `null` cases); report the exact signatures.
|
||||
- Also (§10.3): `rust-toolchain.toml` pins the exact current stable Rust release, and the Dockerfile's stage 1 uses `rust:<that version>-bookworm`.
|
||||
|
||||
## 5. Deliverables
|
||||
- `server/Cargo.toml`: workspace with members `crates/kvitt-domain`, `crates/kvitt-server`, `crates/kvitt-import`; shared lints (deny `unsafe_code`, clippy `unwrap_used`/`expect_used` outside tests).
|
||||
- `server/Cargo.lock`: committed.
|
||||
- `server/rust-toolchain.toml`: the Rust stable version chosen (see step 3).
|
||||
- `server/rust-toolchain.toml`: the exact current stable Rust release (see step 3).
|
||||
- `server/Dockerfile`: the two-stage arm64 image of §10.3.
|
||||
- `server/crates/kvitt-domain/Cargo.toml`, `src/lib.rs`, `src/split.rs`, `src/balance.rs`, `src/allocation.rs`, `src/settlement.rs`: types and functions of §14.5.1 and §14.5.2.
|
||||
- `server/crates/kvitt-domain/Cargo.toml`, `src/lib.rs`, `src/split.rs`, `src/balance.rs`, `src/allocation.rs`, `src/settlement.rs`, `src/recurring.rs`: types and functions of §14.5.1 and §14.5.2, including the Rust-only recurring dates.
|
||||
- `server/crates/kvitt-domain/tests/vectors.rs`: runs every case of the five Rust vector files in `contracts/test-vectors/`.
|
||||
- `server/crates/kvitt-server/Cargo.toml`, `src/main.rs` (CLI parsing, start-up), `src/lib.rs` (public modules for `kvitt-import`), `src/config.rs` (§10.4 variables), `src/error.rs` (§14.3.2), `src/router.rs` (every route of §7, layers of §14.3.1), `src/openapi.rs` (utoipa `ApiDoc`), `src/auth.rs` (device and admin extractors, §8.2, §8.4), `src/time.rs` (fixed RFC 3339 form, `Europe/Stockholm` constant, injectable clock).
|
||||
- `src/db/mod.rs`, `src/db/pool.rs` (pool with the §8.9 pragmas, `BEGIN IMMEDIATE` helper), `src/db/sequence.rs` (next `server_version`).
|
||||
@@ -56,19 +66,20 @@ Open points the orchestrator must settle in the start prompt's notes (stop and a
|
||||
- `src/wire/{mod,user,device,invite,account,category,expense,settlement,recurring,audit,sync}.rs`: serde wire objects of §14.3.3 and §14.4 with utoipa schemas, arrays sorted as §14.3.3 says.
|
||||
- `src/api/mod.rs`, `src/api/health.rs` (E-1), `src/api/setup.rs` (E-2).
|
||||
- `src/cli/mod.rs` (subcommand dispatch, `serve`, `migrate`), `src/cli/healthcheck.rs`.
|
||||
- 501 stubs for the remaining routes and subcommands, placed as the orchestrator decides (open point a).
|
||||
- Integration tests in `kvitt-server/tests/` (names per open point c): OpenAPI document equals `contracts/openapi.yaml`; `0001_init.sql` equals `contracts/schema/server-v1.sql` byte for byte; every route returns 501 `not_implemented` with the error shape; E-1 and E-2 responses; auth extractor cases (missing, unknown, revoked token; member on admin route); protocol header 426; error JSON for every code serialises like `contracts/examples/errors/<code>.json`.
|
||||
- `server/crates/kvitt-import/` minimal crate that builds (if open point a says M2a creates it).
|
||||
- `src/tasks/mod.rs` with `start`; `src/audit/` and `src/push/` with the `AuditWriter` and `PushQueue` traits and no-op implementations (D-015).
|
||||
- 501 stubs for the remaining routes and stub subcommands in the shell files of §4 (D-004).
|
||||
- Integration tests in `kvitt-server/tests/` (names per resolved point c): OpenAPI document equals `contracts/openapi.yaml`; `0001_init.sql` equals `contracts/schema/server-v1.sql` byte for byte; every route returns 501 `not_implemented` with the error shape; E-1 and E-2 responses; auth extractor cases (missing, unknown, revoked token; member on admin route); protocol header 426; error JSON for every code serialises like `contracts/examples/errors/<code>.json`.
|
||||
- `server/crates/kvitt-import/`: a crate that builds and depends on `kvitt-domain` and `kvitt-server`, with a usage-only `main.rs` (D-004).
|
||||
|
||||
## 6. Steps
|
||||
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m2a -b agent-m2a main`, then in `.claude/worktrees/agent-m2a`: `git reset --hard main`. No git-ignored files are needed; copy none. Run `tools/install-git-hooks.sh`.
|
||||
2. Read §2 in order. Confirm `contracts/` exists on `main` (M1 landed); if not, stop and report.
|
||||
3. Versions: "Rust stable" (§6.3) and `rust:<stable>-bookworm` (§10.3). Look up the current stable Rust release on the official Rust site (rust-lang.org / the Rust release blog) and the matching tag on the official Docker Hub `rust` image page; pin the same version in `rust-toolchain.toml` and the Dockerfile. Look up current stable versions of each crate you add on crates.io / docs.rs and the digest of `gcr.io/distroless/cc-debian12:nonroot`. Confirm current axum, sqlx and utoipa usage from their official docs (CLAUDE.md "Fast-moving APIs"). Record every version in the report.
|
||||
3. Versions: "Rust stable" (§6.3) and `rust:<stable>-bookworm` (§10.3). Look up the current stable Rust release on the official Rust site (rust-lang.org / the Rust release blog) and the matching tag on the official Docker Hub `rust` image page; pin that exact version (for example `1.NN.P`, not `stable`) in `rust-toolchain.toml` and the same version in the Dockerfile's `rust:<version>-bookworm`. Look up current stable versions of each crate you add on crates.io / docs.rs and the digest of `gcr.io/distroless/cc-debian12:nonroot`. Confirm current axum, sqlx and utoipa usage from their official docs (CLAUDE.md "Fast-moving APIs"). Record every version in the report.
|
||||
4. Write `kvitt-domain` test-first: `tests/vectors.rs` loads `../../../contracts/test-vectors/{compute_split,compute_balances,propose_allocation,validate_settlement,over_settled_accounts}.json` and checks every case; then implement §14.5.2 with the error order of the enums. Run `cargo test -p kvitt-domain`.
|
||||
5. Write `config.rs`, `time.rs`, `error.rs`, `db/**`, `migrations/0001_init.sql` (copy the bytes with `cp contracts/schema/server-v1.sql server/crates/kvitt-server/migrations/0001_init.sql`), `wire/**`.
|
||||
6. Write `auth.rs` and `router.rs` with every route of §7, the §14.3.1 headers and limits, and stub handlers per open point (a).
|
||||
7. Write `openapi.rs` with utoipa annotations on every handler and wire type; make the generated document equal `contracts/openapi.yaml` (comparison per open point f). Only representational changes to the YAML are allowed; list each in the report.
|
||||
8. Implement E-1, E-2, `serve` (graceful SIGTERM drain up to 10 s, §10.4), `migrate`, `healthcheck`.
|
||||
6. Write `auth.rs` and `router.rs` with every route of §7, the §14.3.1 headers and §8.6 body limits as layers, the marked rate-limit line, and stub handlers in the shell files (resolved points a, d).
|
||||
7. Write `openapi.rs` with utoipa annotations on every handler and wire type; make the generated document equal `contracts/openapi.yaml` (parsed values, resolved point f). Only representational changes to the YAML are allowed; list each in the report.
|
||||
8. Implement E-1, E-2, `serve` (graceful SIGTERM drain up to 10 s, §10.4; calls `tasks::start`), `migrate`, `healthcheck`; write `kvitt-domain`'s recurring dates test-first.
|
||||
9. Write the integration tests of §5 with an in-memory or temp-file SQLite and an injected clock.
|
||||
10. Write `server/Dockerfile`. Build it once locally only if no Gradle build is running (CLAUDE.md); record the image size. Do not push it anywhere.
|
||||
11. Run the gate (§7) in `server/`. Commit on `agent-m2a`, `git rebase main`, rerun the gate. Never merge, push or remove the worktree.
|
||||
@@ -95,8 +106,8 @@ From SPEC §11.2, verbatim: "none".
|
||||
- Gate output: fmt and clippy exit status, every `test result:` line, and the names of the OpenAPI match, migration identity and vector tests.
|
||||
- Versions chosen (Rust, Docker base images with digests, every crate) with the official source.
|
||||
- Representational changes made to `contracts/openapi.yaml`, one line each.
|
||||
- How each open point (a) to (f) was resolved, as given in the start prompt.
|
||||
- Numbering used (decision ids, spec version) or `TODO(spec)` occurrences.
|
||||
- How each resolved point (a) to (f) was applied; the exact `next_occurrence_date` and clamping signatures; the marked rate-limit line.
|
||||
- Proposed decision entries and spec text (numbered from the start prompt's range), never written to `SPEC.md` or `docs/decisions.md` by this agent; or `TODO(spec)` occurrences.
|
||||
- Contract requests, open questions, spec gaps (with section numbers).
|
||||
- Docker image build result (size) if built.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user