Compare commits
1 Commits
main
...
handoff/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7040f0df83 |
382
HANDOFF.md
Normal file
382
HANDOFF.md
Normal 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; §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).
|
||||||
Reference in New Issue
Block a user