feat(cli): pic triggers ls --group + group-template journeys + docs (§11 tail T4)

- apply_service: trigger_report(group) → TriggerTemplateInfo
  (kind/target/script/enabled); resolve_inherited_targets_for(Group) now
  surfaces the group's OWN endpoint scripts so a template's handler
  validates (fixes "binds to unknown script" when the handler is a
  pre-existing group script, not declared in the same manifest).
- apply_api: GET /groups/{id}/triggers (GroupScriptsRead).
- CLI: `pic triggers ls --group <g>` (--app/--group mutually exclusive)
  + the client method + DTO.
- tests/group_trigger_templates.rs (manager-core, live DB): the chain
  union matches a descendant app's kv insert against the group template
  and NOT a sibling subtree — the isolation boundary, deterministic.
- tests/group_triggers.rs (journey): apply a kv template, ls --group
  shows it, re-apply NoOp, cron-on-group rejected.
- docs: design §4.5 (live-event-kinds decision + deferrals), CLAUDE.md.

Full journey suite 119/119; workspace tests 0 failures; clippy -D clean;
schema unchanged (blessed in T1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-30 21:02:03 +02:00
parent 72802de644
commit c492a08775
10 changed files with 519 additions and 10 deletions

View File

@@ -377,9 +377,25 @@ Two distinct constraints:
> **Ergonomic debt (accepted, watch it):** because triggers don't inherit, 100 tenant apps each
> needing the same 5 triggers = 500 declarations. The fix is group trigger/route **templates** that
> fan out per descendant (a *template/instantiation* mechanism, not inheritance) — deferred, but it
> bites early if tenant cardinality is high. Pressure-test against real tenant counts before
> committing to the narrow-inheritance choice (§5.1).
> fan out per descendant — deferred, but it bites early if tenant cardinality is high. Pressure-test
> against real tenant counts before committing to the narrow-inheritance choice (§5.1).
>
> **Shipped — group TRIGGER templates (live, event kinds).** Implemented as **live inheritance via the
> ancestor-chain CTE**, not the materialized "instantiation" this note first sketched — consistent with
> how vars/secrets/scripts/§11.6 collections all resolve. A `[group]` declares an EVENT trigger
> (kv/docs/files/pubsub) binding a group-owned handler; `triggers` gained a polymorphic owner
> (`0056_group_triggers.sql`, nullable `group_id`, mirrors `0050`), and the dispatcher's
> `list_matching_kv/docs/files` + the pubsub publish fan-out prepend `CHAIN_LEVELS_CTE` and
> `JOIN chain c ON (t.app_id = c.app_owner OR t.group_id = c.group_owner)` — so a descendant app's event
> matches its own triggers **plus** ancestor-group templates in one query, the handler running under the
> firing app's `app_id` (the Phase-4 boundary). **That walk is the isolation boundary**: a
> sibling-subtree app never sees the template (pinned by `tests/group_trigger_templates.rs`). Authoring
> is `[[triggers.kv]]` (etc.) on a `[group]`; read-only `pic triggers ls --group`. **Deferred:** the
> **stateful** kinds (cron `last_fired_at`, queue advisory-lock, email sealed secret) need per-app rows
> → materialization, rejected on a `[group]`; **route templates** (same live shape, but the in-memory
> `RouteTable` would need inherited templates per app + new recompile-invalidation edges); per-app
> opt-out / cross-owner dedup (a descendant re-declaring an identical trigger double-fires — "overlapping
> triggers coexist").
### 4.6 Secrets & `pull`