feat(sealed): sealed journey + docs (§11 tail M5)

- tests/sealed.rs: a group declares a sealed [[routes]] template; a descendant
  that suppresses it still serves the path, apply warns "sealed — no effect",
  routes ls --group shows sealed=true, and sealing an [app] route is rejected.
- docs: §4.5 trust-model callout + CLAUDE.md move `sealed` from Deferred to
  implemented — group templates are advisory-by-default *unless* sealed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-01 19:42:18 +02:00
parent fa0702870a
commit bfa4b48930
4 changed files with 197 additions and 11 deletions

View File

@@ -438,16 +438,26 @@ Two distinct constraints:
> A dangling suppress (matching no inherited template) is an apply-time **warning**, not an error;
> read-only `pic suppress ls --app`.
>
> **Trust-model consequence — group templates are now advisory-by-default.** Before opt-out, a group
> template was *guaranteed* to run on every descendant; with suppression the guarantee weakens to "runs
> unless the descendant declines." This is fine for convenience templates (a shared webhook, a default
> route) but is a **footgun for compliance** templates — an audit-logging or security trigger a
> multi-tenant operator deploys at a group can be silently opted out of by a tenant app. There is
> currently **no non-suppressible flag**; an operator who needs an unconditional hook must not rely on a
> group template for it (deploy it per-app, or gate at the platform layer). **Deferred:** a `sealed` /
> `mandatory` marker on a group template that the trigger anti-join + route filter skip (making it
> non-declinable) — cheap to add since both filters are centralized; and group-level suppression
> (decline an ancestor's template for a whole subtree).
> **Trust-model consequence — group templates are advisory-by-default *unless* `sealed`.** Before opt-out,
> a group template was *guaranteed* to run on every descendant; with suppression the guarantee weakens to
> "runs unless the descendant declines." This is fine for convenience templates (a shared webhook, a
> default route) but was a **footgun for compliance** templates — an audit-logging or security trigger a
> multi-tenant operator deploys at a group could be silently opted out of by a tenant app.
>
> **Resolved — `sealed` (mandatory) templates (✅).** A `[group]` marks a route or event-trigger template
> `sealed = true`; the two suppression filters skip a sealed row, so a descendant's `[suppress]` is
> **ignored** and it fires/serves on every descendant. Backing: a `sealed BOOLEAN` column on `triggers` +
> `routes` (`0059_sealed_templates.sql`); the trigger anti-join gains `AND t.sealed = FALSE` (a sealed row
> is never excluded → fires through the opt-out), and `compile_effective_routes` gates its suppression
> `continue` on `!er.route.sealed`. `sealed` is authored **per-template** and is **group-only** —
> `validate_bundle_for` rejects it on an app owner (an app route/trigger is never inherited, so sealing it
> is meaningless). It only *strengthens* the guarantee (a sealed template can't be declined; it never
> grants new reach — the chain walk stays the isolation boundary). It joins the route Update comparison +
> the trigger identity, so toggling it re-applies (a trigger toggle needs `--prune` to drop the stale
> row, like any definitional change). A suppression that matches only sealed templates is an apply-time
> **warning** ("… is sealed — the suppression has no effect"); `pic triggers/routes ls --group` show a
> `sealed` column. Pinned by `manager-core/tests/sealed_templates.rs` + the `sealed` journey.
> **Deferred:** group-level suppression (decline an ancestor's template for a whole subtree).
### 4.6 Secrets & `pull`