STET

agent_patch

runs/2026-02-21__03-36-16__gpt-5-1-codex-mini/flux-pr-1232/flux-pr-1232.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/agent-logs/agent.patch

13888 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1232.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/v2/pkg/engine/postprocess/deduplicate_single_fetches.go b/app/v2/pkg/engine/postprocess/deduplicate_single_fetches.go
index 9fb8c1c..a6fc309 100644
--- a/tmp/agent-patch-flux-pr-1232.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/v2/pkg/engine/postprocess/deduplicate_single_fetches.go
+++ b/app/v2/pkg/engine/postprocess/deduplicate_single_fetches.go
@@ -11,19 +11,199 @@ type deduplicateSingleFetches struct {
 }
 
 func (d *deduplicateSingleFetches) ProcessFetchTree(root *resolve.FetchTreeNode) {
-	if d.disable {
+	if d.disable || root == nil {
 		return
 	}
-	for i := range root.ChildNodes {
+
+	merged := make(map[int]int)
+	for i := 0; i < len(root.ChildNodes); i++ {
+		left := root.ChildNodes[i]
+		if !d.isSingleFetchNode(left) {
+			continue
+		}
 		for j := i + 1; j < len(root.ChildNodes); j++ {
-			if root.ChildNodes[i].Item.Equals(root.ChildNodes[j].Item) {
-				root.ChildNodes[i].Item.FetchPath = d.mergeFetchPath(root.ChildNodes[i].Item.FetchPath, root.ChildNodes[j].Item.FetchPath)
+			right := root.ChildNodes[j]
+			if !d.isSingleFetchNode(right) {
+				continue
+			}
+			if !left.Item.EqualsIgnoringCoordinateDependencies(right.Item) {
+				continue
+			}
+			leftFetch, ok := left.Item.Fetch.(*resolve.SingleFetch)
+			if !ok {
+				continue
+			}
+			rightFetch, ok := right.Item.Fetch.(*resolve.SingleFetch)
+			if !ok {
+				continue
+			}
+
+			merged[rightFetch.FetchDependencies.FetchID] = leftFetch.FetchDependencies.FetchID
+			left.Item.FetchPath = d.mergeFetchPath(left.Item.FetchPath, right.Item.FetchPath)
+			leftFetch.FetchDependencies.DependsOnFetchIDs = d.mergeDependsOnIDs(leftFetch.FetchDependencies.DependsOnFetchIDs, rightFetch.FetchDependencies.DependsOnFetchIDs)
+			leftFetch.FetchConfiguration.CoordinateDependencies = d.mergeCoordinateDependencies(leftFetch.FetchConfiguration.CoordinateDependencies, rightFetch.FetchConfiguration.CoordinateDependencies)
 
-				root.ChildNodes = append(root.ChildNodes[:j], root.ChildNodes[j+1:]...)
-				j--
+			root.ChildNodes = append(root.ChildNodes[:j], root.ChildNodes[j+1:]...)
+			j--
+		}
+	}
+
+	if len(merged) > 0 {
+		d.updateDependencyReferences(root, merged)
+	}
+}
+
+func (d *deduplicateSingleFetches) isSingleFetchNode(node *resolve.FetchTreeNode) bool {
+	return node != nil && node.Kind == resolve.FetchTreeNodeKindSingle && node.Item != nil && node.Item.Fetch != nil
+}
+
+func (d *deduplicateSingleFetches) mergeDependsOnIDs(left, right []int) []int {
+	if len(left) == 0 {
+		return append([]int(nil), right...)
+	}
+	if len(right) == 0 {
+		return append([]int(nil), left...)
+	}
+
+	seen := make(map[int]struct{}, len(left)+len(right))
+	out := append([]int(nil), left...)
+	for _, id := range left {
+		seen[id] = struct{}{}
+	}
+	for _, id := range right {
+		if _, ok := seen[id]; ok {
+			continue
+		}
+		seen[id] = struct{}{}
+		out = append(out, id)
+	}
+	return out
+}
+
+func (d *deduplicateSingleFetches) mergeCoordinateDependencies(left, right []resolve.FetchDependency) []resolve.FetchDependency {
+	if len(left) == 0 {
+		return append([]resolve.FetchDependency(nil), right...)
+	}
+	if len(right) == 0 {
+		return append([]resolve.FetchDependency(nil), left...)
+	}
+
+	out := append([]resolve.FetchDependency(nil), left...)
+	for _, dep := range right {
+		if d.coordinateDependencyExists(out, dep) {
+			continue
+		}
+		out = append(out, dep)
+	}
+	return out
+}
+
+func (d *deduplicateSingleFetches) coordinateDependencyExists(list []resolve.FetchDependency, candidate resolve.FetchDependency) bool {
+	for _, existing := range list {
+		if d.fetchDependencyEqual(existing, candidate) {
+			return true
+		}
+	}
+	return false
+}
+
+func (d *deduplicateSingleFetches) fetchDependencyEqual(a, b resolve.FetchDependency) bool {
+	if a.Coordinate != b.Coordinate {
+		return false
+	}
+	if a.IsUserRequested != b.IsUserRequested {
+		return false
+	}
+	return d.fetchDependencyOriginsEqual(a.DependsOn, b.DependsOn)
+}
+
+func (d *deduplicateSingleFetches) fetchDependencyOriginsEqual(a, b []resolve.FetchDependencyOrigin) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	used := make([]bool, len(b))
+	for _, originA := range a {
+		matchFound := false
+		for i, originB := range b {
+			if used[i] {
+				continue
+			}
+			if d.fetchDependencyOriginEqual(originA, originB) {
+				used[i] = true
+				matchFound = true
+				break
+			}
+		}
+		if !matchFound {
+			return false
+		}
+	}
+	return true
+}
+
+func (d *deduplicateSingleFetches) fetchDependencyOriginEqual(a, b resolve.FetchDependencyOrigin) bool {
+	return a.FetchID == b.FetchID &&
+		a.Subgraph == b.Subgraph &&
+		a.Coordinate == b.Coordinate &&
+		a.IsKey == b.IsKey &&
+		a.IsRequires == b.IsRequires
+}
+
+func (d *deduplicateSingleFetches) updateDependencyReferences(root *resolve.FetchTreeNode, mapping map[int]int) {
+	if root == nil || len(mapping) == 0 {
+		return
+	}
+	d.applyMapping(root, mapping)
+}
+
+func (d *deduplicateSingleFetches) applyMapping(node *resolve.FetchTreeNode, mapping map[int]int) {
+	if node == nil {
+		return
+	}
+	if node.Item != nil {
+		if single, ok := node.Item.Fetch.(*resolve.SingleFetch); ok {
+			single.FetchDependencies.DependsOnFetchIDs = d.mapIDs(single.FetchDependencies.DependsOnFetchIDs, mapping)
+			for i := range single.FetchConfiguration.CoordinateDependencies {
+				for j := range single.FetchConfiguration.CoordinateDependencies[i].DependsOn {
+					single.FetchConfiguration.CoordinateDependencies[i].DependsOn[j].FetchID =
+						d.resolveMapping(mapping, single.FetchConfiguration.CoordinateDependencies[i].DependsOn[j].FetchID)
+				}
 			}
 		}
 	}
+	if node.Trigger != nil {
+		d.applyMapping(node.Trigger, mapping)
+	}
+	for _, child := range node.ChildNodes {
+		d.applyMapping(child, mapping)
+	}
+}
+
+func (d *deduplicateSingleFetches) mapIDs(list []int, mapping map[int]int) []int {
+	if len(list) == 0 {
+		return list
+	}
+	out := make([]int, 0, len(list))
+	seen := make(map[int]struct{}, len(list))
+	for _, id := range list {
+		resolved := d.resolveMapping(mapping, id)
+		if _, ok := seen[resolved]; ok {
+			continue
+		}
+		seen[resolved] = struct{}{}
+		out = append(out, resolved)
+	}
+	return out
+}
+
+func (d *deduplicateSingleFetches) resolveMapping(mapping map[int]int, id int) int {
+	for {
+		next, ok := mapping[id]
+		if !ok || next == id {
+			return id
+		}
+		id = next
+	}
 }
 
 func (d *deduplicateSingleFetches) mergeFetchPath(left, right []resolve.FetchItemPathElement) []resolve.FetchItemPathElement {
diff --git a/tmp/agent-patch-flux-pr-1232.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go b/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go
index 88cf2be..fa3121a 100644
--- a/tmp/agent-patch-flux-pr-1232.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go
+++ b/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go
@@ -115,4 +115,108 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) {
 
 		assert.Equal(t, output, input)
 	})
+
+	t.Run("ignores coordinate dependencies when deduplicating", func(t *testing.T) {
+		withCoords := resolve.Single(&resolve.SingleFetch{
+			FetchDependencies: resolve.FetchDependencies{FetchID: 1},
+			FetchConfiguration: resolve.FetchConfiguration{
+				Input: "a",
+				CoordinateDependencies: []resolve.FetchDependency{
+					{
+						Coordinate:      resolve.GraphCoordinate{TypeName: "Query", FieldName: "keepField"},
+						IsUserRequested: true,
+					},
+				},
+			},
+		})
+		withAdditionalCoords := resolve.Single(&resolve.SingleFetch{
+			FetchDependencies: resolve.FetchDependencies{FetchID: 2},
+			FetchConfiguration: resolve.FetchConfiguration{
+				Input: "a",
+				CoordinateDependencies: []resolve.FetchDependency{
+					{
+						Coordinate:      resolve.GraphCoordinate{TypeName: "Query", FieldName: "otherField"},
+						IsUserRequested: true,
+					},
+					{
+						Coordinate:      resolve.GraphCoordinate{TypeName: "Query", FieldName: "keepField"},
+						IsUserRequested: true,
+					},
+				},
+			},
+		})
+
+		input := &resolve.FetchTreeNode{
+			ChildNodes: []*resolve.FetchTreeNode{
+				withCoords,
+				withAdditionalCoords,
+			},
+		}
+
+		dedup := &deduplicateSingleFetches{}
+		dedup.ProcessFetchTree(input)
+
+		assert.Len(t, input.ChildNodes, 1)
+		fetch := input.ChildNodes[0].Item.Fetch.(*resolve.SingleFetch)
+		assert.Equal(t, 1, fetch.FetchDependencies.FetchID)
+		assert.Len(t, fetch.FetchConfiguration.CoordinateDependencies, 2)
+
+		fields := make(map[string]struct{}, 2)
+		for _, dep := range fetch.FetchConfiguration.CoordinateDependencies {
+			fields[dep.Coordinate.FieldName] = struct{}{}
+		}
+		assert.Contains(t, fields, "keepField")
+		assert.Contains(t, fields, "otherField")
+	})
+
+	t.Run("updates dependent fetches when duplicate is removed", func(t *testing.T) {
+		keep := resolve.Single(&resolve.SingleFetch{
+			FetchDependencies: resolve.FetchDependencies{FetchID: 1},
+			FetchConfiguration: resolve.FetchConfiguration{
+				Input: "a",
+			},
+		})
+		duplicate := resolve.Single(&resolve.SingleFetch{
+			FetchDependencies: resolve.FetchDependencies{FetchID: 2},
+			FetchConfiguration: resolve.FetchConfiguration{
+				Input: "a",
+			},
+		})
+		depending := resolve.Single(&resolve.SingleFetch{
+			FetchDependencies: resolve.FetchDependencies{FetchID: 3, DependsOnFetchIDs: []int{2}},
+			FetchConfiguration: resolve.FetchConfiguration{
+				Input: "b",
+				CoordinateDependencies: []resolve.FetchDependency{
+					{
+						Coordinate:      resolve.GraphCoordinate{TypeName: "Query", FieldName: "dependent"},
+						IsUserRequested: true,
+						DependsOn: []resolve.FetchDependencyOrigin{
+							{
+								FetchID:    2,
+								Subgraph:   "sub",
+								Coordinate: resolve.GraphCoordinate{TypeName: "Query", FieldName: "field"},
+							},
+						},
+					},
+				},
+			},
+		})
+
+		input := &resolve.FetchTreeNode{
+			ChildNodes: []*resolve.FetchTreeNode{
+				keep,
+				duplicate,
+				depending,
+			},
+		}
+
+		dedup := &deduplicateSingleFetches{}
+		dedup.ProcessFetchTree(input)
+
+		assert.Len(t, input.ChildNodes, 2)
+		assert.Equal(t, 1, input.ChildNodes[0].Item.Fetch.(*resolve.SingleFetch).FetchDependencies.FetchID)
+		dependentFetch := input.ChildNodes[1].Item.Fetch.(*resolve.SingleFetch)
+		assert.Equal(t, []int{1}, dependentFetch.FetchDependencies.DependsOnFetchIDs)
+		assert.Equal(t, 1, dependentFetch.FetchConfiguration.CoordinateDependencies[0].DependsOn[0].FetchID)
+	})
 }
