PLAN-<id>.md for U1, U2, M1, M2a, M2b, M2c-dev, M2c-pi, M3, M4, M5, M6, M7a-M7f, M8, M9, M10, M11, each with the eleven fixed sections, and a start prompt per plan in prompts/ plus prompts/README.md (dependency order and parallel waves). SPEC v0.2: owner answers D-001 (development-first, M2c split into M2c-dev and M2c-pi, Gitea registry), D-002 (local backups deleted once on Drive), D-003 (Splitwise CSV format, English and Swedish variants, category mapping), OQ-1, OQ-2, OQ-4 answered; M1's readings of ambiguous contract points stated explicitly in §14.3 to §14.5. HANDOFF §2 lists the plans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 KiB
PLAN-M2c-dev: Infra for development (D-001)
1. Goal
Give development a local server on the owner's M4 Mac: a Docker compose stack with the server (built from server/Dockerfile), optional Caddy, and an on-demand backup job, reachable by debug builds on the owner's phone over the home Wi-Fi. Set up the Firebase project and apps now, because push is needed during development.
2. Read first
- SPEC §10 Deployment & ops: the intro paragraph "Development first (D-001)", §10.2 Compose stack (service shapes, limits, logging), §10.3 Images, registry and build (the
serverandbackupimages), §10.4 Server configuration and CLI, §10.7 Firebase setup, §10.9 Backups and restore (backup steps 1 to 5). - SPEC §4 AC-2.7 and AC-2.8 (debug builds accept
http://to private LAN addresses), FR-20 Health. - SPEC §8.7 Privacy and logging, §8.9 Server hardening (container user, read-only root, pragmas).
- SPEC §11.2 (row M2c-dev), §11.3 (note M2c-dev / M2c-pi), §12 (OQ-1 answered, OQ-3 open for M2c-pi only), §14.7.
docs/decisions.mdD-001, D-002.- Files:
server/Dockerfile(from M2a, if landed),.gitignore,CLAUDE.md(BUILD AND VERIFY: Cargo and Docker builds; EXECUTION PROTOCOLS: Stay inside the repository, Stop and ask; THE OWNER).
3. Already established
- During development the server runs on the Mac, via
infra/dev/compose.yamlor plaincargo run; debug builds pair against it (D-001, §10 intro). - The dev stack has
server, an optionalcaddywith a self-signed certificate or no TLS for local use, andbackuprunnable on demand without the Drive upload (D-001, §10 intro, §11.2). backup.shsteps:.backupsnapshot,PRAGMA integrity_checkmust printok, gzip and pictures tar with a UTC stamp, rclone copy only,last-successmarker; whenRCLONE_FOLDER_IDis empty step 4 is skipped with a log line and local files are kept (§10.9, D-001). On the Pi, local files are deleted as soon as confirmed on Drive (D-002; built by M2c-pi).backupimage:alpinewithsqlite,gzip,tar,rclone,infra/backup/backup.shrun bycrond(§10.3).- Server config is only the env variables of §10.4;
KVITT_FCM_CREDENTIALSdefault/secrets/fcm-service-account.json, push disabled with a warning when missing;KVITT_FCM_PROJECT_IDkvitt-1337(§10.4). - Firebase: project
kvitt-1337; Android appsse.hagstedt.kvitt("Kvitt") andse.hagstedt.kvitt.debug("Kvitt debug"); one mergedandroid/app/google-services.json(git-ignored); service accountkvitt-fcmwithroles/firebasecloudmessaging.admin, key atinfra/secrets/fcm-service-account.json; stop if an organisation policy blocks key creation; free tier, no billing (§10.7). - The registry is Gitea at
http://gitea.lan; not used by this plan (D-001, OQ-1). - Secrets live in
infra/secrets/(git-ignored) and are never committed (§10 intro,.gitignore). - Never build Docker images while a Gradle build runs (CLAUDE.md).
4. Owned paths
From SPEC §14.7, verbatim: infra/** (owner "M2c-dev, then M2c-pi (D-001)"; after landing: "M10 updates runbooks").
Also, from §11.2 (row M2c-dev): the git-ignored android/app/google-services.json and infra/secrets/ contents, written in the main checkout /Users/jhag/projects/kvitt (where every worktree copies them from, CLAUDE.md), only with the start prompt's allowance.
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator.
5. Deliverables
infra/dev/compose.yaml:server(image built fromserver/Dockerfile, data in a named volume, FCM key mounted read-only frominfra/secrets/,KVITT_PUBLIC_URLset to the Mac's LAN URL, user 10001, read-only root, no capabilities,no-new-privileges), optionalcaddy,backup(on demand), logging andTZ=Europe/Stockholmas §10.2.infra/dev/Caddyfile: only if the stack includes Caddy (self-signed viatls internal, or plain HTTP).infra/dev/README.md: start, stop, logs,bootstrapandpair-devicecommands, finding the Mac's LAN address, pairing a debug build over the home Wi-Fi, running a backup, resetting the dev data.infra/backup/Dockerfile,infra/backup/backup.sh: the backup image and script of §10.9 with the empty-RCLONE_FOLDER_IDlocal mode.infra/env.example: the.envkeys of §10.2 with dev-friendly comments (no secret values).- Firebase project
kvitt-1337with both Android apps (cloud state, no repository file). infra/secrets/fcm-service-account.json(git-ignored, main checkout).android/app/google-services.jsonwith both clients (git-ignored, main checkout).
6. Steps
- Worktree. From the main checkout
/Users/jhag/projects/kvitt:git worktree add .claude/worktrees/agent-m2c-dev -b agent-m2c-dev main, then in.claude/worktrees/agent-m2c-dev:git reset --hard main. Copy from the main checkout whatever already exists ofinfra/secrets/(mkdir -p infra/secrets && cp -R /Users/jhag/projects/kvitt/infra/secrets/. infra/secrets/if present). Runtools/install-git-hooks.sh. - Read §2 in order. Check the tools:
command -v docker gcloud firebase shellcheck rclone,docker buildx version,docker compose version. Ifdocker,gcloudorfirebaseis missing, stop and ask the owner (do not install). - Versions: resolve from official sources and record: the
alpineimage tag and digest (Docker Hub official image),rclonepackage version in that Alpine release, thecaddyimage version and digest if Caddy is used (Docker Hub official image). Pin by version and digest. - Write
infra/backup/backup.shandinfra/backup/Dockerfile; runshellcheckon the script. - Write
infra/dev/compose.yaml(andinfra/dev/Caddyfileif used),infra/env.example,infra/dev/README.md. Record which TLS option (self-signed or none) was chosen and why. - Run
docker compose -f infra/dev/compose.yaml config. - Firebase (needs HUMAN step H-1, §8). After the owner signed in, run §10.7 steps 2 to 6 exactly:
gcloud config set project kvitt-1337;gcloud services enable fcm.googleapis.com firebase.googleapis.com;firebase projects:addfirebase kvitt-1337(skip if already a Firebase project);firebase apps:create ANDROID "Kvitt" --package-name se.hagstedt.kvitt;firebase apps:create ANDROID "Kvitt debug" --package-name se.hagstedt.kvitt.debug;firebase apps:sdkconfig ANDROID <app id>for each, merged into oneandroid/app/google-services.jsonin the main checkout;gcloud iam service-accounts create kvitt-fcm --display-name "Kvitt FCM sender"; grantroles/firebasecloudmessaging.adminon the project;gcloud iam service-accounts keys create /Users/jhag/projects/kvitt/infra/secrets/fcm-service-account.json --iam-account kvitt-fcm@kvitt-1337.iam.gserviceaccount.com. If an organisation policy blocks key creation, stop and report; change no policy. Copying the key to the Pi (§10.7 step 7) belongs to M2c-pi. - Health check (needs M2a on
main; if not landed, report this part as outstanding): build the server image locally (no Gradle build running),docker compose -f infra/dev/compose.yaml up -d,curl -fsS http://localhost:<port>/healthon the Mac; then from the owner's phone on the home Wi-Fi (see §8 H-3). - Backup check: run the backup job once on demand (
docker compose -f infra/dev/compose.yaml run --rm backup /backup.shor as written in the README), thensqlite3 <snapshot> "PRAGMA integrity_check"printsok. - Run
shellcheck infra/**/*.sh(inzshorbashwithshopt -s globstar, so**recurses) if shellcheck is installed. - Run the gate (§7). Commit on
agent-m2c-dev(never a secret),git rebase main, rerun the gate. Never merge, push or remove the worktree. Leave the dev stack running or stopped as the start prompt says, and say which in the report.
7. Gate
From SPEC §11.2, verbatim: "docker compose -f infra/dev/compose.yaml config succeeds; shellcheck infra/**/*.sh clean if shellcheck is installed; on the Mac (with the M2a image): GET /health answers 200 from the Mac and from the owner's phone on the home Wi-Fi; one local backup run produces a snapshot that passes PRAGMA integrity_check".
Commands: docker compose -f infra/dev/compose.yaml config; shellcheck infra/**/*.sh (globstar on); curl -fsS http://localhost:<port>/health; the phone check of H-3; sqlite3 <snapshot> "PRAGMA integrity_check".
Green means: config exits 0; shellcheck prints nothing and exits 0 (or "not installed" stated); /health returns 200 with "status":"ok" from the Mac and the phone; integrity check prints ok.
8. HUMAN steps
From SPEC §11.2, verbatim: "gcloud auth login, firebase login". No open question blocks this plan (OQ-1 answered; OQ-3 only blocks M2c-pi).
- H-1, before step 7, on the owner's Mac:
gcloud auth login(sign in with the owner's Google account in the browser that opens), thenfirebase login(same account). Expected result: both commands end with a "logged in as" line. - H-2 (only if step 2 finds a tool missing; not a §11.2 step): the owner decides whether and how to install it.
- H-3, in step 8 (needed by the gate; not listed in §11.2): if the agent cannot query the server from the phone over USB itself, the owner opens
http://<Mac LAN address>:<port>/healthin Chrome on the phone on the home Wi-Fi and answers yes/no: the page shows"status":"ok". If macOS asks whether Docker may accept incoming connections, the owner taps Allow.
9. Out of scope
- Everything that concerns the Pi (M2c-pi):
infra/compose.yaml,infra/Caddyfile, cert-sync image,infra/build-images.sh,infra/deploy.sh,infra/publish-apk.sh,infra/README.md,infra/RESTORE.md, the Gitea registry and Gitea Actions, router forwarding, the Drive upload andrclone authorize, copying secrets to the Pi. - Server code (M2a, M3, M4); Android code and Gradle files (M2b and later).
- Changing org policies or billing in Google Cloud.
10. Report
- Branch
agent-m2c-dev, head commit hash after rebasing onmain. - Gate output lines:
configexit status, shellcheck result,/healthresponses (Mac, phone), integrity check output. - Versions chosen (images with digests, rclone) with sources; TLS option chosen.
- Firebase: app ids created (ids are not secrets), service account email, whether
google-services.jsonholds both clients; any policy block. - Where the secrets were written (paths only, never contents).
- Whether the dev stack is left running, and its URL.
- Numbering used or
TODO(spec)occurrences. - HUMAN steps done and outstanding, worded for relay.
- 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.