From 95007c124e020ac1b032af069bd7455ade4a7871 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Tue, 30 Jun 2026 21:11:56 +0200 Subject: [PATCH] =?UTF-8?q?fix(triggers):=20SELECT=20group=5Fid=20in=20Tri?= =?UTF-8?q?ggerRepo::get=20for=20template=20fidelity=20(=C2=A711=20tail=20?= =?UTF-8?q?review)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `get` can fetch any trigger by id, including a group-owned TEMPLATE. It omitted `group_id` and leaned on `#[sqlx(default)]`, so a template would hydrate with both owners None — silently breaking the exactly-one-owner invariant in memory. No caller hits this today (interactive trigger management is app-scoped; prune deletes by id), but the fetch now round-trips the owner faithfully and stays honest for future callers. `list_for_app` is left as-is: its rows are all app-owned, so group_id is legitimately NULL there. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/manager-core/src/trigger_repo.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/manager-core/src/trigger_repo.rs b/crates/manager-core/src/trigger_repo.rs index e3d0e90..4ce15b5 100644 --- a/crates/manager-core/src/trigger_repo.rs +++ b/crates/manager-core/src/trigger_repo.rs @@ -1271,8 +1271,12 @@ impl TriggerRepo for PostgresTriggerRepo { } async fn get(&self, id: TriggerId) -> Result, TriggerRepoError> { + // §11 tail: `get` can fetch ANY trigger by id, including a group-owned + // TEMPLATE, so it must SELECT `group_id` (unlike `list_for_app`, whose + // rows are all app-owned) — else a template would hydrate with both + // owners None, breaking the exactly-one invariant in memory. let parent: Option = sqlx::query_as( - "SELECT id, app_id, script_id, name, kind, enabled, dispatch_mode, \ + "SELECT id, app_id, group_id, script_id, name, kind, enabled, dispatch_mode, \ retry_max_attempts, retry_backoff, retry_base_ms, \ registered_by_principal, created_at, updated_at \ FROM triggers WHERE id = $1",