feat: E2E #2 (Stash) gap remediation + S6 hardening
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:
@@ -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'
|
||||
));
|
||||
@@ -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%';
|
||||
Reference in New Issue
Block a user