fix(canvas): rollup preserves leaf status when group has no children (#1478)

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 <catalyst@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
e3mrah 2026-05-14 16:38:30 +04:00 committed by GitHub
parent 195c6b5bc5
commit 690d588a04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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"
}