Commit Graph

4 Commits

Author SHA1 Message Date
MechaCat02
9626d15863 feat(shared-topics): GroupPubsubService + shared publish fan-out + SDK handle (D2)
The runtime for shared topics:
- pubsub_repo: fan_out_publish gains `AND t.shared = FALSE` (a shared
  trigger never fires on a per-app publish); new fan_out_shared_publish
  matches `shared = true` pubsub triggers on the resolved owning group,
  each delivery stamped with the writer app_id (M2 model).
- GroupPubsubService trait (shared) + GroupPubsubServiceImpl: resolve the
  owning group (kind='topic') from cx.app_id's chain, require editor+
  (GroupPubsubPublish, fails closed for anon), size-cap, fan out.
- SDK: `pubsub::shared_topic("name")` -> GroupTopicHandle with
  `.publish(subtopic, msg)` / `.publish(msg)`; wired through Services +
  the picloud binary (reuses the one PubsubRepo).
- authz: Capability::GroupPubsubPublish(GroupId), editor+ / script:write.

The owning-group chain walk is the isolation boundary; a sibling-subtree
app never resolves the topic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:48:59 +02:00
MechaCat02
4b5bf72a66 feat(suppress): consume per-app suppressions at trigger + route dispatch (§11 tail S3)
The runtime half — suppressions now actually decline inherited templates.

- Triggers (live, per-event): a correlated NOT EXISTS anti-join
  (TRIGGER_SUPPRESSION_ANTIJOIN) appended to all four dispatch match
  queries (list_matching_kv/docs/files + the pubsub fan-out). It excludes a
  group-owned trigger whose handler script name the firing app suppresses;
  `$1` is the firing app (already bound), and the `t.group_id IS NOT NULL`
  guard keeps an app's OWN trigger unsuppressable.
- Routes (rebuild-time): compile_effective_routes takes a
  `suppressed_paths: &HashSet<(AppId, path)>` and drops an inherited
  (`depth > 0`) route at a suppressed path — the binding 404s.
  rebuild_route_table loads the set via a new
  RouteRepository::list_route_suppressions, so every existing rebuild edge
  (route CRUD, apply, tree mutations) already applies it. No new
  invalidation edges; the marker CASCADEs on app delete.

Pinned by tests/template_suppression.rs (live DB): a suppressing app
matches NEITHER the inherited trigger NOR route; a sibling that did not
suppress still inherits both; the app's OWN trigger on the suppressed
handler still fires (suppression is inheritance-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 07:39:06 +02:00
MechaCat02
72802de644 feat(modules): live chain-union dispatch for group trigger templates (§11 tail T3)
The runtime heart: a descendant app's event now fires its ancestor
groups' trigger templates, resolved LIVE (no materialization).

- trigger_repo: list_matching_kv/docs/files prepend CHAIN_LEVELS_CTE and
  `JOIN chain c ON (t.app_id = c.app_owner OR t.group_id = c.group_owner)`
  (bind $1 = app_id), so each returns the app's own triggers PLUS
  ancestor-group templates in one statement. NULL-comparison semantics
  keep app/group rows from cross-matching; each row matches at exactly one
  chain depth (no dup fan-out).
- pubsub_repo: same union on the publish fan-out query.
- The outbox row stamps the firing app_id + the (group-owned) script_id;
  the dispatcher's existing script_invocable() (app-owned OR invocable via
  chain membership) already runs a group handler under a descendant app —
  the Phase-4 inheriting-app boundary, unchanged.

Glob/ops/topic filtering in Rust is untouched. 404 manager-core unit
tests green; clippy -D clean. Cross-subtree firing is journey-proven in T4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:43:01 +02:00
MechaCat02
834c787ee1 feat(v1.1.5): pubsub::publish_durable SDK + pubsub:* triggers
Durable pub/sub through the universal outbox — the sixth trigger kind.

- `pubsub::publish_durable(topic, message)` Rhai SDK (no handle; topics
  ARE the grouping unit). Message JSON-encoded; Blobs base64 at any
  depth.
- `PubsubService` trait in picloud-shared with the topic matcher +
  validator (exact / `<prefix>.*` / `*`; mid-pattern wildcards
  rejected). `PostgresPubsubRepo` + `PubsubServiceImpl` in manager-core.
- Publish-time fan-out: one outbox row per matching enabled pubsub
  trigger, all in ONE transaction (no half-fan-out on crash). No
  matching trigger → publish succeeds silently, zero rows.
- `pubsub:*` trigger kind via Layout-E (0020: widen both CHECKs +
  pubsub_trigger_details + partial index), TriggerEvent::Pubsub +
  ctx.event.pubsub, dispatcher arm, admin endpoint POST /triggers/pubsub
  (validates topic pattern + reuses validate_trigger_target).
- AppPubsubPublish capability → script:write (seven-scope held).
- Dashboard Pub/Sub trigger form on the Triggers tab + list rendering.

publish_ephemeral stays deferred to v1.2. ~18 new tests (service
in-memory incl. transactional-rollback, shared matcher, bridge
encoding). No DB required for the suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:37:06 +02:00