diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..d77537e --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,382 @@ +# 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).