feat(sealed): consume sealed at the two suppression gates (§11 tail M3)

The runtime effect: a sealed group template ignores a descendant's opt-out.
- Trigger dispatch: `AND t.sealed = FALSE` inside TRIGGER_SUPPRESSION_ANTIJOIN
  — a sealed row is never excluded, so it fires through the suppression (one
  edit covers list_matching_kv/docs/files + pubsub fan-out).
- Route rebuild: compile_effective_routes gates its suppression `continue` on
  `!er.route.sealed`, so a sealed inherited route stays in the app's slice.

Pinned by tests/sealed_templates.rs (live-DB): a sealed trigger + route
survive an app's suppression of both; an unsealed sibling with the same
suppression is still declined — the gate is exactly `sealed`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-01 19:34:22 +02:00
parent 95f20b4add
commit 247e2836b8
3 changed files with 244 additions and 2 deletions

View File

@@ -28,7 +28,8 @@ pub(crate) const TRIGGER_SUPPRESSION_ANTIJOIN: &str = " \
JOIN scripts s ON s.id = t.script_id \
WHERE ts.app_id = $1 AND ts.target_kind = 'trigger' \
AND LOWER(ts.reference) = LOWER(s.name) \
AND t.group_id IS NOT NULL)";
AND t.group_id IS NOT NULL \
AND t.sealed = FALSE)";
#[derive(Debug, thiserror::Error)]
pub enum TriggerRepoError {