From 690d588a046b1408c5a7b81b6dd9c096896f84c0 Mon Sep 17 00:00:00 2001 From: e3mrah <81884938+emrahbaysal@users.noreply.github.com> Date: Thu, 14 May 2026 16:38:30 +0400 Subject: [PATCH] fix(canvas): rollup preserves leaf status when group has no children (#1478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug found on prov #76 rollup: cluster-bootstrap (a leaf with family='bootstrap') was being treated as an empty group and reset from succeeded → pending. That status then cascaded up through provisioner (whose 5 children include cluster-bootstrap) making provisioner show pending despite all 5 phase jobs being succeeded. Fix: when a node in groupNodeIdx has zero children in contains rels, keep its STORED status instead of forcing pending. This preserves leaf-with-group-family nodes (cluster-bootstrap) AND empty phase groups (handover/apps before their Jobs exist). Co-authored-by: e3mrah Co-authored-by: Claude Opus 4.7 (1M context) --- .../internal/handler/flow_snapshot_local.go | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/products/catalyst/bootstrap/api/internal/handler/flow_snapshot_local.go b/products/catalyst/bootstrap/api/internal/handler/flow_snapshot_local.go index ccf6e303..3dd96d19 100644 --- a/products/catalyst/bootstrap/api/internal/handler/flow_snapshot_local.go +++ b/products/catalyst/bootstrap/api/internal/handler/flow_snapshot_local.go @@ -716,23 +716,39 @@ func (h *Handler) flowSnapshotFromJobs(deploymentID string) (*flowSnapshotLocalM // (which may themselves be groups) to be resolved first. var resolve func(id string) string memo := map[string]string{} + // idxByID gives O(1) status lookup for the fallback below. + idxByID := map[string]int{} + for i, n := range nodes { + idxByID[n.ID] = i + } resolve = func(id string) string { if s, ok := memo[id]; ok { return s } - // Leaf node — use stored status. + // Leaf node (not a group/bootstrap) — use stored status. if _, isGroup := groupNodeIdx[id]; !isGroup { - for _, n := range nodes { - if n.ID == id { - memo[id] = n.Status - return n.Status - } + if i, ok := idxByID[id]; ok { + memo[id] = nodes[i].Status + return nodes[i].Status } memo[id] = "pending" return "pending" } kids := childrenOf[id] if len(kids) == 0 { + // Group with no children — keep its stored status + // rather than forcing pending. This covers two cases: + // (1) genuine leaf nodes that carry family=bootstrap + // (cluster-bootstrap is a single-row "bootstrap" + // family job, not a parent of anything); (2) phase + // groups that legitimately have no descendants yet + // (handover/apps on a fresh prov before those Jobs + // are emitted) — their hardcoded "pending" status + // should bubble through unchanged. + if i, ok := idxByID[id]; ok { + memo[id] = nodes[i].Status + return nodes[i].Status + } memo[id] = "pending" return "pending" }