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:
@@ -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`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user