feat(interceptors): ordered before-chains + cycle guard + ctx plumbing (§9.4 M1+M2)
M1: introduces a clone-cheap InterceptorCtx { interceptors, self_engine,
limits } threaded into every SDK register fn (kv/docs/files/queue/pubsub/http)
so the non-KV services carry the hook seam (unused until M7-M11); KvHandle
collapses its three fields into one ictx.
M2: replaces the nearest-only resolver with ordered before/after chains. The
trait becomes resolve(cx, service, op) -> InterceptorChain { before, after }
(migration 0074 adds a phase column + phase-aware unique indexes). The before
-chain runs ancestor->app (depth DESC) so a group compliance guard can't be
bypassed by a descendant; single-marker behavior is byte-identical to before.
An identity cycle guard (thread-local visited-set keyed by script_id) denies a
detected cycle, alongside the existing binary re-entrancy break. Fail-closed
verdict preserved (allow only on #{ allowed: true }; a Dangling entry or a
missing engine back-ref denies); app_id still derives from cx.app_id only.
after-chains resolve but stay unused until M3. Pinned by two new interceptor
journeys (ancestor->app chain ordering; self-referential no-recurse).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -317,6 +317,7 @@ table: interceptors
|
||||
interceptor_script: text NOT NULL
|
||||
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
|
||||
|
||||
table: kv_entries
|
||||
app_id: uuid NOT NULL
|
||||
@@ -675,9 +676,9 @@ indexes on groups:
|
||||
|
||||
indexes on interceptors:
|
||||
interceptors_app_id_idx: public.interceptors USING btree (app_id) WHERE (app_id IS NOT NULL)
|
||||
interceptors_app_uidx: public.interceptors USING btree (app_id, service, op) WHERE (app_id IS NOT NULL)
|
||||
interceptors_app_uidx: public.interceptors USING btree (app_id, service, op, phase) WHERE (app_id IS NOT NULL)
|
||||
interceptors_group_id_idx: public.interceptors USING btree (group_id) WHERE (group_id IS NOT NULL)
|
||||
interceptors_group_uidx: public.interceptors USING btree (group_id, service, op) WHERE (group_id IS NOT NULL)
|
||||
interceptors_group_uidx: public.interceptors USING btree (group_id, service, op, phase) WHERE (group_id IS NOT NULL)
|
||||
interceptors_pkey: public.interceptors USING btree (id)
|
||||
|
||||
indexes on kv_entries:
|
||||
@@ -952,6 +953,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])))
|
||||
[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)
|
||||
@@ -1145,3 +1147,4 @@ constraints on workflows:
|
||||
0071: workflows
|
||||
0072: execution source workflow
|
||||
0073: interceptors
|
||||
0074: interceptor phase
|
||||
|
||||
Reference in New Issue
Block a user