agent_patch
runs/2026-02-28__13-38-10__gpt-5-4/flux-pr-1293/flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/agent-logs/agent.patch
38761 bytes
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/execution/engine/execution_engine.go b/app/execution/engine/execution_engine.go
index 51b2742..42302aa 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/execution/engine/execution_engine.go
+++ b/app/execution/engine/execution_engine.go
@@ -66,6 +66,7 @@ type ExecutionEngine struct {
logger abstractlogger.Logger
config Configuration
resolver *resolve.Resolver
+ buildFetchReasons bool
executionPlanCache *lru.Cache
apolloCompatibilityFlags apollocompatibility.Flags
}
@@ -140,6 +141,7 @@ func NewExecutionEngine(ctx context.Context, logger abstractlogger.Logger, engin
logger: logger,
config: engineConfig,
resolver: resolve.New(ctx, resolverOptions),
+ buildFetchReasons: resolverOptions.PropagateFetchReasons,
executionPlanCache: executionPlanCache,
apolloCompatibilityFlags: apollocompatibility.Flags{
ReplaceInvalidVarError: resolverOptions.ResolvableOptions.ApolloCompatibilityReplaceInvalidVarError,
@@ -255,7 +257,11 @@ func (e *ExecutionEngine) getCachedPlan(ctx *internalExecutionContext, operation
}
planner, _ := plan.NewPlanner(e.config.plannerConfig)
- planResult := planner.Plan(operation, definition, operationName, report)
+ var planOptions []plan.Opts
+ if e.buildFetchReasons {
+ planOptions = append(planOptions, plan.BuildFetchReasons())
+ }
+ planResult := planner.Plan(operation, definition, operationName, report, planOptions...)
if report.HasErrors() {
return nil
}
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go b/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
index 2e881ca..2d369ac 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
+++ b/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
@@ -3096,7 +3096,33 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
Input: `{"method":"POST","url":"http://user.service","body":{"query":"{user {account {address {line1 line2 __typename id}}}}"}}`,
DataSource: &Source{},
PostProcessing: DefaultPostProcessingConfiguration,
- FieldFetchReasons: []resolve.FetchReason{
+ },
+ Info: &resolve.FetchInfo{
+ FetchReasons: []resolve.FetchReason{
+ {
+ TypeName: "Address",
+ FieldName: "id",
+ BySubgraphs: []string{
+ "account.service",
+ "address-enricher.service",
+ "address.service",
+ },
+ IsKey: true,
+ },
+ {
+ TypeName: "Address",
+ FieldName: "line1",
+ BySubgraphs: []string{"account.service"},
+ IsRequires: true,
+ },
+ {
+ TypeName: "Address",
+ FieldName: "line2",
+ BySubgraphs: []string{"account.service"},
+ IsRequires: true,
+ },
+ },
+ PropagatedFetchReasons: []resolve.FetchReason{
{
TypeName: "Address",
FieldName: "id",
@@ -3129,8 +3155,36 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
},
DataSourceIdentifier: []byte("graphql_datasource.Source"),
FetchConfiguration: resolve.FetchConfiguration{
- Input: `{"method":"POST","url":"http://address-enricher.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename country city}}}","variables":{"representations":[$$0$$]}}}`,
- DataSource: &Source{},
+ Input: `{"method":"POST","url":"http://address-enricher.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename country city}}}","variables":{"representations":[$$0$$]}}}`,
+ DataSource: &Source{},
+ PostProcessing: SingleEntityPostProcessingConfiguration,
+ RequiresEntityFetch: true,
+ Variables: []resolve.Variable{
+ &resolve.ResolvableObjectVariable{
+ Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{
+ Nullable: true,
+ Fields: []*resolve.Field{
+ {
+ Name: []byte("__typename"),
+ Value: &resolve.String{
+ Path: []string{"__typename"},
+ },
+ OnTypeNames: [][]byte{[]byte("Address")},
+ },
+ {
+ Name: []byte("id"),
+ Value: &resolve.Scalar{
+ Path: []string{"id"},
+ },
+ OnTypeNames: [][]byte{[]byte("Address")},
+ },
+ },
+ }),
+ },
+ },
+ SetTemplateOutputToNullOnVariableNull: true,
+ },
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
Coordinate: resolve.GraphCoordinate{
@@ -3167,6 +3221,17 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
},
},
},
+ },
+ }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")),
+ resolve.SingleWithPath(&resolve.SingleFetch{
+ FetchDependencies: resolve.FetchDependencies{
+ FetchID: 2,
+ DependsOnFetchIDs: []int{0, 1},
+ },
+ DataSourceIdentifier: []byte("graphql_datasource.Source"),
+ FetchConfiguration: resolve.FetchConfiguration{
+ Input: `{"method":"POST","url":"http://address.service","body":{"query":"query($representations: [_Any!]!, $a: String!){_entities(representations: $representations){... on Address {__typename line3(test: $a) zip}}}","variables":{"a":"BOOM","representations":[$$0$$]}}}`,
+ DataSource: &Source{},
PostProcessing: SingleEntityPostProcessingConfiguration,
RequiresEntityFetch: true,
Variables: []resolve.Variable{
@@ -3188,24 +3253,27 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
},
OnTypeNames: [][]byte{[]byte("Address")},
},
+ {
+ Name: []byte("country"),
+ Value: &resolve.String{
+ Path: []string{"country"},
+ },
+ OnTypeNames: [][]byte{[]byte("Address")},
+ },
+ {
+ Name: []byte("city"),
+ Value: &resolve.String{
+ Path: []string{"city"},
+ },
+ OnTypeNames: [][]byte{[]byte("Address")},
+ },
},
}),
},
},
SetTemplateOutputToNullOnVariableNull: true,
},
- }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")),
- resolve.SingleWithPath(&resolve.SingleFetch{
- FetchDependencies: resolve.FetchDependencies{
- FetchID: 2,
- DependsOnFetchIDs: []int{0, 1},
- },
- DataSourceIdentifier: []byte("graphql_datasource.Source"),
- FetchConfiguration: resolve.FetchConfiguration{
- Input: `{"method":"POST","url":"http://address.service","body":{"query":"query($representations: [_Any!]!, $a: String!){_entities(representations: $representations){... on Address {__typename line3(test: $a) zip}}}","variables":{"a":"BOOM","representations":[$$0$$]}}}`,
- DataSource: &Source{},
- PostProcessing: SingleEntityPostProcessingConfiguration,
- RequiresEntityFetch: true,
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
Coordinate: resolve.GraphCoordinate{
@@ -3260,7 +3328,21 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
},
},
},
- FieldFetchReasons: []resolve.FetchReason{
+ FetchReasons: []resolve.FetchReason{
+ {
+ TypeName: "Address",
+ FieldName: "line3",
+ BySubgraphs: []string{"account.service"},
+ IsRequires: true,
+ },
+ {
+ TypeName: "Address",
+ FieldName: "zip",
+ BySubgraphs: []string{"account.service"},
+ IsRequires: true,
+ },
+ },
+ PropagatedFetchReasons: []resolve.FetchReason{
{
TypeName: "Address",
FieldName: "line3",
@@ -3274,6 +3356,19 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
IsRequires: true,
},
},
+ },
+ }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")),
+ resolve.SingleWithPath(&resolve.SingleFetch{
+ FetchDependencies: resolve.FetchDependencies{
+ FetchID: 3,
+ DependsOnFetchIDs: []int{0, 2},
+ },
+ DataSourceIdentifier: []byte("graphql_datasource.Source"),
+ FetchConfiguration: resolve.FetchConfiguration{
+ Input: `{"method":"POST","url":"http://account.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename fullAddress}}}","variables":{"representations":[$$0$$]}}}`,
+ DataSource: &Source{},
+ PostProcessing: SingleEntityPostProcessingConfiguration,
+ RequiresEntityFetch: true,
Variables: []resolve.Variable{
&resolve.ResolvableObjectVariable{
Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{
@@ -3287,23 +3382,37 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
OnTypeNames: [][]byte{[]byte("Address")},
},
{
- Name: []byte("id"),
- Value: &resolve.Scalar{
- Path: []string{"id"},
+ Name: []byte("line1"),
+ Value: &resolve.String{
+ Path: []string{"line1"},
},
OnTypeNames: [][]byte{[]byte("Address")},
},
{
- Name: []byte("country"),
+ Name: []byte("line2"),
Value: &resolve.String{
- Path: []string{"country"},
+ Path: []string{"line2"},
},
OnTypeNames: [][]byte{[]byte("Address")},
},
{
- Name: []byte("city"),
+ Name: []byte("line3"),
Value: &resolve.String{
- Path: []string{"city"},
+ Path: []string{"line3"},
+ },
+ OnTypeNames: [][]byte{[]byte("Address")},
+ },
+ {
+ Name: []byte("zip"),
+ Value: &resolve.String{
+ Path: []string{"zip"},
+ },
+ OnTypeNames: [][]byte{[]byte("Address")},
+ },
+ {
+ Name: []byte("id"),
+ Value: &resolve.Scalar{
+ Path: []string{"id"},
},
OnTypeNames: [][]byte{[]byte("Address")},
},
@@ -3313,17 +3422,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
},
SetTemplateOutputToNullOnVariableNull: true,
},
- }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")),
- resolve.SingleWithPath(&resolve.SingleFetch{
- FetchDependencies: resolve.FetchDependencies{
- FetchID: 3,
- DependsOnFetchIDs: []int{0, 2},
- },
- DataSourceIdentifier: []byte("graphql_datasource.Source"),
- FetchConfiguration: resolve.FetchConfiguration{
- Input: `{"method":"POST","url":"http://account.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename fullAddress}}}","variables":{"representations":[$$0$$]}}}`,
- DataSource: &Source{},
- PostProcessing: SingleEntityPostProcessingConfiguration,
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
Coordinate: resolve.GraphCoordinate{
@@ -3380,59 +3479,6 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
},
},
},
- RequiresEntityFetch: true,
- Variables: []resolve.Variable{
- &resolve.ResolvableObjectVariable{
- Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{
- Nullable: true,
- Fields: []*resolve.Field{
- {
- Name: []byte("__typename"),
- Value: &resolve.String{
- Path: []string{"__typename"},
- },
- OnTypeNames: [][]byte{[]byte("Address")},
- },
- {
- Name: []byte("line1"),
- Value: &resolve.String{
- Path: []string{"line1"},
- },
- OnTypeNames: [][]byte{[]byte("Address")},
- },
- {
- Name: []byte("line2"),
- Value: &resolve.String{
- Path: []string{"line2"},
- },
- OnTypeNames: [][]byte{[]byte("Address")},
- },
- {
- Name: []byte("line3"),
- Value: &resolve.String{
- Path: []string{"line3"},
- },
- OnTypeNames: [][]byte{[]byte("Address")},
- },
- {
- Name: []byte("zip"),
- Value: &resolve.String{
- Path: []string{"zip"},
- },
- OnTypeNames: [][]byte{[]byte("Address")},
- },
- {
- Name: []byte("id"),
- Value: &resolve.Scalar{
- Path: []string{"id"},
- },
- OnTypeNames: [][]byte{[]byte("Address")},
- },
- },
- }),
- },
- },
- SetTemplateOutputToNullOnVariableNull: true,
},
}, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")),
),
@@ -3521,6 +3567,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
},
WithDefaultPostProcessor(),
WithFieldDependencies(),
+ WithFetchReasons(),
)
})
@@ -16067,7 +16114,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
SelectionSet: "id",
Conditions: []plan.KeyCondition{
{
- Coordinates: []plan.KeyConditionCoordinate{
+ Coordinates: []plan.FieldCoordinate{
{
TypeName: "User",
FieldName: "hostedImageWithProvides",
@@ -17008,7 +17055,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) {
SelectionSet: "id",
Conditions: []plan.KeyCondition{
{
- Coordinates: []plan.KeyConditionCoordinate{
+ Coordinates: []plan.FieldCoordinate{
{
TypeName: "Host",
FieldName: "image",
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasourcetesting/datasourcetesting.go b/app/v2/pkg/engine/datasourcetesting/datasourcetesting.go
index 1b1a622..17da5ce 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasourcetesting/datasourcetesting.go
+++ b/app/v2/pkg/engine/datasourcetesting/datasourcetesting.go
@@ -33,6 +33,7 @@ type testOptions struct {
withFieldInfo bool
withPrintPlan bool
withFieldDependencies bool
+ withFetchReasons bool
}
func WithPostProcessors(postProcessors ...*postprocess.Processor) func(*testOptions) {
@@ -74,6 +75,12 @@ func WithFieldDependencies() func(*testOptions) {
}
}
+func WithFetchReasons() func(*testOptions) {
+ return func(o *testOptions) {
+ o.withFetchReasons = true
+ }
+}
+
func RunWithPermutations(t *testing.T, definition, operation, operationName string, expectedPlan plan.Plan, config plan.Configuration, options ...func(*testOptions)) {
t.Helper()
@@ -177,6 +184,9 @@ func RunTestWithVariables(definition, operation, operationName, variables string
if opts.withPrintPlan {
planOpts = append(planOpts, plan.IncludeQueryPlanInResponse())
}
+ if opts.withFetchReasons {
+ planOpts = append(planOpts, plan.BuildFetchReasons())
+ }
actualPlan := p.Plan(&op, &def, operationName, &report, planOpts...)
if report.HasErrors() {
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/federation_metadata.go b/app/v2/pkg/engine/plan/federation_metadata.go
index b4479a4..994ea95 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/federation_metadata.go
+++ b/app/v2/pkg/engine/plan/federation_metadata.go
@@ -82,11 +82,11 @@ type FederationFieldConfiguration struct {
}
type KeyCondition struct {
- Coordinates []KeyConditionCoordinate `json:"coordinates"`
- FieldPath []string `json:"field_path"`
+ Coordinates []FieldCoordinate `json:"coordinates"`
+ FieldPath []string `json:"field_path"`
}
-type KeyConditionCoordinate struct {
+type FieldCoordinate struct {
TypeName string `json:"type_name"`
FieldName string `json:"field_name"`
}
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/node_selection_visitor.go b/app/v2/pkg/engine/plan/node_selection_visitor.go
index 6a1259a..2273710 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/node_selection_visitor.go
+++ b/app/v2/pkg/engine/plan/node_selection_visitor.go
@@ -45,7 +45,7 @@ type nodeSelectionVisitor struct {
hasNewFields bool // hasNewFields is used to determine if we need to run the planner again. It will be true in case required fields were added
hasUnresolvedFields bool // hasUnresolvedFields is used to determine if we need to run the planner again. We should set it to true in case we have unresolved fields
- fieldPathCoordinates []KeyConditionCoordinate // currentFieldPathCoordinates is a stack of field path coordinates // TODO: remove me
+ fieldPathCoordinates []FieldCoordinate // currentFieldPathCoordinates is a stack of field path coordinates // TODO: remove me
rewrittenFieldRefs []int
}
@@ -128,7 +128,7 @@ func (c *nodeSelectionVisitor) EnterDocument(operation, definition *ast.Document
}
if c.fieldPathCoordinates == nil {
- c.fieldPathCoordinates = make([]KeyConditionCoordinate, 0, 8)
+ c.fieldPathCoordinates = make([]FieldCoordinate, 0, 8)
} else {
c.fieldPathCoordinates = c.fieldPathCoordinates[:0]
}
@@ -219,7 +219,7 @@ func (c *nodeSelectionVisitor) EnterField(fieldRef int) {
c.rewriteSelectionSetHavingAbstractFragments(fieldRef, ds)
}
- c.fieldPathCoordinates = append(c.fieldPathCoordinates, KeyConditionCoordinate{
+ c.fieldPathCoordinates = append(c.fieldPathCoordinates, FieldCoordinate{
FieldName: fieldName,
TypeName: typeName,
})
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/planner.go b/app/v2/pkg/engine/plan/planner.go
index 25d6e89..68dc0e6 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/planner.go
+++ b/app/v2/pkg/engine/plan/planner.go
@@ -85,6 +85,7 @@ func (p *Planner) SetDebugConfig(config DebugConfiguration) {
type _opts struct {
includeQueryPlanInResponse bool
+ buildFetchReasons bool
}
type Opts func(*_opts)
@@ -95,6 +96,12 @@ func IncludeQueryPlanInResponse() Opts {
}
}
+func BuildFetchReasons() Opts {
+ return func(o *_opts) {
+ o.buildFetchReasons = true
+ }
+}
+
func (p *Planner) Plan(operation, definition *ast.Document, operationName string, report *operationreport.Report, options ...Opts) (plan Plan) {
var opts _opts
@@ -103,6 +110,7 @@ func (p *Planner) Plan(operation, definition *ast.Document, operationName string
}
p.planningVisitor.includeQueryPlans = opts.includeQueryPlanInResponse
+ p.planningVisitor.buildFetchReasons = opts.buildFetchReasons
p.selectOperation(operation, operationName, report)
if report.HasErrors() {
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/visitor.go b/app/v2/pkg/engine/plan/visitor.go
index 382be28..9f57521 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/plan/visitor.go
+++ b/app/v2/pkg/engine/plan/visitor.go
@@ -53,6 +53,7 @@ type Visitor struct {
skipIncludeOnFragments map[int]skipIncludeInfo
disableResolveFieldPositions bool
includeQueryPlans bool
+ buildFetchReasons bool
indirectInterfaceFields map[int]indirectInterfaceField
pathCache map[astvisitor.VisitorKind]map[int]string
@@ -205,7 +206,7 @@ func (v *Visitor) AllowVisitor(kind astvisitor.VisitorKind, ref int, visitor any
}
}
- if !v.Config.DisableIncludeFieldDependencies && kind == astvisitor.LeaveField {
+ if v.shouldTrackFetchMetadata() && kind == astvisitor.LeaveField {
// we don't need to do this twice, so we only do it on leave
// store which fields are planned on which planners
@@ -340,7 +341,7 @@ func (v *Visitor) LeaveSelectionSet(ref int) {
func (v *Visitor) EnterField(ref int) {
v.debugOnEnterNode(ast.NodeKindField, ref)
- if !v.Config.DisableIncludeFieldDependencies {
+ if v.shouldTrackFetchMetadata() {
v.fieldEnclosingTypeNames[ref] = strings.Clone(v.Walker.EnclosingTypeDefinition.NameString(v.Definition))
}
// check if we have to skip the field in the response
@@ -1325,24 +1326,33 @@ func (v *Visitor) configureFetch(internal *objectFetchConfiguration, external re
DataSourceIdentifier: []byte(dataSourceType),
}
+ if !v.Config.DisableIncludeInfo || !v.Config.DisableIncludeFieldDependencies || v.buildFetchReasons {
+ singleFetch.Info = &resolve.FetchInfo{}
+ }
+
if !v.Config.DisableIncludeInfo {
- singleFetch.Info = &resolve.FetchInfo{
- DataSourceID: internal.sourceID,
- DataSourceName: internal.sourceName,
- RootFields: internal.rootFields,
- OperationType: internal.operationType,
- QueryPlan: external.QueryPlan,
- }
+ singleFetch.Info.DataSourceID = internal.sourceID
+ singleFetch.Info.DataSourceName = internal.sourceName
+ singleFetch.Info.RootFields = internal.rootFields
+ singleFetch.Info.OperationType = internal.operationType
+ singleFetch.Info.QueryPlan = external.QueryPlan
}
if !v.Config.DisableIncludeFieldDependencies {
- singleFetch.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID)
- singleFetch.FieldFetchReasons = v.buildFetchReasons(internal.fetchID)
+ singleFetch.Info.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID)
+ }
+
+ if v.buildFetchReasons {
+ singleFetch.Info.FetchReasons, singleFetch.Info.PropagatedFetchReasons = v.buildFetchReasonsInfo(internal.fetchID)
}
return singleFetch
}
+func (v *Visitor) shouldTrackFetchMetadata() bool {
+ return !v.Config.DisableIncludeFieldDependencies || v.buildFetchReasons
+}
+
func (v *Visitor) resolveFetchDependencies(fetchID int) []resolve.FetchDependency {
fields, ok := v.plannerFields[fetchID]
if !ok {
@@ -1398,10 +1408,10 @@ func (v *Visitor) resolveFetchDependencies(fetchID int) []resolve.FetchDependenc
return dependencies
}
-func (v *Visitor) buildFetchReasons(fetchID int) []resolve.FetchReason {
+func (v *Visitor) buildFetchReasonsInfo(fetchID int) ([]resolve.FetchReason, []resolve.FetchReason) {
fields, ok := v.plannerFields[fetchID]
if !ok {
- return nil
+ return nil, nil
}
dsConfig := v.planners[fetchID].DataSourceConfiguration()
@@ -1418,9 +1428,6 @@ func (v *Visitor) buildFetchReasons(fetchID int) []resolve.FetchReason {
continue
}
typeName := v.fieldEnclosingTypeNames[fieldRef]
- if !dsConfig.RequiresFetchReason(typeName, fieldName) {
- continue
- }
byUser := !v.skipField(fieldRef)
dependants, ok := v.fieldRefDependants[fieldRef]
@@ -1495,5 +1502,13 @@ func (v *Visitor) buildFetchReasons(fetchID int) []resolve.FetchReason {
cmp.Compare(a.FieldName, b.FieldName),
)
})
- return reasons
+
+ propagated := make([]resolve.FetchReason, 0, len(reasons))
+ for i := range reasons {
+ if dsConfig.RequiresFetchReason(reasons[i].TypeName, reasons[i].FieldName) {
+ propagated = append(propagated, reasons[i])
+ }
+ }
+
+ return reasons, propagated
}
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go b/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
index 3d6a00b..f5d0b2a 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
+++ b/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
@@ -75,10 +75,8 @@ func (d *createConcreteSingleFetchTypes) createEntityBatchFetch(fetch *resolve.S
}
return &resolve.BatchEntityFetch{
- FetchDependencies: fetch.FetchDependencies,
- CoordinateDependencies: fetch.CoordinateDependencies,
- FieldFetchReasons: fetch.FieldFetchReasons,
- Info: fetch.Info,
+ FetchDependencies: fetch.FetchDependencies,
+ Info: fetch.Info,
Input: resolve.BatchInput{
Header: resolve.InputTemplate{
Segments: fetch.InputTemplate.Segments[:representationsVariableIndex],
@@ -122,10 +120,8 @@ func (d *createConcreteSingleFetchTypes) createEntityFetch(fetch *resolve.Single
}
return &resolve.EntityFetch{
- FetchDependencies: fetch.FetchDependencies,
- CoordinateDependencies: fetch.CoordinateDependencies,
- FieldFetchReasons: fetch.FieldFetchReasons,
- Info: fetch.Info,
+ FetchDependencies: fetch.FetchDependencies,
+ Info: fetch.Info,
Input: resolve.EntityInput{
Header: resolve.InputTemplate{
Segments: fetch.InputTemplate.Segments[:representationsVariableIndex],
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go b/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go
index 9a0121b..d3da371 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go
+++ b/app/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go
@@ -99,6 +99,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) {
},
FetchConfiguration: resolve.FetchConfiguration{
Input: "a",
+ },
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
DependsOn: []resolve.FetchDependencyOrigin{
@@ -123,6 +125,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) {
},
FetchConfiguration: resolve.FetchConfiguration{
Input: "a",
+ },
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
DependsOn: []resolve.FetchDependencyOrigin{
@@ -147,6 +151,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) {
},
FetchConfiguration: resolve.FetchConfiguration{
Input: "b",
+ },
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
DependsOn: []resolve.FetchDependencyOrigin{
@@ -174,6 +180,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) {
},
FetchConfiguration: resolve.FetchConfiguration{
Input: "b",
+ },
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
DependsOn: []resolve.FetchDependencyOrigin{
@@ -219,6 +227,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) {
},
FetchConfiguration: resolve.FetchConfiguration{
Input: "a",
+ },
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
DependsOn: []resolve.FetchDependencyOrigin{
@@ -243,6 +253,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) {
},
FetchConfiguration: resolve.FetchConfiguration{
Input: "b",
+ },
+ Info: &resolve.FetchInfo{
CoordinateDependencies: []resolve.FetchDependency{
{
DependsOn: []resolve.FetchDependencyOrigin{
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/fetch.go b/app/v2/pkg/engine/resolve/fetch.go
index 137670b..87ade48 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/fetch.go
+++ b/app/v2/pkg/engine/resolve/fetch.go
@@ -20,6 +20,7 @@ const (
type Fetch interface {
FetchKind() FetchKind
Dependencies() *FetchDependencies
+ FetchInfo() *FetchInfo
DataSourceInfo() DataSourceInfo
DependenciesCoordinates() []FetchDependency
FetchReasons() []FetchReason
@@ -101,15 +102,28 @@ func (s *SingleFetch) Dependencies() *FetchDependencies {
return &s.FetchDependencies
}
+func (s *SingleFetch) FetchInfo() *FetchInfo {
+ return s.Info
+}
+
func (s *SingleFetch) DependenciesCoordinates() []FetchDependency {
- return s.CoordinateDependencies
+ if s.Info == nil {
+ return nil
+ }
+ return s.Info.CoordinateDependencies
}
func (s *SingleFetch) FetchReasons() []FetchReason {
- return s.FieldFetchReasons
+ if s.Info == nil {
+ return nil
+ }
+ return s.Info.PropagatedFetchReasons
}
func (s *SingleFetch) DataSourceInfo() DataSourceInfo {
+ if s.Info == nil {
+ return DataSourceInfo{}
+ }
return DataSourceInfo{
ID: s.Info.DataSourceID,
Name: s.Info.DataSourceName,
@@ -170,29 +184,40 @@ func (*SingleFetch) FetchKind() FetchKind {
type BatchEntityFetch struct {
FetchDependencies
- Input BatchInput
- DataSource DataSource
- PostProcessing PostProcessingConfiguration
- DataSourceIdentifier []byte
- Trace *DataSourceLoadTrace
- Info *FetchInfo
- CoordinateDependencies []FetchDependency
- FieldFetchReasons []FetchReason
+ Input BatchInput
+ DataSource DataSource
+ PostProcessing PostProcessingConfiguration
+ DataSourceIdentifier []byte
+ Trace *DataSourceLoadTrace
+ Info *FetchInfo
}
func (b *BatchEntityFetch) Dependencies() *FetchDependencies {
return &b.FetchDependencies
}
+func (b *BatchEntityFetch) FetchInfo() *FetchInfo {
+ return b.Info
+}
+
func (b *BatchEntityFetch) DependenciesCoordinates() []FetchDependency {
- return b.CoordinateDependencies
+ if b.Info == nil {
+ return nil
+ }
+ return b.Info.CoordinateDependencies
}
func (b *BatchEntityFetch) FetchReasons() []FetchReason {
- return b.FieldFetchReasons
+ if b.Info == nil {
+ return nil
+ }
+ return b.Info.PropagatedFetchReasons
}
func (b *BatchEntityFetch) DataSourceInfo() DataSourceInfo {
+ if b.Info == nil {
+ return DataSourceInfo{}
+ }
return DataSourceInfo{
ID: b.Info.DataSourceID,
Name: b.Info.DataSourceName,
@@ -223,29 +248,40 @@ func (*BatchEntityFetch) FetchKind() FetchKind {
type EntityFetch struct {
FetchDependencies
- Input EntityInput
- DataSource DataSource
- PostProcessing PostProcessingConfiguration
- DataSourceIdentifier []byte
- Trace *DataSourceLoadTrace
- Info *FetchInfo
- CoordinateDependencies []FetchDependency
- FieldFetchReasons []FetchReason
+ Input EntityInput
+ DataSource DataSource
+ PostProcessing PostProcessingConfiguration
+ DataSourceIdentifier []byte
+ Trace *DataSourceLoadTrace
+ Info *FetchInfo
}
func (e *EntityFetch) Dependencies() *FetchDependencies {
return &e.FetchDependencies
}
+func (e *EntityFetch) FetchInfo() *FetchInfo {
+ return e.Info
+}
+
func (e *EntityFetch) DependenciesCoordinates() []FetchDependency {
- return e.CoordinateDependencies
+ if e.Info == nil {
+ return nil
+ }
+ return e.Info.CoordinateDependencies
}
func (e *EntityFetch) FetchReasons() []FetchReason {
- return e.FieldFetchReasons
+ if e.Info == nil {
+ return nil
+ }
+ return e.Info.PropagatedFetchReasons
}
func (e *EntityFetch) DataSourceInfo() DataSourceInfo {
+ if e.Info == nil {
+ return DataSourceInfo{}
+ }
return DataSourceInfo{
ID: e.Info.DataSourceID,
Name: e.Info.DataSourceName,
@@ -276,12 +312,25 @@ func (p *ParallelListItemFetch) Dependencies() *FetchDependencies {
return &p.Fetch.FetchDependencies
}
+func (p *ParallelListItemFetch) FetchInfo() *FetchInfo {
+ if p.Fetch == nil {
+ return nil
+ }
+ return p.Fetch.Info
+}
+
func (p *ParallelListItemFetch) DependenciesCoordinates() []FetchDependency {
- return p.Fetch.CoordinateDependencies
+ if p.Fetch == nil || p.Fetch.Info == nil {
+ return nil
+ }
+ return p.Fetch.Info.CoordinateDependencies
}
func (p *ParallelListItemFetch) FetchReasons() []FetchReason {
- return p.Fetch.FieldFetchReasons
+ if p.Fetch == nil || p.Fetch.Info == nil {
+ return nil
+ }
+ return p.Fetch.Info.PropagatedFetchReasons
}
func (*ParallelListItemFetch) FetchKind() FetchKind {
@@ -341,17 +390,6 @@ type FetchConfiguration struct {
QueryPlan *QueryPlan
- // CoordinateDependencies contain a list of GraphCoordinates (typeName+fieldName)
- // and which fields from other fetches they depend on.
- // This information is useful to understand why a fetch depends on other fetches,
- // and how multiple dependencies lead to a chain of fetches
- CoordinateDependencies []FetchDependency
-
- // FieldFetchReasons contains provenance for fields that require fetch reason to be propagated
- // to their subgraph. It is optional propagation via request extensions;
- // it does not affect execution.
- FieldFetchReasons []FetchReason
-
// OperationName is non-empty when the operation name is propagated to the upstream subgraph fetch.
OperationName string
}
@@ -367,11 +405,6 @@ func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool {
}
// Note: we do not compare datasources, as they will always be a different instance.
- // Note: we do not compare CoordinateDependencies, as they contain more detailed
- // dependencies information that is already present in the FetchDependencies on the fetch itself.
- // Note: we do not compare FieldFetchReasons, as it is derived data for an extension
- // and does not affect fetch execution semantics.
-
if fc.RequiresParallelListItemFetch != other.RequiresParallelListItemFetch {
return false
}
@@ -430,11 +463,14 @@ type FetchReason struct {
}
type FetchInfo struct {
- DataSourceID string
- DataSourceName string
- RootFields []GraphCoordinate
- OperationType ast.OperationType
- QueryPlan *QueryPlan
+ DataSourceID string
+ DataSourceName string
+ RootFields []GraphCoordinate
+ OperationType ast.OperationType
+ QueryPlan *QueryPlan
+ CoordinateDependencies []FetchDependency
+ FetchReasons []FetchReason
+ PropagatedFetchReasons []FetchReason
}
type GraphCoordinate struct {
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/fetchtree.go b/app/v2/pkg/engine/resolve/fetchtree.go
index ad1eb2f..f4fd987 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/fetchtree.go
+++ b/app/v2/pkg/engine/resolve/fetchtree.go
@@ -216,7 +216,7 @@ func (n *FetchTreeNode) queryPlan() *FetchTreeQueryPlanNode {
SubgraphName: f.Info.DataSourceName,
SubgraphID: f.Info.DataSourceID,
Path: n.Item.ResponsePath,
- Dependencies: f.FetchConfiguration.CoordinateDependencies,
+ Dependencies: f.Info.CoordinateDependencies,
}
if f.Info.QueryPlan != nil {
@@ -231,7 +231,7 @@ func (n *FetchTreeNode) queryPlan() *FetchTreeQueryPlanNode {
SubgraphName: f.Info.DataSourceName,
SubgraphID: f.Info.DataSourceID,
Path: n.Item.ResponsePath,
- Dependencies: f.CoordinateDependencies,
+ Dependencies: f.Info.CoordinateDependencies,
}
if f.Info.QueryPlan != nil {
@@ -246,7 +246,7 @@ func (n *FetchTreeNode) queryPlan() *FetchTreeQueryPlanNode {
SubgraphName: f.Info.DataSourceName,
SubgraphID: f.Info.DataSourceID,
Path: n.Item.ResponsePath,
- Dependencies: f.CoordinateDependencies,
+ Dependencies: f.Info.CoordinateDependencies,
}
if f.Info.QueryPlan != nil {
diff --git a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/loader.go b/app/v2/pkg/engine/resolve/loader.go
index 685383c..9a3690d 100644
--- a/tmp/agent-patch-flux-pr-1293.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/loader.go
+++ b/app/v2/pkg/engine/resolve/loader.go
@@ -1599,7 +1599,10 @@ func (l *Loader) executeSourceLoad(ctx context.Context, fetchItem *FetchItem, so
}
}
if l.propagateFetchReasons && !IsIntrospectionDataSource(res.ds.ID) {
- fetchReasons := fetchItem.Fetch.FetchReasons()
+ var fetchReasons []FetchReason
+ if info := fetchItem.Fetch.FetchInfo(); info != nil {
+ fetchReasons = info.PropagatedFetchReasons
+ }
if len(fetchReasons) > 0 {
var encoded []byte
encoded, res.err = json.Marshal(fetchReasons)