feat: E2E #2 (Stash) gap remediation + S6 hardening
Some checks failed
CI / Rust — fmt, clippy, test (push) Failing after 6m19s
CI / Dashboard — check (push) Successful in 9m48s

Closes the gaps and the one security finding from the second end-to-end
CLI test (E2E_STASH_REPORT.md), plus the H1 boot-regression found while
re-reviewing those fixes.

Security
- S6: reserved-path validation (`check_reserved`) now case-folds before
  comparing, so `/API/v2/x`, `/HEALTHZ`, `/Admin/x` are rejected like
  their lowercase forms. Request-time matching stays case-sensitive.
- S10: "public route != public data" callout in sdk-shape.md (script_gate
  skips authz when the principal is anonymous).

Observability / features
- G1: trigger executions now write `execution_logs`. Migration 0043 adds
  a `source` column (CHECK mirrors ExecutionSource/OutboxSourceKind,
  DEFAULT 'http' backfills history); a shared `build_execution_log` helper
  in executor-core; dispatcher logging for outbox triggers + queue
  consumers (skips sync-HTTP rows the orchestrator already logs). `pic
  logs` gains a source column + `--source` filter.
- G5: dev-only in-memory email capture under PICLOUD_DEV_MODE with no SMTP
  (email::send succeeds locally), readable at GET /api/v1/admin/dev/emails
  (Owner/Admin only; route mounted only in capture mode).
- G6: generalized the Rhai in-place-mutation footgun note (trim/replace/
  make_upper/make_lower/crop/truncate/pad return ()).
- G2/G3/G4 (CLI): `pic members`, `pic files`, `pic queues`, read-only
  `pic kv` (+ new kv_api.rs); `pic deploy --timeout/--memory/--kind/
  --sandbox`; first-class `pic triggers create-{docs,files,pubsub,queue,
  email}` wrappers. All new client path segments percent-encoded via seg().

H1 regression fix (found in re-review)
- The S6 change also runs in `compile_routes`, which compiles every stored
  route at boot and on each route CRUD. A single stored route the new
  validation rejects (creatable while the S6 gap existed) made the whole
  compile Err and aborted startup. `compile_routes` is now lenient: it
  skips an un-compilable row with a warning instead of bricking boot
  (route creation still validates separately). Migration 0044 sweeps
  pre-existing reserved-path routes on upgrade (WHERE mirrors
  check_reserved exactly). Added regression tests for both.

Verified: cargo fmt, clippy --all-targets --all-features -D warnings, the
schema_snapshot test, and the new S6/lenient-compile unit tests all pass;
boot-resilience and G1/G5 confirmed live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-13 15:01:04 +02:00
parent a91b134285
commit 51f14fa2b1
33 changed files with 2223 additions and 195 deletions

View File

@@ -0,0 +1,20 @@
-- G1 (E2E #2 "Stash"): trigger executions were invisible to `pic logs`.
--
-- Only HTTP-route executions ever wrote an `execution_logs` row; queue,
-- cron, dead-letter, and `invoke()` runs left no trace, so background
-- workers were observable only via dead-letters (failures) or their own
-- side effects. The dispatcher now logs every trigger run too — this
-- column records which kind of event dispatched each execution so the
-- logs surface can show, and filter by, the origin.
--
-- DEFAULT 'http' backfills every pre-existing row: before this change the
-- only thing that logged was the HTTP path, so 'http' is correct history.
-- The CHECK list mirrors `manager-core::OutboxSourceKind` /
-- `shared::ExecutionSource`; keep all three in sync.
ALTER TABLE execution_logs
ADD COLUMN source TEXT NOT NULL DEFAULT 'http'
CHECK (source IN (
'http', 'kv', 'docs', 'dead_letter', 'cron',
'files', 'pubsub', 'email', 'invoke', 'queue'
));

View File

@@ -0,0 +1,22 @@
-- H1 (re-review of the S6 reserved-path fix): the reserved-prefix check is
-- now case-insensitive, both at route creation AND when the route table is
-- compiled at boot. Routes created before the fix — while validation was
-- case-sensitive — could hold paths like `/API/v2/x`, `/Admin/x`, or
-- `/HEALTHZ`. `compile_routes` now skips such rows with a warning instead of
-- aborting startup (so an un-upgraded boot can't be bricked), but those
-- routes violate the reserved namespace and can never be served safely, so
-- sweep them here on upgrade.
--
-- Mirrors `orchestrator-core::routing::pattern::check_reserved` exactly,
-- case-insensitively: a path is reserved if its lowercased form equals one
-- of the bare names (`/api` `/admin` `/healthz` `/version`) or starts with
-- one of the prefixes. Note `/api/` and `/admin/` reserve on the trailing
-- slash, while `/healthz` and `/version` reserve on bare prefix — matching
-- the RESERVED_PATH_PREFIXES list. (Idempotent: a no-op once swept.)
DELETE FROM routes
WHERE lower(path) IN ('/api', '/admin', '/healthz', '/version')
OR lower(path) LIKE '/api/%'
OR lower(path) LIKE '/admin/%'
OR lower(path) LIKE '/healthz%'
OR lower(path) LIKE '/version%';