# Handoff — 2026-06-05 Machine-switch handoff. This document is the entry point for picking up PiCloud work on a different machine. It captures session state, what shipped, what's queued, and how to continue. --- ## TL;DR - **`main` is at v1.1.7** — seven minor releases (v1.1.1 → v1.1.7) shipped this session via the dispatch-and-review workflow. - Working tree is clean. - Next release is **v1.1.8** (User Management). A draft dispatch prompt is sketched in §6 below; ready to send to a dev agent. - One dev Postgres container (`picloud-postgres-1` on port 15432) is still running on the source machine — tear it down with `docker compose down -v` before the source machine goes offline. --- ## How to resume on the new machine ```sh git clone https://git.mc02.dev/fabi/PiCloud.git cd PiCloud git checkout main git log --oneline -10 # should show v1.1.7 reviewer commit at HEAD docker compose up -d # local Postgres for DB-gated tests export DATABASE_URL='postgres://picloud:picloud@127.0.0.1:5432/picloud' cargo test --workspace -- --test-threads=2 ``` If you're starting from this branch (`handoff/2026-06-05`), it points at the same `main` HEAD with this `HANDOFF.md` added; merge or just read it and continue work on `main`. For the master encryption key needed by v1.1.7+ secrets: ```sh export PICLOUD_SECRET_KEY="$(openssl rand -base64 32)" # OR, for dev only: export PICLOUD_DEV_MODE=true ``` The dev fallback uses a deterministic key (`SHA-256` of a hardcoded string) — fine for local testing, fatal for any real deployment. --- ## Session summary: v1.1.1 → v1.1.7 All seven minor releases completed in one session via the dispatch workflow you set up: I draft a prompt, you dispatch it to a fresh agent in another session, the agent implements and writes `HANDBACK.md`, you bounce the report back to me, I audit the branch and write `REVIEW.md` with a verdict, you bounce-back-for-fixes-if-needed, and on approve I fast-forward merge into `main`. | Release | Capability | Iterations | Status | |---|---|---|---| | **v1.1.1** | Storage & Events (KV + triggers framework + outbox + dispatcher + NATS-style sync HTTP + dead-letter table + dashboard surface) | 1 | ✅ merged | | **v1.1.2** | Documents (`docs::*` SDK + query DSL + `docs:*` triggers) | 2 | ✅ merged (iteration 2 fixed a fmt diff) | | **v1.1.3** | Modules (`scripts.kind` + `PicloudModuleResolver` + AST caches + `script_imports`) | 1 | ✅ merged | | **v1.1.4** | Outbound HTTP & Scheduled Tasks (`http::*` with SSRF deny-list + cron triggers) | 1 | ✅ merged | | **v1.1.5** | Files & Pub/Sub (filesystem-backed blobs + `pubsub::publish_durable` + first CI workflow) | 1 | ✅ merged | | **v1.1.6** | Realtime Channels & Client Library (SSE + topics + HMAC subscriber tokens + `@picloud/client@1.0.0`) | 1 | ✅ merged | | **v1.1.7** | Configuration & Email (encrypted secrets + outbound/inbound email + dead-letter handler fix) | 1 | ✅ merged | **Versioning state on `main`:** - Workspace `1.1.7` - SDK schema `1.8` - Dashboard `0.13.0` - `@picloud/client` `1.0.0` - Migrations applied through `0025` **Test counts at HEAD:** `cargo test --workspace --test-threads=2` with `DATABASE_URL` set → **617 passed / 0 failed**. The `--test-threads=2` is required on shared dev Postgres (~9 concurrent `build_app`s otherwise exhaust connections); CI's dedicated Postgres doesn't hit this. --- ## Branches on this machine ### v1.1.x feature branches (all merged into main, kept locally for traceability) | Branch | HEAD | What it contains | |---|---|---| | `feat/v1.1.1-storage-and-events` | `2796f36` | v1.1.1 work + HANDBACK + REVIEW | | `feat/v1.1.2-documents` | `5bbbc26` | v1.1.2 work (2 iterations) + HANDBACK + REVIEW | | `feat/v1.1.3-modules` | `6f17259` | v1.1.3 work + HANDBACK + REVIEW | | `feat/v1.1.4-http-cron` | `03d03ea` | v1.1.4 work + HANDBACK + REVIEW | | `feat/v1.1.5-files-pubsub` | `d064681` | v1.1.5 work + HANDBACK + REVIEW | | `feat/v1.1.6-realtime-client` | `64ad978` | v1.1.6 work + HANDBACK + REVIEW | | `feat/v1.1.7-secrets-email` | `5cbb6ca` | v1.1.7 work + HANDBACK + REVIEW | All seven HEADs are reachable from `main` (fast-forward merges). Keeping the branches makes it easy to inspect the per-release commit slice without git log filtering. ### Older branches predating this session (state uncertain) These appeared in `git branch` at session start and weren't touched by v1.1.x work. I don't know which are abandoned, in-flight, or already merged under different names. **On the new machine, decide for each:** | Branch | Last commit | Tracking | |---|---|---| | `chore/ui-hardening` | `b42e273 fix(test): admin_is_implicit_app_admin uses force=true on app delete` | local-only | | `feat/app-members` | `e6fc6e6 test(picloud): close two app_members test gaps` | local-only | | `feat/cli` | `5d08974 style(cli): re-fmt one stray format! line in the integration test` | tracks `origin/feat/cli` (up to date) | | `feat/multi-app-scoping` | `a393f11 feat(dashboard): auto-slug app names and infer route host kind from input` | tracks `origin/feat/multi-app-scoping` (ahead 3) | | `feat/users-and-keys-ui` | `6eb32a7 feat(dashboard): adopt ActionMenu for user row actions` | local-only | | `feat/users-authz` | `2aab92a style: cargo fmt across Phase 3.5 changes` | local-only | | `test/cli-journeys` | `e4851b3 test(cli): extract shared Fixture into tests/common` | tracks `origin/test/cli-journeys` (up to date) | | `test/frontend-e2e` | `ec3c768 test(dashboard): add full-stack integration specs` | local-only | **Push these if you want them mirrored on the new machine** — see §3 below for the push commands. If any are obsolete, delete them locally before resuming. --- ## §3 — Push instructions Push was denied in this session (sandbox restriction). Run these on the source machine to mirror state to `origin`: ```sh # 1. The v1.1.x releases on main (55 commits) git push origin main # 2. The seven v1.1.x feature branches (preserves per-release history) git push origin feat/v1.1.1-storage-and-events git push origin feat/v1.1.2-documents git push origin feat/v1.1.3-modules git push origin feat/v1.1.4-http-cron git push origin feat/v1.1.5-files-pubsub git push origin feat/v1.1.6-realtime-client git push origin feat/v1.1.7-secrets-email # 3. This handoff branch git push -u origin handoff/2026-06-05 # 4. OPTIONAL — push the older branches you want on the new machine # (decide per-branch; some may be abandoned) git push origin chore/ui-hardening git push origin feat/app-members git push origin feat/multi-app-scoping # ahead 3 of remote git push origin feat/users-and-keys-ui git push origin feat/users-authz git push origin test/frontend-e2e ``` After pushing, on the new machine: `git fetch --all` brings everything down. `git checkout main` puts you at v1.1.7 HEAD. --- ## §4 — Workflow context (read before dispatching v1.1.8) The dispatch-and-review workflow you've been using: 1. **You ask me to draft the dispatch prompt** for the next release. 2. **I draft the prompt** based on: - The roadmap in [`docs/v1.1.x-design-notes.md` §7](docs/v1.1.x-design-notes.md) - Three or so follow-ups identified in the prior release's REVIEW.md - Discipline lessons carried forward from prior retros 3. **You dispatch the prompt to a fresh agent in another session** — that agent gets no prior conversation context; the prompt + the docs it points at are everything they have. 4. **The agent implements + writes `HANDBACK.md`** at the repo root, then stops. 5. **You bounce the HANDBACK back to me.** 6. **I audit the branch and write `REVIEW.md`** with a verdict (`APPROVE` or `NEEDS CHANGES`). 7. **If `NEEDS CHANGES`:** you bounce the REVIEW back to the agent; they iterate; back to step 5. 8. **If `APPROVE`:** I fast-forward merge the branch into `main` and pause for your next instruction. What's worked well across seven releases: - The discipline reminders compound. Each release's retro identifies one small habit the agent dropped (§8 attestation hand-counting, silent prompt-default deviations, brief-internal contradictions silently reinterpreted, clippy run without `--all-targets`); the next release's prompt explicitly addresses it. By v1.1.7 the agent was catching their own latent findings without prompting. - Explicit "deviations beyond the brief" sections in HANDBACK make audits fast — every meaningful judgment call is in one place. - The "this is the deferrable piece under scope pressure" clause in big releases (v1.1.6 client lib, v1.1.7 inbound email) gave the agent a clean escape hatch they never actually needed but worked as intended. - Latent findings discovered during implementation (v1.1.3 cross-app trigger gap, v1.1.4 SSRF literal-IP bypass, v1.1.6 dead_letter handler never firing, v1.1.7 clippy regression at v1.1.6 HEAD) all surfaced honestly rather than being silently worked around. What to do differently in v1.1.8: - **Walk through each code example in the prompt** before sending. v1.1.4 brief said `(url, opts)` but its example was `http::post(url, body)` — the agent had to fix it during implementation. v1.1.7 brief sketched `TriggerEvent::DeadLetter` field names that didn't match the actual variant. Both flagged correctly, but pre-resolution saves agent effort. - **Pin the clippy gate**: `cargo clean` before `cargo clippy --all-targets` to defeat incremental-cache false-greens. See v1.1.7 REVIEW §3.3 for context. --- ## §5 — Pending follow-ups for v1.1.8 From the v1.1.7 REVIEW.md, three load-bearing items to fold into the v1.1.8 dispatch prompt: ### 5.1 Drop the plaintext `realtime_signing_key` column The v1.1.7 phase-2 commitment. v1.1.7 added NULL-able encrypted columns + DROP NOT NULL on the plaintext column; the startup task encrypts existing rows. v1.1.8 drops the plaintext column entirely. **Pre-flight check:** scan for any remaining non-NULL rows on the plaintext column. If found, run the encryption migration before the drop. If the v1.1.7 startup task ran on the operator's deploy, all rows should already be encrypted. **CHANGELOG must note** that v1.1.8 requires v1.1.7 to have been applied first. No skipping versions. ### 5.2 Clippy `--all-targets` discipline refinement The v1.1.7 audit caught a real regression: four warnings predated v1.1.7 that the v1.1.6 audit reported as clippy-green. Likely cause: cargo's incremental cache leaving test binaries unchecked. v1.1.8 prompt should require either: - `cargo clean` before `cargo clippy --all-targets`, OR - Explicit verification that the clippy output includes `Checking` lines for test crates. CI's `.github/workflows/ci.yml` (added in v1.1.5) might also benefit from a clippy-cache-check step. ### 5.3 `auth_mode = 'session'` for realtime subscriber tokens v1.1.7's CHECK constraint on `topics.auth_mode` only allows `('public', 'token')`. v1.1.8's `users::*` work needs to: - Extend the CHECK to include `'session'`. - Add a session-token validator alongside the existing HMAC validator behind the unchanged `RealtimeAuthority` trait. The trait shape from v1.1.6 already supports this — natural extension. --- ## §6 — Draft v1.1.8 dispatch prompt outline Not the full prompt — just the scope sketch so you can ask me to expand it on the new machine. **v1.1.8 — User Management (`users::*`)** Core scope: - `users::create / get / find / update / delete / list` SDK - Password hashing (argon2id) - `users` table per-app - Sessions: `users::login(email, password)` → returns a session token; `users::verify(session_token)` returns the user or `()` - Sessions table with TTL + revocation - Email verification flow (uses v1.1.7 email::send) - Password reset flow (uses v1.1.7 email::send + tokens) - Invitations (admin creates an invite → email link → user accepts + sets password) - Roles: per-app role assignments on users - `Capability::AppUsersRead/Write/Admin` mapped to existing scopes - Dashboard: Users tab on app detail page (list, invite, role-edit) Follow-ups from v1.1.7 retro (fold in): - Drop plaintext `realtime_signing_key` column (phase-2) - Clippy `--all-targets` discipline refinement - `auth_mode = 'session'` for realtime subscriber tokens (uses v1.1.8 sessions) Out of scope: - OAuth providers (defer to v1.2+) - 2FA / MFA (defer to v1.2+) - SSO / SAML (defer) - Password policy customization (defer; ship with sensible default) - User-to-user messaging (defer; userland) Ask me to expand this into a full prompt when you're ready. --- ## §7 — Environmental notes - **Dev Postgres container** `picloud-postgres-1` (port 15432) was running at session end on the source machine. The v1.1.5/v1.1.6/ v1.1.7 agents started it for live DB-gated tests. **Tear down with `docker compose down -v` before the source machine goes offline** if you want a clean state. - **`PICLOUD_SECRET_KEY`** is required for v1.1.7+ to start. Pick one with `openssl rand -base64 32` for production; use `PICLOUD_DEV_MODE=true` (no master key needed) for local development. The dev key is deterministic so secrets persist across restarts in dev. - **CI workflow** lives at [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (added in v1.1.5). Runs fmt + clippy + `cargo test --workspace` against a `postgres:15` service, plus dashboard `npm run check`. When you push to `main` or open a PR, CI will run. **First push after this handoff will exercise it for the first time on real workload — watch the run.** --- ## §8 — Key documents for orientation - **[`CLAUDE.md`](CLAUDE.md)** — project conventions. Read first. - **[`serverless_cloud_blueprint.md`](serverless_cloud_blueprint.md)** — the authoritative architecture document. - **[`docs/sdk-shape.md`](docs/sdk-shape.md)** — SDK conventions every v1.1.x service follows. - **[`docs/v1.1.x-design-notes.md`](docs/v1.1.x-design-notes.md)** — the in-flight-decisions document. Sections §1–§6 contain the "Decided 2026-06-01" annotations from the design conversation that preceded this session; §7 holds the v1.1.x roadmap; §1–4 are candidates for pruning (their decisions shipped in v1.1.1). - **[`docs/versioning.md`](docs/versioning.md)** — patch-bump policy under the post-1.0 expansion-phase carve-out. - **[`docs/git-workflow.md`](docs/git-workflow.md)** — trunk-based workflow conventions. - **[`CHANGELOG.md`](CHANGELOG.md)** — release notes for v1.1.1 onward. v1.1.7's entry includes the retroactive dead_letter security note. Per-release artifacts on `main`: - `HANDBACK.md` at repo root — currently holds the v1.1.7 agent's handback. Overwritten each release. - `REVIEW.md` at repo root — currently holds the v1.1.7 reviewer's audit. Overwritten each release. If you want the full per-release HANDBACK + REVIEW history, the seven `feat/v1.1.x-*` branches preserve them (each branch's `HEAD~1` contains the HANDBACK and `HEAD` contains the REVIEW for that release). --- ## §9 — Quick smoke after resuming After cloning + setting up the new machine: ```sh # Basic gates cargo fmt --all -- --check cargo clippy --all-targets --all-features -- -D warnings cargo test --workspace # DB-gated (needs Postgres) docker compose up -d export DATABASE_URL='postgres://picloud:picloud@127.0.0.1:5432/picloud' export PICLOUD_DEV_MODE=true cargo test --workspace --test-threads=2 # Dashboard cd dashboard && npm install && npm run check # Client library cd clients/typescript && npm install && npm run lint && npm run test && npm run build ``` If all green: machine is ready. Resume v1.1.8 work by asking me for the full dispatch prompt. --- **Handoff written 2026-06-05.** Main HEAD: `5cbb6ca` (v1.1.7 reviewer APPROVE).