feat(interceptors): per-interceptor wall-clock timeout (§9.4 M5)

A [[interceptors]] marker can set timeout_ms (migration 0075, CHECK > 0); a
runaway guard (loop {}) is interrupted and the op DENIED (fail closed) within
budget rather than hanging the write path. The effective deadline is
min(caller-remaining, now + timeout) — a hook can only tighten, never extend,
its caller's deadline. NULL uses PICLOUD_INTERCEPTOR_TIMEOUT_MS (default 5s).

Wiring: run_resolved_blocking splits into a core + a _with_timeout variant that
computes the effective deadline from engine::ambient_deadline() and runs via
execute_ast_with_deadline; run_one_hook passes the marker's timeout_ms (or the
env default). timeout_ms threaded end to end — manifest, plan, BundleInterceptor,
the reconcile diff (part of the mutable body: a timeout change is an Update),
insert_interceptor_tx, resolve_chain/list_for_owner/list_on_app_chain +
SealedInterceptor/InterceptorMarker, and interceptor_service → ResolvedInterceptor.
Schema snapshot re-blessed. Pinned by a journey: a loop{} guard with
timeout_ms=100 is denied and its write does not persist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-16 20:18:03 +02:00
parent 5592f1c97e
commit 8b70d52d43
12 changed files with 244 additions and 47 deletions

View File

@@ -318,6 +318,7 @@ table: interceptors
created_at: timestamp with time zone NOT NULL default=now()
updated_at: timestamp with time zone NOT NULL default=now()
phase: text NOT NULL default='before'::text
timeout_ms: integer NULL
table: kv_entries
app_id: uuid NOT NULL
@@ -954,6 +955,7 @@ constraints on groups:
constraints on interceptors:
[CHECK] interceptors_owner_exactly_one: CHECK (((group_id IS NULL) <> (app_id IS NULL)))
[CHECK] interceptors_phase_check: CHECK ((phase = ANY (ARRAY['before'::text, 'after'::text])))
[CHECK] interceptors_timeout_ms_check: CHECK (((timeout_ms IS NULL) OR (timeout_ms > 0)))
[FOREIGN KEY] interceptors_app_id_fkey: FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
[FOREIGN KEY] interceptors_group_id_fkey: FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
[PRIMARY KEY] interceptors_pkey: PRIMARY KEY (id)
@@ -1148,3 +1150,4 @@ constraints on workflows:
0072: execution source workflow
0073: interceptors
0074: interceptor phase
0075: interceptor timeout