feat(suppress): template_suppressions marker for per-app opt-out (§11 tail S1)
A group TRIGGER/ROUTE template inherits to every descendant app; until now
a descendant could shadow one but not decline it. This marker records that
an app opts OUT of a specific inherited template — coarse by REFERENCE (a
handler script name for triggers, a path for routes), not row id (template
ids churn on re-apply, references are stable so re-apply is a NoOp).
App-only (a group would just not declare the template) → app_id NOT NULL,
no polymorphic owner; pure app config → ON DELETE CASCADE. A target_kind
discriminator ('trigger'|'route') keeps it one table, one reconcile loop.
Schema blessed at 58 migrations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
-- §11 tail (v1.2 Hierarchies): per-app opt-out of inherited group templates.
|
||||||
|
--
|
||||||
|
-- A group TRIGGER or ROUTE template inherits to every descendant app. Until now
|
||||||
|
-- a descendant could SHADOW a template (declare its own identical binding) but
|
||||||
|
-- not DECLINE one. This marker records that a specific app opts OUT of a
|
||||||
|
-- specific inherited template — the template then stops resolving for that app
|
||||||
|
-- (and only that app; a sibling subtree is unaffected).
|
||||||
|
--
|
||||||
|
-- Coarse by REFERENCE (not row id — template ids churn on re-apply): a
|
||||||
|
-- suppression names a handler SCRIPT NAME (target_kind='trigger') or a PATH
|
||||||
|
-- (target_kind='route'). A reference may decline more than one inherited
|
||||||
|
-- template (a group that bound several to the same script/path). The dispatch
|
||||||
|
-- filters gate to group-owned rows, so an app can only decline what it
|
||||||
|
-- INHERITS — never its own trigger/route, never a sibling's.
|
||||||
|
--
|
||||||
|
-- App-only: only an app suppresses (a group would just not declare the
|
||||||
|
-- template). So `app_id NOT NULL` — no polymorphic owner. It is pure app config
|
||||||
|
-- (like `extension_points`), so ON DELETE CASCADE (not the code-owner RESTRICT).
|
||||||
|
|
||||||
|
CREATE TABLE template_suppressions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
|
||||||
|
target_kind TEXT NOT NULL CHECK (target_kind IN ('trigger', 'route')),
|
||||||
|
-- A handler script name (trigger) or a path (route). Matched
|
||||||
|
-- case-insensitively for triggers (as script-name resolution is
|
||||||
|
-- elsewhere) and exactly for routes; stored as authored.
|
||||||
|
reference TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- One marker per (app, kind, reference) — re-apply is a NoOp.
|
||||||
|
CREATE UNIQUE INDEX template_suppressions_uidx
|
||||||
|
ON template_suppressions (app_id, target_kind, reference);
|
||||||
|
|
||||||
|
-- The trigger anti-join / route rebuild both look up by app_id.
|
||||||
|
CREATE INDEX template_suppressions_app_idx ON template_suppressions (app_id);
|
||||||
@@ -376,6 +376,13 @@ table: secrets
|
|||||||
group_id: uuid NULL
|
group_id: uuid NULL
|
||||||
environment_scope: text NOT NULL default='*'::text
|
environment_scope: text NOT NULL default='*'::text
|
||||||
|
|
||||||
|
table: template_suppressions
|
||||||
|
id: uuid NOT NULL default=gen_random_uuid()
|
||||||
|
app_id: uuid NOT NULL
|
||||||
|
target_kind: text NOT NULL
|
||||||
|
reference: text NOT NULL
|
||||||
|
created_at: timestamp with time zone NOT NULL default=now()
|
||||||
|
|
||||||
table: topics
|
table: topics
|
||||||
app_id: uuid NOT NULL
|
app_id: uuid NOT NULL
|
||||||
name: text NOT NULL
|
name: text NOT NULL
|
||||||
@@ -605,6 +612,11 @@ indexes on secrets:
|
|||||||
secrets_app_uidx: public.secrets USING btree (app_id, environment_scope, name) WHERE (app_id IS NOT NULL)
|
secrets_app_uidx: public.secrets USING btree (app_id, environment_scope, name) WHERE (app_id IS NOT NULL)
|
||||||
secrets_group_uidx: public.secrets USING btree (group_id, environment_scope, name) WHERE (group_id IS NOT NULL)
|
secrets_group_uidx: public.secrets USING btree (group_id, environment_scope, name) WHERE (group_id IS NOT NULL)
|
||||||
|
|
||||||
|
indexes on template_suppressions:
|
||||||
|
template_suppressions_app_idx: public.template_suppressions USING btree (app_id)
|
||||||
|
template_suppressions_pkey: public.template_suppressions USING btree (id)
|
||||||
|
template_suppressions_uidx: public.template_suppressions USING btree (app_id, target_kind, reference)
|
||||||
|
|
||||||
indexes on topics:
|
indexes on topics:
|
||||||
topics_pkey: public.topics USING btree (app_id, name)
|
topics_pkey: public.topics USING btree (app_id, name)
|
||||||
|
|
||||||
@@ -833,6 +845,11 @@ constraints on secrets:
|
|||||||
[FOREIGN KEY] secrets_app_id_fkey: FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
|
[FOREIGN KEY] secrets_app_id_fkey: FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
|
||||||
[FOREIGN KEY] secrets_group_id_fkey: FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
[FOREIGN KEY] secrets_group_id_fkey: FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
||||||
|
|
||||||
|
constraints on template_suppressions:
|
||||||
|
[CHECK] template_suppressions_target_kind_check: CHECK ((target_kind = ANY (ARRAY['trigger'::text, 'route'::text])))
|
||||||
|
[FOREIGN KEY] template_suppressions_app_id_fkey: FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
|
||||||
|
[PRIMARY KEY] template_suppressions_pkey: PRIMARY KEY (id)
|
||||||
|
|
||||||
constraints on topics:
|
constraints on topics:
|
||||||
[CHECK] topics_auth_mode_check: CHECK ((auth_mode = ANY (ARRAY['public'::text, 'token'::text, 'session'::text])))
|
[CHECK] topics_auth_mode_check: CHECK ((auth_mode = ANY (ARRAY['public'::text, 'token'::text, 'session'::text])))
|
||||||
[FOREIGN KEY] topics_app_id_fkey: FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
|
[FOREIGN KEY] topics_app_id_fkey: FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
|
||||||
@@ -913,3 +930,4 @@ constraints on vars:
|
|||||||
0055: group files
|
0055: group files
|
||||||
0056: group triggers
|
0056: group triggers
|
||||||
0057: group routes
|
0057: group routes
|
||||||
|
0058: template suppressions
|
||||||
|
|||||||
Reference in New Issue
Block a user