STET

agent_patch

runs/2026-02-28__13-38-10__gpt-5-3-codex/flux-pr-1297/flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/agent-logs/agent.patch

25410 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go b/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go
index c5a7f3f..ae56a98 100644
--- a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go
+++ b/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go
@@ -130,6 +130,8 @@ type FetchConfiguration struct {
 type FederationConfiguration struct {
 	Enabled    bool
 	ServiceSDL string
+	// ValidateRequiresDependencies enables optional validation of nullable @requires dependencies.
+	ValidateRequiresDependencies bool
 }
 
 type SchemaConfiguration struct {
diff --git a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go b/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
index 3acdd07..5a2a4cd 100644
--- a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
+++ b/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
@@ -338,6 +338,9 @@ func (p *Planner[T]) ConfigureFetch() resolve.FetchConfiguration {
 	postProcessing := DefaultPostProcessingConfiguration
 	requiresEntityFetch := p.requiresEntityFetch()
 	requiresEntityBatchFetch := p.requiresEntityBatchFetch()
+	validateRequiresDependencies := p.config.IsFederationEnabled() &&
+		p.config.FederationConfiguration().ValidateRequiresDependencies &&
+		(requiresEntityFetch || requiresEntityBatchFetch)
 
 	switch {
 	case requiresEntityFetch:
@@ -384,6 +387,7 @@ func (p *Planner[T]) ConfigureFetch() resolve.FetchConfiguration {
 		RequiresEntityBatchFetch:              requiresEntityBatchFetch,
 		PostProcessing:                        postProcessing,
 		SetTemplateOutputToNullOnVariableNull: requiresEntityFetch || requiresEntityBatchFetch,
+		ValidateRequiresDependencies:          validateRequiresDependencies,
 		QueryPlan:                             p.queryPlan,
 		OperationName:                         p.propagatedOperationName,
 	}
diff --git a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go b/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
index f5d0b2a..81aef08 100644
--- a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
+++ b/app/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
@@ -1,6 +1,8 @@
 package postprocess
 
 import (
+	"strings"
+
 	"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
 )
 
@@ -73,6 +75,7 @@ func (d *createConcreteSingleFetchTypes) createEntityBatchFetch(fetch *resolve.S
 			break
 		}
 	}
+	nullableRepresentationVariablePaths := d.nullableRepresentationVariablePaths(fetch, representationsVariableIndex)
 
 	return &resolve.BatchEntityFetch{
 		FetchDependencies: fetch.FetchDependencies,
@@ -88,9 +91,11 @@ func (d *createConcreteSingleFetchTypes) createEntityBatchFetch(fetch *resolve.S
 					SetTemplateOutputToNullOnVariableNull: fetch.InputTemplate.SetTemplateOutputToNullOnVariableNull,
 				},
 			},
-			SkipNullItems:        true,
-			SkipEmptyObjectItems: true,
-			SkipErrItems:         true,
+			SkipNullItems:                       true,
+			SkipEmptyObjectItems:                true,
+			SkipErrItems:                        true,
+			ValidateRequiresDependencies:        fetch.ValidateRequiresDependencies,
+			NullableRepresentationVariablePaths: nullableRepresentationVariablePaths,
 			Separator: resolve.InputTemplate{
 				Segments: []resolve.TemplateSegment{
 					{
@@ -118,6 +123,7 @@ func (d *createConcreteSingleFetchTypes) createEntityFetch(fetch *resolve.Single
 			break
 		}
 	}
+	nullableRepresentationVariablePaths := d.nullableRepresentationVariablePaths(fetch, representationsVariableIndex)
 
 	return &resolve.EntityFetch{
 		FetchDependencies: fetch.FetchDependencies,
@@ -131,7 +137,9 @@ func (d *createConcreteSingleFetchTypes) createEntityFetch(fetch *resolve.Single
 				Segments:                              []resolve.TemplateSegment{fetch.InputTemplate.Segments[representationsVariableIndex]},
 				SetTemplateOutputToNullOnVariableNull: fetch.InputTemplate.SetTemplateOutputToNullOnVariableNull,
 			},
-			SkipErrItem: true,
+			SkipErrItem:                         true,
+			ValidateRequiresDependencies:        fetch.ValidateRequiresDependencies,
+			NullableRepresentationVariablePaths: nullableRepresentationVariablePaths,
 			Footer: resolve.InputTemplate{
 				Segments:                              fetch.InputTemplate.Segments[representationsVariableIndex+1:],
 				SetTemplateOutputToNullOnVariableNull: fetch.InputTemplate.SetTemplateOutputToNullOnVariableNull,
@@ -141,3 +149,64 @@ func (d *createConcreteSingleFetchTypes) createEntityFetch(fetch *resolve.Single
 		PostProcessing: fetch.PostProcessing,
 	}
 }
+
+func (d *createConcreteSingleFetchTypes) nullableRepresentationVariablePaths(fetch *resolve.SingleFetch, representationsVariableIndex int) []string {
+	if representationsVariableIndex < 0 || representationsVariableIndex >= len(fetch.InputTemplate.Segments) {
+		return nil
+	}
+
+	renderer, ok := fetch.InputTemplate.Segments[representationsVariableIndex].Renderer.(*resolve.GraphQLVariableResolveRenderer)
+	if !ok {
+		return nil
+	}
+	root, ok := renderer.Node.(*resolve.Object)
+	if !ok || root == nil {
+		return nil
+	}
+
+	paths := make([]string, 0, len(root.Fields))
+	collectNullableRepresentationPaths(root, nil, &paths)
+
+	return paths
+}
+
+func collectNullableRepresentationPaths(node resolve.Node, currentPath []string, out *[]string) {
+	switch n := node.(type) {
+	case *resolve.Object:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+		for _, field := range n.Fields {
+			collectNullableRepresentationPaths(field.Value, append(currentPath, string(field.Name)), out)
+		}
+	case *resolve.Array:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+		collectNullableRepresentationPaths(n.Item, currentPath, out)
+	case *resolve.String:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+	case *resolve.Boolean:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+	case *resolve.Integer:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+	case *resolve.Float:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+	case *resolve.BigInt:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+	case *resolve.Scalar:
+		if n.Nullable && len(currentPath) > 0 {
+			*out = append(*out, strings.Join(currentPath, "."))
+		}
+	}
+}
diff --git a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/resolve/fetch.go b/app/v2/pkg/engine/resolve/fetch.go
index 26e20c7..9f1c361 100644
--- a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/resolve/fetch.go
+++ b/app/v2/pkg/engine/resolve/fetch.go
@@ -187,8 +187,12 @@ type BatchInput struct {
 	// In this case, the error will be swallowed
 	// E.g. if a field is not nullable and the value is null, the item will be skipped
 	SkipErrItems bool
-	Separator    InputTemplate
-	Footer       InputTemplate
+	// ValidateRequiresDependencies enables filtering entities that have tainted nullable dependencies.
+	ValidateRequiresDependencies bool
+	// NullableRepresentationVariablePaths are the representation field paths that are nullable.
+	NullableRepresentationVariablePaths []string
+	Separator                           InputTemplate
+	Footer                              InputTemplate
 }
 
 func (*BatchEntityFetch) FetchKind() FetchKind {
@@ -220,7 +224,11 @@ type EntityInput struct {
 	Header      InputTemplate
 	Item        InputTemplate
 	SkipErrItem bool
-	Footer      InputTemplate
+	// ValidateRequiresDependencies enables filtering entities that have tainted nullable dependencies.
+	ValidateRequiresDependencies bool
+	// NullableRepresentationVariablePaths are the representation field paths that are nullable.
+	NullableRepresentationVariablePaths []string
+	Footer                              InputTemplate
 }
 
 func (*EntityFetch) FetchKind() FetchKind {
@@ -294,6 +302,8 @@ type FetchConfiguration struct {
 	// This is the case, e.g. when using batching and one sibling is null, resulting in a null value for one batch item
 	// Returning null in this case tells the batch implementation to skip this item
 	SetTemplateOutputToNullOnVariableNull bool
+	// ValidateRequiresDependencies enables optional validation of nullable @requires dependencies.
+	ValidateRequiresDependencies bool
 
 	QueryPlan *QueryPlan
 
@@ -328,6 +338,9 @@ func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool {
 	if fc.SetTemplateOutputToNullOnVariableNull != other.SetTemplateOutputToNullOnVariableNull {
 		return false
 	}
+	if fc.ValidateRequiresDependencies != other.ValidateRequiresDependencies {
+		return false
+	}
 
 	return true
 }
diff --git a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/resolve/loader.go b/app/v2/pkg/engine/resolve/loader.go
index 1e9f993..4fd7212 100644
--- a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/resolve/loader.go
+++ b/app/v2/pkg/engine/resolve/loader.go
@@ -159,6 +159,8 @@ type Loader struct {
 	resolvable *Resolvable
 	ctx        *Context
 	info       *GraphQLResponseInfo
+	// taintedRepresentationVariablePaths stores field paths on items that were returned as null with a downstream error.
+	taintedRepresentationVariablePaths map[*astjson.Value]map[string]struct{}
 
 	propagateSubgraphErrors           bool
 	propagateSubgraphStatusCodes      bool
@@ -189,6 +191,7 @@ func (l *Loader) LoadGraphQLResponseData(ctx *Context, response *GraphQLResponse
 	l.resolvable = resolvable
 	l.ctx = ctx
 	l.info = response.Info
+	l.taintedRepresentationVariablePaths = make(map[*astjson.Value]map[string]struct{})
 	return l.resolveFetchNode(response.Fetches)
 }
 
@@ -557,6 +560,7 @@ func (l *Loader) mergeResult(fetchItem *FetchItem, res *result, items []*astjson
 	}
 
 	hasErrors := false
+	parsedResponse := value
 
 	// We check if the subgraph response has errors
 	if res.postProcessing.SelectResponseErrorsPath != nil {
@@ -572,6 +576,7 @@ func (l *Loader) mergeResult(fetchItem *FetchItem, res *result, items []*astjson
 				if err != nil {
 					return errors.WithStack(err)
 				}
+				l.trackTaintedRepresentationVariablePaths(parsedResponse, res, errorObjects, items)
 			}
 		}
 	}
@@ -991,6 +996,7 @@ const (
 	invalidGraphQLResponse      = "invalid JSON"
 	invalidGraphQLResponseShape = "no data or errors in response"
 	invalidBatchItemCount       = "returned entities count does not match the count of representation variables in the entities request. Expected %d, got %d"
+	missingRequiredDependencies = "required federation dependencies are missing from the entity representation"
 )
 
 func (l *Loader) renderAtPathErrorPart(path string) string {
@@ -1038,6 +1044,14 @@ func (l *Loader) renderErrorsFailedToFetch(fetchItem *FetchItem, res *result, re
 	return nil
 }
 
+func (l *Loader) renderErrorsMissingRequiresDependencies(fetchItem *FetchItem, res *result) {
+	errorObject, err := astjson.ParseWithoutCache(l.renderSubgraphBaseError(res.ds, fetchItem.ResponsePath, missingRequiredDependencies))
+	if err != nil {
+		return
+	}
+	astjson.AppendToArray(l.resolvable.errors, errorObject)
+}
+
 func (l *Loader) renderErrorsStatusFallback(fetchItem *FetchItem, res *result, statusCode int) error {
 	reason := fmt.Sprintf("%d", statusCode)
 	if statusText := http.StatusText(statusCode); statusText != "" {
@@ -1339,6 +1353,17 @@ func (l *Loader) loadEntityFetch(ctx context.Context, fetchItem *FetchItem, fetc
 			return nil
 		}
 	}
+	if fetch.Input.ValidateRequiresDependencies &&
+		len(items) == 1 &&
+		l.itemHasMissingNullableRequiresDependencies(items[0], fetch.Input.NullableRepresentationVariablePaths) {
+		// skip fetch if nullable @requires dependencies are tainted
+		res.fetchSkipped = true
+		if l.ctx.TracingOptions.Enable {
+			fetch.Trace.LoadSkipped = true
+		}
+		l.renderErrorsMissingRequiresDependencies(fetchItem, res)
+		return nil
+	}
 	_, _ = buf.item.WriteTo(buf.preparedInput)
 	err = fetch.Input.Footer.RenderAndCollectUndefinedVariables(l.ctx, nil, buf.preparedInput, &undefinedVariables)
 	if err != nil {
@@ -1425,6 +1450,12 @@ func (l *Loader) loadBatchEntityFetch(ctx context.Context, fetchItem *FetchItem,
 
 WithNextItem:
 	for i, item := range items {
+		if fetch.Input.ValidateRequiresDependencies &&
+			l.itemHasMissingNullableRequiresDependencies(item, fetch.Input.NullableRepresentationVariablePaths) {
+			res.batchStats[i] = append(res.batchStats[i], -1)
+			l.renderErrorsMissingRequiresDependencies(fetchItem, res)
+			continue
+		}
 		for j := range fetch.Input.Items {
 			buf.itemInput.Reset()
 			err = fetch.Input.Items[j].Render(l.ctx, item, buf.itemInput)
@@ -1508,6 +1539,143 @@ WithNextItem:
 	return nil
 }
 
+func (l *Loader) itemHasMissingNullableRequiresDependencies(item *astjson.Value, nullablePaths []string) bool {
+	if item == nil {
+		return false
+	}
+
+	taintedPaths, ok := l.taintedRepresentationVariablePaths[item]
+	if !ok || len(taintedPaths) == 0 || len(nullablePaths) == 0 {
+		return false
+	}
+
+	for _, nullablePath := range nullablePaths {
+		if _, ok := taintedPaths[nullablePath]; ok {
+			return true
+		}
+		for taintedPath := range taintedPaths {
+			if strings.HasPrefix(nullablePath, taintedPath+".") || strings.HasPrefix(taintedPath, nullablePath+".") {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (l *Loader) trackTaintedRepresentationVariablePaths(responseValue *astjson.Value, res *result, errorObjects, items []*astjson.Value) {
+	if responseValue == nil || len(errorObjects) == 0 || len(items) == 0 {
+		return
+	}
+	if !isEntityResponsePath(res.postProcessing.SelectResponseDataPath) {
+		return
+	}
+
+	for _, errorObject := range errorObjects {
+		pathValue := errorObject.Get("path")
+		entityIndex, fieldPath, ok := entityErrorPath(pathValue)
+		if !ok || len(fieldPath) == 0 {
+			continue
+		}
+
+		fullPath := make([]string, 0, len(fieldPath)+3)
+		fullPath = append(fullPath, "data", "_entities", strconv.Itoa(entityIndex))
+		fullPath = append(fullPath, fieldPath...)
+		if !astjson.ValueIsNull(responseValue.Get(fullPath...)) {
+			continue
+		}
+
+		if res.batchStats != nil {
+			for itemIndex, stats := range res.batchStats {
+				for _, batchIndex := range stats {
+					if batchIndex == entityIndex && itemIndex < len(items) {
+						l.markItemAsTainted(items[itemIndex], fieldPath)
+					}
+				}
+			}
+			continue
+		}
+
+		if entityIndex >= 0 && entityIndex < len(items) {
+			l.markItemAsTainted(items[entityIndex], fieldPath)
+		}
+	}
+}
+
+func (l *Loader) markItemAsTainted(item *astjson.Value, fieldPath []string) {
+	if item == nil {
+		return
+	}
+	pathKey := strings.Join(fieldPath, ".")
+	if _, ok := l.taintedRepresentationVariablePaths[item]; !ok {
+		l.taintedRepresentationVariablePaths[item] = make(map[string]struct{})
+	}
+	l.taintedRepresentationVariablePaths[item][pathKey] = struct{}{}
+}
+
+func isEntityResponsePath(path []string) bool {
+	for _, item := range path {
+		if item == "_entities" {
+			return true
+		}
+	}
+	return false
+}
+
+func entityErrorPath(pathValue *astjson.Value) (entityIndex int, fieldPath []string, ok bool) {
+	if pathValue == nil || pathValue.Type() != astjson.TypeArray {
+		return 0, nil, false
+	}
+
+	pathItems := pathValue.GetArray()
+	if len(pathItems) < 2 {
+		return 0, nil, false
+	}
+
+	entitiesIndex := -1
+	for i, item := range pathItems {
+		if item.Type() == astjson.TypeString && unsafebytes.BytesToString(item.GetStringBytes()) == "_entities" {
+			entitiesIndex = i
+			break
+		}
+	}
+	if entitiesIndex == -1 || entitiesIndex+1 >= len(pathItems) {
+		return 0, nil, false
+	}
+
+	indexValue := pathItems[entitiesIndex+1]
+	switch indexValue.Type() {
+	case astjson.TypeNumber:
+		index, err := strconv.Atoi(unsafebytes.BytesToString(indexValue.MarshalTo(nil)))
+		if err != nil {
+			return 0, nil, false
+		}
+		entityIndex = index
+	case astjson.TypeString:
+		index, err := strconv.Atoi(unsafebytes.BytesToString(indexValue.GetStringBytes()))
+		if err != nil {
+			return 0, nil, false
+		}
+		entityIndex = index
+	default:
+		return 0, nil, false
+	}
+
+	fieldPath = make([]string, 0, len(pathItems)-(entitiesIndex+2))
+	for i := entitiesIndex + 2; i < len(pathItems); i++ {
+		switch pathItems[i].Type() {
+		case astjson.TypeString:
+			fieldPath = append(fieldPath, unsafebytes.BytesToString(pathItems[i].GetStringBytes()))
+		case astjson.TypeNumber:
+			fieldPath = append(fieldPath, unsafebytes.BytesToString(pathItems[i].MarshalTo(nil)))
+		default:
+			return 0, nil, false
+		}
+	}
+
+	return entityIndex, fieldPath, true
+}
+
 func redactHeaders(rawJSON json.RawMessage) (json.RawMessage, error) {
 	var obj map[string]interface{}
 
diff --git a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/resolve/loader_test.go b/app/v2/pkg/engine/resolve/loader_test.go
index 10c6168..d943e07 100644
--- a/tmp/agent-patch-flux-pr-1297.1-of-1.2026-02-28__13-38-10__gpt-5-3-codex/app/v2/pkg/engine/resolve/loader_test.go
+++ b/app/v2/pkg/engine/resolve/loader_test.go
@@ -1,8 +1,10 @@
 package resolve
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
+	"io"
 	"net/http"
 	"testing"
 
@@ -298,6 +300,223 @@ func TestLoader_LoadGraphQLResponseData(t *testing.T) {
 	assert.Equal(t, expected, out)
 }
 
+func TestLoader_ValidateRequiresDependencies(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	usersService := mockedDS(t, ctrl,
+		`{"method":"POST","url":"http://users","body":{"query":"query{users{__typename id}}"}}`,
+		`{"users":[{"__typename":"User","id":"1"},{"__typename":"User","id":"2"}]}`)
+
+	namesService := NewMockDataSource(ctrl)
+	namesService.EXPECT().
+		Load(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&bytes.Buffer{})).
+		DoAndReturn(func(ctx context.Context, input []byte, w io.Writer) (err error) {
+			assert.Equal(t, `{"method":"POST","url":"http://names","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {id name}}}","variables":{"representations":[{"__typename":"User","id":"1"},{"__typename":"User","id":"2"}]}}}`, string(input))
+			pair := NewBufPair()
+			pair.Data.WriteString(`{"_entities":[{"__typename":"User","id":"1","name":"Alice"},{"__typename":"User","id":"2","name":null}]}`)
+			pair.Errors.WriteString(`{"message":"failed to resolve name","path":["_entities",1,"name"]}`)
+			return writeGraphqlResponse(pair, w, false)
+		})
+
+	profilesService := mockedDS(t, ctrl,
+		`{"method":"POST","url":"http://profiles","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {profile}}}","variables":{"representations":[{"__typename":"User","id":"1","name":"Alice"}]}}}`,
+		`{"_entities":[{"profile":"profile-1"}]}`)
+
+	response := &GraphQLResponse{
+		Fetches: Sequence(
+			Single(&SingleFetch{
+				InputTemplate: InputTemplate{
+					Segments: []TemplateSegment{
+						{
+							Data:        []byte(`{"method":"POST","url":"http://users","body":{"query":"query{users{__typename id}}"}}`),
+							SegmentType: StaticSegmentType,
+						},
+					},
+				},
+				FetchConfiguration: FetchConfiguration{
+					DataSource: usersService,
+					PostProcessing: PostProcessingConfiguration{
+						SelectResponseDataPath: []string{"data"},
+					},
+				},
+			}),
+			Single(&BatchEntityFetch{
+				Input: BatchInput{
+					Header: InputTemplate{
+						Segments: []TemplateSegment{
+							{
+								Data:        []byte(`{"method":"POST","url":"http://names","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {id name}}}","variables":{"representations":[`),
+								SegmentType: StaticSegmentType,
+							},
+						},
+					},
+					Items: []InputTemplate{
+						{
+							Segments: []TemplateSegment{
+								{
+									SegmentType:  VariableSegmentType,
+									VariableKind: ResolvableObjectVariableKind,
+									Renderer: NewGraphQLVariableResolveRenderer(&Object{
+										Fields: []*Field{
+											{
+												Name: []byte("__typename"),
+												Value: &String{
+													Path: []string{"__typename"},
+												},
+											},
+											{
+												Name: []byte("id"),
+												Value: &String{
+													Path: []string{"id"},
+												},
+											},
+										},
+									}),
+								},
+							},
+						},
+					},
+					Separator: InputTemplate{
+						Segments: []TemplateSegment{
+							{
+								Data:        []byte(`,`),
+								SegmentType: StaticSegmentType,
+							},
+						},
+					},
+					Footer: InputTemplate{
+						Segments: []TemplateSegment{
+							{
+								Data:        []byte(`]}}}`),
+								SegmentType: StaticSegmentType,
+							},
+						},
+					},
+				},
+				DataSource: namesService,
+				PostProcessing: PostProcessingConfiguration{
+					SelectResponseDataPath:   []string{"data", "_entities"},
+					SelectResponseErrorsPath: []string{"errors"},
+				},
+			}, ArrayPath("users")),
+			Single(&BatchEntityFetch{
+				Input: BatchInput{
+					Header: InputTemplate{
+						Segments: []TemplateSegment{
+							{
+								Data:        []byte(`{"method":"POST","url":"http://profiles","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {profile}}}","variables":{"representations":[`),
+								SegmentType: StaticSegmentType,
+							},
+						},
+					},
+					Items: []InputTemplate{
+						{
+							Segments: []TemplateSegment{
+								{
+									SegmentType:  VariableSegmentType,
+									VariableKind: ResolvableObjectVariableKind,
+									Renderer: NewGraphQLVariableResolveRenderer(&Object{
+										Fields: []*Field{
+											{
+												Name: []byte("__typename"),
+												Value: &String{
+													Path: []string{"__typename"},
+												},
+											},
+											{
+												Name: []byte("id"),
+												Value: &String{
+													Path: []string{"id"},
+												},
+											},
+											{
+												Name: []byte("name"),
+												Value: &String{
+													Path:     []string{"name"},
+													Nullable: true,
+												},
+											},
+										},
+									}),
+								},
+							},
+						},
+					},
+					ValidateRequiresDependencies:        true,
+					NullableRepresentationVariablePaths: []string{"name"},
+					Separator: InputTemplate{
+						Segments: []TemplateSegment{
+							{
+								Data:        []byte(`,`),
+								SegmentType: StaticSegmentType,
+							},
+						},
+					},
+					Footer: InputTemplate{
+						Segments: []TemplateSegment{
+							{
+								Data:        []byte(`]}}}`),
+								SegmentType: StaticSegmentType,
+							},
+						},
+					},
+				},
+				DataSource: profilesService,
+				PostProcessing: PostProcessingConfiguration{
+					SelectResponseDataPath: []string{"data", "_entities"},
+				},
+			}, ArrayPath("users")),
+		),
+		Data: &Object{
+			Fields: []*Field{
+				{
+					Name: []byte("users"),
+					Value: &Array{
+						Path: []string{"users"},
+						Item: &Object{
+							Fields: []*Field{
+								{
+									Name: []byte("id"),
+									Value: &String{
+										Path: []string{"id"},
+									},
+								},
+								{
+									Name: []byte("name"),
+									Value: &String{
+										Path:     []string{"name"},
+										Nullable: true,
+									},
+								},
+								{
+									Name: []byte("profile"),
+									Value: &String{
+										Path:     []string{"profile"},
+										Nullable: true,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	ctx := &Context{ctx: context.Background()}
+	resolvable := NewResolvable(ResolvableOptions{})
+	loader := &Loader{}
+	err := resolvable.Init(ctx, nil, ast.OperationTypeQuery)
+	assert.NoError(t, err)
+	err = loader.LoadGraphQLResponseData(ctx, response, resolvable)
+	assert.NoError(t, err)
+
+	out := fastjsonext.PrintGraphQLResponse(resolvable.data, resolvable.errors)
+	expected := `{"errors":[{"message":"Failed to fetch from Subgraph."},{"message":"Failed to fetch from Subgraph, Reason: required federation dependencies are missing from the entity representation."}],"data":{"users":[{"__typename":"User","id":"1","name":"Alice","profile":"profile-1"},{"__typename":"User","id":"2","name":null}]}}`
+	assert.Equal(t, expected, out)
+}
+
 func TestLoader_MergeErrorDifferingTypes(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()