76ee957f41
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>
82 lines
8.8 KiB
Markdown
82 lines
8.8 KiB
Markdown
# PLAN-M10: First release
|
|
|
|
## 1. Goal
|
|
Ship the first signed release to both phones from the production server on the Pi: release keystore, signed APK published, both phones paired, push working both ways, nightly backup on Drive, a restore drill, and the memory check. Record the evidence in `docs/validation/first-release.md` and `docs/validation/restore-drill.md`.
|
|
|
|
## 2. Read first
|
|
1. SPEC §10.6 APK hosting and release, §10.9 Backups and restore, §10.10 Operations, §10.4 Server configuration and CLI (`bootstrap`, `rotate-epoch`), §10.7 (step 7, the FCM key on the Pi).
|
|
2. SPEC §4 FR-1, FR-2 (pairing both phones), FR-13 Push notifications, FR-16 In-app update, FR-18 Backups and restore.
|
|
3. SPEC §8.5 Transport (release network security config), §14.4.8 Epoch recovery.
|
|
4. SPEC §11.2 (row M10), §11.3 (note M10), §14.7.
|
|
5. `infra/README.md`, `infra/RESTORE.md`, `infra/publish-apk.sh`, `infra/deploy.sh` (from M2c-pi); `android/app/build.gradle.kts` (signing config, from M8).
|
|
6. `CLAUDE.md` (THE PHONE, THE OWNER: confirm before anything destructive; EXECUTION PROTOCOLS: Reports are deliverables).
|
|
|
|
## 3. Already established
|
|
- `version_code` = `git rev-list --count HEAD` of the released commit; `version_name` = `YYYY.MM.DD-<short sha>`; passed as `kvitt.versionCode`/`kvitt.versionName` by the release script (§10.6).
|
|
- Release keystore `kvitt-release.jks`, alias `kvitt`, RSA 4096, validity 10000 days, created once by the owner with the `keytool` command of §10.6 in a directory outside the repository (D-024); `android/local.properties` holds its absolute path and the secrets in `kvitt.release.storeFile`, `kvitt.release.storePassword`, `kvitt.release.keyAlias`, `kvitt.release.keyPassword`; the owner keeps a copy in a password manager; losing it means reinstalling (§10.6).
|
|
- `infra/publish-apk.sh` builds `:app:assembleRelease` through `tools/gradle-serial.sh`, writes `apk/kvitt-<version_code>.apk` and an atomic `apk/latest.json`, keeps the newest 5 (§10.6).
|
|
- The landing page links "Download Kvitt" to the latest APK; the first install asks to allow "Install unknown apps" (AC-2.5, AC-16.3).
|
|
- Pairing: `kvitt-server bootstrap` on the Pi prints the admin code (FR-1); the admin creates the partner invite in Settings and shares it (AC-2.1); after pairing the app syncs and asks for notifications (AC-2.9).
|
|
- Push both ways: a change by one user notifies the other user's devices (AC-13.1).
|
|
- Backup nightly 03:30 to Drive folder `1jDp1dGE5R3H2DBcXCqIPN1iWzQnPkdlV`; local files deleted once confirmed on Drive (D-002, §10.9).
|
|
- Restore drill follows §10.9 steps 1 to 7 and is documented in `docs/validation/restore-drill.md`; both phones must sync correctly afterwards (AC-18.2).
|
|
- Memory: `docker stats --no-stream` with both phones syncing; server RSS under 100 MB (§10.10, §11.2).
|
|
- Production runs on the Pi; development data on the Mac is not migrated (D-001).
|
|
|
|
## 4. Owned paths
|
|
From SPEC §14.7, verbatim: `infra/**` row, after-landing column "M10 updates runbooks" (that is `infra/README.md`, `infra/RESTORE.md`), and `docs/validation/<file>` | "the milestone that names the file", here `docs/validation/first-release.md` and `docs/validation/restore-drill.md`.
|
|
|
|
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator.
|
|
|
|
## 5. Deliverables
|
|
- `docs/validation/first-release.md`: release version, publish result, pairing of both phones, push both ways, update check, backup on Drive, memory check result, outstanding items; a "Results (owner's phone)" section for anything still to come; aggregates only.
|
|
- `docs/validation/restore-drill.md`: each §10.9 restore step as run, timings, the epoch rotation, both phones' re-push and balance check, issues found.
|
|
- `infra/README.md`, `infra/RESTORE.md`: corrections found during the release and the drill.
|
|
- On the Pi (not in git): published `apk/kvitt-<version_code>.apk` and `apk/latest.json`; a production household with both users.
|
|
|
|
## 6. Steps
|
|
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m10 -b agent-m10 main`, then in `.claude/worktrees/agent-m10`: `git reset --hard main`. Copy `android/local.properties`, `android/app/google-services.json` and `infra/secrets/` from the main checkout; the keystore stays outside the repository and is referenced by absolute path from `local.properties` (never copied into a checkout or git). Run `tools/install-git-hooks.sh`.
|
|
2. Read §2 in order. Confirm M2c-pi, M3, M4 and M8 have landed and the Pi stack is healthy (`infra/deploy.sh` output, `/health`); if not, stop and report.
|
|
3. Keystore (H-1). After the owner created it, add the four `kvitt.release.*` keys to the main checkout's `android/local.properties` (never overwrite other lines).
|
|
4. Deploy the current `main` image to the Pi with `infra/deploy.sh <tag>`; publish the APK with `infra/publish-apk.sh`; check `GET /apk/latest` returns the new `version_code`.
|
|
5. Bootstrap production: `docker compose exec server kvitt-server bootstrap` on the Pi (over SSH); give the code to the owner through the orchestrator.
|
|
6. Owner's phone (H-2 to H-4): install from the landing page, pair, allow notifications. Confirm with `adb shell pm list packages | grep se.hagstedt.kvitt` that the release id is installed and does not clash with the debug build.
|
|
7. Partner (H-5): the owner creates the partner invite in Settings and shares it; the partner installs, pairs, completes the profile step.
|
|
8. Push both ways (H-6): one test expense from each phone (deleted afterwards); each phone confirms the notification.
|
|
9. Backup: trigger `docker compose exec backup /backup.sh`; confirm on Drive with `rclone lsf`; confirm the next nightly run the following day if the session allows, else leave it in "Results (owner's phone)".
|
|
10. Restore drill (confirm with the owner before step 1 of §10.9 because it stops production): follow `infra/RESTORE.md` exactly with the latest backup; both phones re-push after the new epoch; the agent compares balances from the server with what the phones hold (pulled over USB from the owner's phone; the partner phone by yes/no). Fix the runbook where it was wrong.
|
|
11. Memory check: `docker stats --no-stream` on the Pi while both phones sync; record the server's memory.
|
|
12. Write the two validation files. Commit on `agent-m10`, `git rebase main`. Never merge, push or remove the worktree.
|
|
|
|
## 7. Gate
|
|
From SPEC §11.2, verbatim: "`docs/validation/first-release.md` and `docs/validation/restore-drill.md` complete; server RSS under 100 MB".
|
|
Green means: both files exist with every section filled (no `TODO` other than a "Results (owner's phone)" item named as outstanding); the `docker stats` line recorded in `first-release.md` shows the server below 100 MB with both phones syncing.
|
|
|
|
## 8. HUMAN steps
|
|
From SPEC §11.2, verbatim: "Create the release keystore; install the APK on both phones from the landing page; allow "Install unknown apps"; partner pairs with the invite; confirm notifications arrive".
|
|
- H-1, step 3, on the owner's Mac, in a directory outside the repository (D-024): `keytool -genkeypair -v -keystore kvitt-release.jks -alias kvitt -keyalg RSA -keysize 4096 -validity 10000`; choose the passwords, store them in the password manager, and tell the agent the keystore's absolute path and where the passwords are entered (the agent never echoes them).
|
|
- H-2, step 6, on the owner's phone: open the invite link or `https://icehack.asuscomm.com:8443/i/<code>`, tap "Download Kvitt", open the download.
|
|
- H-3, step 6: when Android asks, allow "Install unknown apps" for the browser, install, open Kvitt, pair with the code, allow notifications.
|
|
- H-4, step 6 and later updates: allow "Install unknown apps" for Kvitt itself when the in-app update first asks (AC-16.3).
|
|
- H-5, step 7, on the partner's phone: the same as H-2 and H-3 with the partner invite link, then the profile step.
|
|
- H-6, step 8: each phone answers yes/no: a notification about the other's test expense arrived.
|
|
|
|
## 9. Out of scope
|
|
- New features or fixes in app or server code (findings go in the report; fixes are new plans).
|
|
- The Splitwise import (M11).
|
|
- Gitea Actions changes and Pi provisioning beyond runbook corrections (M2c-pi).
|
|
- Deleting anything on Drive.
|
|
|
|
## 10. Report
|
|
- Branch `agent-m10`, head commit hash after rebasing on `main`.
|
|
- Released `version_code` and `version_name`; image tag deployed.
|
|
- Gate: the two files' section list and the `docker stats` server line.
|
|
- Restore drill result in one line; runbook corrections.
|
|
- Versions: none expected beyond what landed; state any tool used (keytool, rclone) and its version.
|
|
- Proposed decision entries (never written to `SPEC.md` or `docs/decisions.md` by this agent) 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.
|