-- §4.5 M5: STATEFUL group trigger templates (cron / queue / email) via -- per-descendant MATERIALIZATION. -- -- The event kinds (kv/docs/files/pubsub) resolve a group template LIVE at -- dispatch via the chain CTE — no per-app rows. The stateful kinds can't: -- * cron needs a per-app `last_fired_at` the scheduler advances; -- * queue needs the one-consumer-per-(app_id, queue_name) advisory lock; -- * email needs a per-app SEALED inbound secret. -- So a group stateful template is instead MATERIALIZED into an app-owned -- trigger row per descendant app (created/removed as the tree changes), and the -- unchanged dispatch paths (scheduler, queue consumer, email webhook) only ever -- see app-owned rows. -- -- `materialized_from` links a managed app-owned row back to the group template -- it was expanded from: NULL = a hand-authored trigger; set = a reconciler-owned -- copy (dropped + re-created as descendants come and go). ON DELETE CASCADE so -- deleting the group template (RESTRICT-guarded elsewhere) or the group cleans -- up the copies; a plain app delete CASCADEs the row away via `triggers.app_id`. ALTER TABLE triggers ADD COLUMN materialized_from UUID REFERENCES triggers(id) ON DELETE CASCADE; -- The reconciler looks up existing copies by their source template. CREATE INDEX idx_triggers_materialized_from ON triggers (materialized_from) WHERE materialized_from IS NOT NULL;