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>
This commit is contained in:
@@ -16,6 +16,8 @@ use picloud_shared::{topic_matches, AdminUserId, AppId, ExecutionId};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config_resolver::CHAIN_LEVELS_CTE;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PubsubRepoError {
|
||||
#[error("database error: {0}")]
|
||||
@@ -78,12 +80,18 @@ impl PubsubRepo for PostgresPubsubRepo {
|
||||
// Load all enabled pubsub triggers for the app; filter by topic
|
||||
// pattern in Rust (keeps the query simple, honours the
|
||||
// empty/`*`/prefix semantics without teaching SQL about globs).
|
||||
let rows: Vec<PubsubTriggerRow> = sqlx::query_as(
|
||||
"SELECT t.id, t.script_id, d.topic_pattern \
|
||||
// §11 tail: the chain union picks up the app's own pubsub triggers AND
|
||||
// ancestor-group pubsub TEMPLATES (live, no materialization). The outbox
|
||||
// row below stamps the firing `ctx.app_id`, so a template runs under the
|
||||
// publishing app — the inheriting-app boundary.
|
||||
let rows: Vec<PubsubTriggerRow> = sqlx::query_as(&format!(
|
||||
"{CHAIN_LEVELS_CTE} \
|
||||
SELECT t.id, t.script_id, d.topic_pattern \
|
||||
FROM triggers t \
|
||||
JOIN pubsub_trigger_details d ON d.trigger_id = t.id \
|
||||
WHERE t.app_id = $1 AND t.kind = 'pubsub' AND t.enabled = TRUE",
|
||||
)
|
||||
JOIN chain c ON (t.app_id = c.app_owner OR t.group_id = c.group_owner) \
|
||||
WHERE t.kind = 'pubsub' AND t.enabled = TRUE"
|
||||
))
|
||||
.bind(ctx.app_id.into_inner())
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user