Compare commits
1 Commits
feat/v1.1.
...
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