# 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-`; 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-.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/` | "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-.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 `; 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/`, 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.