Closes the audit's Critical "operator can't deploy a serverless endpoint
end-to-end from the CLI" finding. Two new subcommand families bring the
CLI coverage from 14 commands to ~25:
- pic routes {ls, create, rm, check, match}: full route CRUD plus the
dry-run conflict checker and the URL matcher already exposed by the
dashboard. The create form accepts --path-kind, --host-kind, and
--dispatch (sync|async) so async routes can finally be created
headlessly. The ls output adds a dispatch column.
- pic admins {ls, create, show, set, rm}: per-instance admin user
management. Create reads passwords from stdin via --password - so
shell history never sees the cleartext. Set is a JSON-Merge-Patch
shape that lets operators deactivate accounts or rotate roles
without touching the dashboard.
Six new ignored integration tests follow the established #[ignore]
pattern (DATABASE_URL gates them). They cover the happy-path round
trip, the async-dispatch persistence path, the password-required
error path, and the capability gate (a Member sees HTTP 403). All
pass against the local dev stack with PICLOUD_DEV_MODE=true +
PICLOUD_DEV_INSECURE_KEY=i-understand-this-is-insecure.
The `pic members` and `pic domains` subcommands the audit mentioned
are deferred — the apps_api shape may shift in v1.2 with per-app
roles and rebuilding the CLI surface twice would be churn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The single bare-metal integration test now reuses a `LazyLock<Fixture>`
that spawns picloud once on a private port and shares it across every
test in the binary. Sets the stage for per-surface journey modules
(auth, apps, scripts, invoke, logs, roles, output) without each one
paying for its own server spawn — same trick the dashboard Playwright
suite uses with global-setup.
Notes:
- `tests/cli.rs` becomes a tiny module list; the seed flow moved to
`tests/integration.rs`. The seed slug now goes through
`common::unique_slug` so parallel/serial reruns can't collide.
- `autotests = false` + an explicit `[[test]] name = "cli"` keeps Cargo
from auto-promoting future `tests/*.rs` files into their own binaries
(which would each respawn picloud).
- Subprocess cleanup uses `libc::atexit` to SIGTERM picloud when the
test binary exits. PR_SET_PDEATHSIG was tried and rejected: it fires
when the *thread* that forked dies, and cargo's per-test worker
threads exit between tests, which killed the fixture mid-suite.
- New helpers: AppGuard/UserGuard (RAII teardown), member_user /
grant_membership / update_membership (direct API for role tests),
unique_slug / unique_username, pic_as / pic_no_env.
- Two `fixture_url_is_shared_*` tests prove the LazyLock is actually
shared, not respawned per test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>