-- ยง9.4 Service Interceptors (v1.2) โ€” before-op allow/deny hooks. -- -- A marker `(owner, service, op) -> interceptor_script` declares that a script -- runs BEFORE a data-plane operation and may deny it. MVP scope: `service='kv'`, -- `op IN ('set','delete')`, allow/deny only (no data transform, no chaining, -- no after-hooks). The interceptor is itself a script owned by the same node -- (or an ancestor group); it is resolved + run through the existing `invoke()` -- re-entry path, so this table holds only the MARKER โ€” pure declaration, -- structurally like an `extension_points` row (0051): config, not code, so -- ON DELETE CASCADE. -- -- Ownership is polymorphic (mirrors extension_points/vars/secrets/scripts): -- exactly one of (app_id, group_id) is set. Resolution walks the calling app's -- chain (app, then nearest ancestor group) and picks the nearest declaration -- for a (service, op) โ€” nearest-owner-wins, so an app overrides a group's -- interceptor, the deliberate inverse of a sealed import. CREATE TABLE interceptors ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), group_id UUID REFERENCES groups(id) ON DELETE CASCADE, app_id UUID REFERENCES apps(id) ON DELETE CASCADE, CONSTRAINT interceptors_owner_exactly_one CHECK ((group_id IS NULL) <> (app_id IS NULL)), -- The intercepted operation. MVP: service='kv', op IN ('set','delete'). service TEXT NOT NULL, op TEXT NOT NULL, -- Name of the interceptor script (resolved on the owner's chain at run time). interceptor_script TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- One marker per (owner, service, op). Partial because the owner is split -- across two nullable columns. CREATE UNIQUE INDEX interceptors_group_uidx ON interceptors (group_id, service, op) WHERE group_id IS NOT NULL; CREATE UNIQUE INDEX interceptors_app_uidx ON interceptors (app_id, service, op) WHERE app_id IS NOT NULL; -- Lookup indexes for the resolver's chain join + list-by-owner. CREATE INDEX interceptors_group_id_idx ON interceptors (group_id) WHERE group_id IS NOT NULL; CREATE INDEX interceptors_app_id_idx ON interceptors (app_id) WHERE app_id IS NOT NULL;