docs(handoff): machine-switch handoff report 2026-06-05

Session summary, branch inventory, push instructions, v1.1.8
follow-ups, and pickup-on-new-machine smoke commands. Main is at
v1.1.7 (5cbb6ca); seven minor releases shipped this session via
the dispatch-and-review workflow.

Read this first on the new machine.
This commit is contained in:
MechaCat02
2026-06-05 07:12:06 +02:00
parent 5cbb6ca427
commit 7040f0df83

382
HANDOFF.md Normal file
View File

@@ -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; §14 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).