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:
MechaCat02
2026-07-15 22:54:51 +02:00
parent eae2ee08f1
commit be0c618672
15 changed files with 623 additions and 200 deletions

View File

@@ -0,0 +1,20 @@
-- §9.4 Service Interceptors M2 — before + after phases.
--
-- The 0073 marker guarded only the BEFORE-op allow/deny hook. M2 introduces an
-- explicit `phase` so an owner can declare a `before` AND an `after` interceptor
-- for one `(service, op)`. Existing markers default to `phase='before'` — the
-- 0073 behaviour is unchanged. The partial-unique indexes gain `phase` so the
-- two phases are distinct rows per owner (one before + one after each).
ALTER TABLE interceptors
ADD COLUMN phase TEXT NOT NULL DEFAULT 'before'
CHECK (phase IN ('before', 'after'));
-- Recreate the per-owner uniqueness to be per (owner, service, op, PHASE).
DROP INDEX interceptors_group_uidx;
DROP INDEX interceptors_app_uidx;
CREATE UNIQUE INDEX interceptors_group_uidx
ON interceptors (group_id, service, op, phase) WHERE group_id IS NOT NULL;
CREATE UNIQUE INDEX interceptors_app_uidx
ON interceptors (app_id, service, op, phase) WHERE app_id IS NOT NULL;