feat(shared-triggers): visibility + tests + docs (M2.5)

- `shared` column in `pic triggers ls --group` (TriggerTemplateInfo + DTO +
  report + renderer).
- manager-core/tests/shared_triggers.rs: a shared write matches the group's
  shared trigger and NOT a same-named per-app trigger; a per-app write matches
  the app trigger and NOT the shared one (the `shared` flag is the boundary).
- shared_triggers journey: declarative authoring applies, ls --group shows
  shared, an undeclared-collection shared trigger + an app shared trigger are
  rejected.
- docs: §11.6 + CLAUDE.md move shared-collection triggers from Deferred to
  implemented.

Completes M2. (Pre-existing async-dispatcher timing flakes pass in isolation.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-01 21:05:30 +02:00
parent f04eaed0b7
commit 9a4b83dfcf
8 changed files with 376 additions and 7 deletions

View File

@@ -1160,11 +1160,23 @@ Resolved items now live inline next to their topic. What genuinely remains:
> (app A writes, app B reads/finds/gets the same bytes; a sibling-subtree app gets
> `CollectionNotShared`).
>
> **Deferred (documented gaps):** write-triggers/events on shared collections (the "group trigger
> has no app to watch" problem); **topics/queue** shared collections (trigger-centric, same gap);
> per-group total-size quotas + write-rate limits; CAS/`set_if`; an operator admin API for shared
> blobs (scripts use the SDK; `pic collections ls` shows the marker — matches KV/docs); app-declared
> collections. Multi-node tree-apply leans on the runtime backstop for no-op edges, as elsewhere.
> **Shared-collection triggers (✅, M2).** A write to a shared collection now fires a trigger,
> resolving the "group trigger has no app to watch" gap. A group-owned trigger marked `shared = true`
> (`shared BOOLEAN` on `triggers`, `0061`) watches the group's shared collection; the per-app match
> queries add `AND t.shared = FALSE` and new `list_matching_shared_{kv,docs,files}` select
> `shared = TRUE` triggers on the OWNING group — the `shared` flag is the namespace boundary between a
> shared collection and a same-named per-app one. `ServiceEventEmitter::emit_shared` fires from the
> group services (wired via `with_events`), stamping the WRITER `app_id` so the handler runs under the
> writer (the "group template runs under the firing app" model). Authored on a group
> `[[triggers.kv|docs|files]]`; `validate_bundle_for` rejects `shared` on an app owner, a non-collection
> kind, or an undeclared collection. Read-only `shared` column in `pic triggers ls --group`; pinned by
> `tests/shared_triggers.rs` + the `shared_triggers` journey.
>
> **Deferred (documented gaps):** shared **pubsub** triggers (no shared topic store yet); **topics/queue**
> shared collections (trigger-centric, same gap); per-group total-size quotas + write-rate limits;
> CAS/`set_if`; an operator admin API for shared blobs (scripts use the SDK; `pic collections ls` shows
> the marker — matches KV/docs); app-declared collections. Multi-node tree-apply leans on the runtime
> backstop for no-op edges, as elsewhere.
### 11.1 Re-sequencing review (post-Phase-3)