From bbfdb1bf47288e6e0da7baa10e9039904a9d6f24 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 11 Jul 2026 13:25:06 +0200 Subject: [PATCH] fix(apply): bound the blast-radius subtree down-walk CTE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `group_blast_radius`'s `app_chain` up-walk is depth-capped (`ac.depth < 64`, added by 76926de to stop a should-be-impossible `groups.parent_id` cycle from spinning the recursive CTE forever), but the sibling `subtree` down-walk in the same query was left unbounded. A `parent_id` cycle would loop `subtree` indefinitely and hang the plan — the exact exposure 76926de set out to close, left half-done on the descendant side. Add a `depth` column + `WHERE s.depth < 64` to `subtree`, mirroring `app_chain`. Defense-in-depth: the reparent cycle-guard already prevents `parent_id` cycles, so this hardens against an unreachable state — but that is precisely the bar 76926de applied. Surfaced by a post-adoption audit of the §7/§6 ownership code. Verified: 416 manager-core lib tests; 12 ownership/create/divergence/approval journeys (incl. plan_previews_ownership_and_blast_radius, which drives this query); clippy -D warnings + fmt clean. Co-Authored-By: Claude Opus 4.8 --- crates/manager-core/src/apply_service.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/manager-core/src/apply_service.rs b/crates/manager-core/src/apply_service.rs index 194df18..8e1f308 100644 --- a/crates/manager-core/src/apply_service.rs +++ b/crates/manager-core/src/apply_service.rs @@ -2739,9 +2739,10 @@ impl ApplyService { // `exclude` (the planning project) or by nothing are dropped. let rows: Vec<(String, i64)> = sqlx::query_as( "WITH RECURSIVE subtree AS ( - SELECT id FROM groups WHERE id = $1 + SELECT id, 0 AS depth FROM groups WHERE id = $1 UNION ALL - SELECT g.id FROM groups g JOIN subtree s ON g.parent_id = s.id + SELECT g.id, s.depth + 1 FROM groups g JOIN subtree s ON g.parent_id = s.id + WHERE s.depth < 64 ), app_chain AS ( SELECT a.id AS app_id, a.group_id AS gid, 0 AS depth