diff --git a/tmp/agent-patch-flux-pr-1232.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/v2/pkg/engine/resolve/fetch.go b/app/v2/pkg/engine/resolve/fetch.go
index 9979256..e0d0ebe 100644
--- a/tmp/agent-patch-flux-pr-1232.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/v2/pkg/engine/resolve/fetch.go
+++ b/app/v2/pkg/engine/resolve/fetch.go
@@ -43,6 +43,14 @@ func FetchItemWithPath(fetch Fetch, responsePath string, path ...FetchItemPathEl
 }
 
 func (f *FetchItem) Equals(other *FetchItem) bool {
+	return f.equals(other, true)
+}
+
+func (f *FetchItem) EqualsIgnoringCoordinateDependencies(other *FetchItem) bool {
+	return f.equals(other, false)
+}
+
+func (f *FetchItem) equals(other *FetchItem, includeCoordinateDependencies bool) bool {
 	if len(f.FetchPath) != len(other.FetchPath) {
 		return false
 	}
@@ -68,7 +76,10 @@ func (f *FetchItem) Equals(other *FetchItem) bool {
 	if !ok {
 		return false
 	}
-	return l.FetchConfiguration.Equals(&r.FetchConfiguration)
+	if includeCoordinateDependencies {
+		return l.FetchConfiguration.Equals(&r.FetchConfiguration)
+	}
+	return l.FetchConfiguration.EqualsIgnoringCoordinateDependencies(&r.FetchConfiguration)
 }
 
 type FetchItemPathElement struct {
@@ -293,6 +304,16 @@ type FetchConfiguration struct {
 }
 
 func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool {
+	return fc.equals(other, true)
+}
+
+// EqualsIgnoringCoordinateDependencies performs the same comparison as Equals but ignores
+// coordinate-level dependency metadata, which is not relevant when deduplicating single fetches.
+func (fc *FetchConfiguration) EqualsIgnoringCoordinateDependencies(other *FetchConfiguration) bool {
+	return fc.equals(other, false)
+}
+
+func (fc *FetchConfiguration) equals(other *FetchConfiguration, includeCoordinateDependencies bool) bool {
 	if fc.Input != other.Input {
 		return false
 	}
@@ -319,22 +340,24 @@ func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool {
 	if fc.SetTemplateOutputToNullOnVariableNull != other.SetTemplateOutputToNullOnVariableNull {
 		return false
 	}
-	if !slices.EqualFunc(fc.CoordinateDependencies, other.CoordinateDependencies, func(a, b FetchDependency) bool {
-		if a.Coordinate != b.Coordinate {
-			return false
-		}
-		if a.IsUserRequested != b.IsUserRequested {
+	if includeCoordinateDependencies {
+		if !slices.EqualFunc(fc.CoordinateDependencies, other.CoordinateDependencies, func(a, b FetchDependency) bool {
+			if a.Coordinate != b.Coordinate {
+				return false
+			}
+			if a.IsUserRequested != b.IsUserRequested {
+				return false
+			}
+			return slices.EqualFunc(a.DependsOn, b.DependsOn, func(x, y FetchDependencyOrigin) bool {
+				return x.FetchID == y.FetchID &&
+					x.Subgraph == y.Subgraph &&
+					x.Coordinate == y.Coordinate &&
+					x.IsKey == y.IsKey &&
+					x.IsRequires == y.IsRequires
+			})
+		}) {
 			return false
 		}
-		return slices.EqualFunc(a.DependsOn, b.DependsOn, func(x, y FetchDependencyOrigin) bool {
-			return x.FetchID == y.FetchID &&
-				x.Subgraph == y.Subgraph &&
-				x.Coordinate == y.Coordinate &&
-				x.IsKey == y.IsKey &&
-				x.IsRequires == y.IsRequires
-		})
-	}) {
-		return false
 	}
 	return true
 }