From 1f91c8e8e9a1cf6c2547d851510f94084cf3d385 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 29 May 2026 17:56:28 +0300 Subject: [PATCH 1/6] fix: calculate costs for abstract fields without double counting This PR fixes how costs for abstract types are calculated. Especially for fragments used on abstract types. For example, for actual costs, instead of summing each type of the union, engine sums only costs for types actually seen in the returned results. For estimated costs, instead of summing all the implementing nodes of abstract types, we just pick the implementing type with the maximum cost and add that to the costs of fields selected on the abstract type itself. I had to recalculate and verify numbers in tests. Unfortunately, this fix is not final. There is unfinished work with how the cost of the field itself is selected. Right now we pick the maximum weight across implementing types and dataSources independently from children costs selection. This concerns the estimation the most. The better approach would be to be tie this process with the selection of the type with maximum cost. --- router-tests/go.mod | 2 +- router-tests/go.sum | 4 ++-- router/core/graphql_handler.go | 12 ++++++------ router/core/header_rule_engine.go | 11 ++++++----- router/go.mod | 2 +- router/go.sum | 4 ++-- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/router-tests/go.mod b/router-tests/go.mod index 5e9b2373a1..da6da4ab94 100644 --- a/router-tests/go.mod +++ b/router-tests/go.mod @@ -31,7 +31,7 @@ require ( github.com/wundergraph/cosmo/router v0.0.0-20260330183556-dc4388d100a4 github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e github.com/wundergraph/cosmo/speedtrap v0.0.0-00010101000000-000000000000 - github.com/wundergraph/graphql-go-tools/v2 v2.4.1 + github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/sdk v1.43.0 go.opentelemetry.io/otel/sdk/metric v1.43.0 diff --git a/router-tests/go.sum b/router-tests/go.sum index 4f94b1cbb4..0f3cb33710 100644 --- a/router-tests/go.sum +++ b/router-tests/go.sum @@ -379,8 +379,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.1 h1:zkqqyojT4feX65E+4i+1FKidpzhoeU/5NzNxs6z6a9I= -github.com/wundergraph/graphql-go-tools/v2 v2.4.1/go.mod h1:xH7XBGtKJkNTi6w6TnCDLRa7Jo2gyBBRUipIYwC5vLI= +github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 h1:aQ5aL6DjHEmWANGBWiAph9XUGFS30UfVTp2L2Jtbrck= +github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= diff --git a/router/core/graphql_handler.go b/router/core/graphql_handler.go index dd4166d859..c31a25780c 100644 --- a/router/core/graphql_handler.go +++ b/router/core/graphql_handler.go @@ -227,13 +227,13 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { pw.reqCtx = reqCtx } if h.enableCostResponseHeaders && reqCtx.operation.costEstimatedSet { - // actualListSizes is populated by the resolver after resolution completes, + // ArrayStats is populated by the resolver after resolution completes, // and we need to set headers before actual write happens in the same resolver. - pw.costHeaderSetter = func(actualListSizes map[string]int) { + pw.costHeaderSetter = func(arrayStats map[string]resolve.ArrayStats) { pw.writer.Header().Set(CostEstimatedHeader, strconv.Itoa(reqCtx.operation.costEstimated)) - if actualListSizes != nil { + if arrayStats != nil { if costCalc := reqCtx.operation.preparedPlan.preparedPlan.GetCostCalculator(); costCalc != nil { - actual := costCalc.ActualCost(resolveCtx.VariablesView(), actualListSizes) + actual := costCalc.ActualCost(resolveCtx.VariablesView(), arrayStats) reqCtx.operation.costActual = actual reqCtx.operation.costActualSet = true pw.writer.Header().Set(CostActualHeader, strconv.Itoa(actual)) @@ -252,10 +252,10 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Compute actual cost for metrics/telemetry if not already set by the header callback - if !reqCtx.operation.costActualSet && resolveCtx.ActualListSizes != nil && + if !reqCtx.operation.costActualSet && resolveCtx.ArrayStats != nil && reqCtx.operation.preparedPlan != nil && reqCtx.operation.preparedPlan.preparedPlan != nil { if costCalc := reqCtx.operation.preparedPlan.preparedPlan.GetCostCalculator(); costCalc != nil { - reqCtx.operation.costActual = costCalc.ActualCost(resolveCtx.VariablesView(), resolveCtx.ActualListSizes) + reqCtx.operation.costActual = costCalc.ActualCost(resolveCtx.VariablesView(), resolveCtx.ArrayStats) reqCtx.operation.costActualSet = true } } diff --git a/router/core/header_rule_engine.go b/router/core/header_rule_engine.go index c25bf9d8a1..fe716b2478 100644 --- a/router/core/header_rule_engine.go +++ b/router/core/header_rule_engine.go @@ -16,6 +16,10 @@ import ( "github.com/cespare/xxhash/v2" "github.com/expr-lang/expr/vm" cachedirective "github.com/pquerna/cachecontrol/cacheobject" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" "github.com/wundergraph/cosmo/router/internal/expr" "github.com/wundergraph/cosmo/router/internal/headers" @@ -25,9 +29,6 @@ import ( "github.com/wundergraph/graphql-go-tools/v2/pkg/ast" "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/datasource/graphql_datasource" "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "go.uber.org/zap" ) var ( @@ -103,7 +104,7 @@ type headerPropagationWriter struct { routerHeaderPropagation *HeaderPropagation reqCtx *requestContext didApplyRouterRespHeaders bool - costHeaderSetter func(actualListSizes map[string]int) + costHeaderSetter func(arrayStats map[string]resolve.ArrayStats) didSetCostHeaders bool } @@ -137,7 +138,7 @@ func (h *headerPropagationWriter) Write(p []byte) (n int, err error) { } if h.costHeaderSetter != nil && !h.didSetCostHeaders { h.didSetCostHeaders = true - h.costHeaderSetter(h.resolveCtx.ActualListSizes) + h.costHeaderSetter(h.resolveCtx.ArrayStats) } return h.writer.Write(p) } diff --git a/router/go.mod b/router/go.mod index c6d7494346..ec33002dc3 100644 --- a/router/go.mod +++ b/router/go.mod @@ -31,7 +31,7 @@ require ( github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/twmb/franz-go v1.16.1 - github.com/wundergraph/graphql-go-tools/v2 v2.4.1 + github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 // Do not upgrade, it renames attributes we rely on go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 go.opentelemetry.io/contrib/propagators/b3 v1.43.0 diff --git a/router/go.sum b/router/go.sum index 4017d37639..f1dcfb08f1 100644 --- a/router/go.sum +++ b/router/go.sum @@ -331,8 +331,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.1 h1:zkqqyojT4feX65E+4i+1FKidpzhoeU/5NzNxs6z6a9I= -github.com/wundergraph/graphql-go-tools/v2 v2.4.1/go.mod h1:xH7XBGtKJkNTi6w6TnCDLRa7Jo2gyBBRUipIYwC5vLI= +github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 h1:aQ5aL6DjHEmWANGBWiAph9XUGFS30UfVTp2L2Jtbrck= +github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= From 1cfa48058e10499724e8d1a354446420564f02f8 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:00:19 +0300 Subject: [PATCH 2/6] bump the engine to 2.4.3 --- router-tests/go.mod | 2 +- router-tests/go.sum | 6 ++++-- router/go.mod | 2 +- router/go.sum | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/router-tests/go.mod b/router-tests/go.mod index da6da4ab94..99fe263320 100644 --- a/router-tests/go.mod +++ b/router-tests/go.mod @@ -31,7 +31,7 @@ require ( github.com/wundergraph/cosmo/router v0.0.0-20260330183556-dc4388d100a4 github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e github.com/wundergraph/cosmo/speedtrap v0.0.0-00010101000000-000000000000 - github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 + github.com/wundergraph/graphql-go-tools/v2 v2.4.3 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/sdk v1.43.0 go.opentelemetry.io/otel/sdk/metric v1.43.0 diff --git a/router-tests/go.sum b/router-tests/go.sum index 0f3cb33710..578459323d 100644 --- a/router-tests/go.sum +++ b/router-tests/go.sum @@ -301,6 +301,8 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E= @@ -379,8 +381,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 h1:aQ5aL6DjHEmWANGBWiAph9XUGFS30UfVTp2L2Jtbrck= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= +github.com/wundergraph/graphql-go-tools/v2 v2.4.3 h1:ZaS96clCYlf/AyZmia+VSgfOmZK2Gae4yVcJcnou0QQ= +github.com/wundergraph/graphql-go-tools/v2 v2.4.3/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= diff --git a/router/go.mod b/router/go.mod index ec33002dc3..24ea3bb5aa 100644 --- a/router/go.mod +++ b/router/go.mod @@ -31,7 +31,7 @@ require ( github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/twmb/franz-go v1.16.1 - github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 + github.com/wundergraph/graphql-go-tools/v2 v2.4.3 // Do not upgrade, it renames attributes we rely on go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 go.opentelemetry.io/contrib/propagators/b3 v1.43.0 diff --git a/router/go.sum b/router/go.sum index f1dcfb08f1..562ddf42ab 100644 --- a/router/go.sum +++ b/router/go.sum @@ -261,6 +261,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E= @@ -331,8 +333,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347 h1:aQ5aL6DjHEmWANGBWiAph9XUGFS30UfVTp2L2Jtbrck= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2-0.20260529145034-8d4ca864a347/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= +github.com/wundergraph/graphql-go-tools/v2 v2.4.3 h1:ZaS96clCYlf/AyZmia+VSgfOmZK2Gae4yVcJcnou0QQ= +github.com/wundergraph/graphql-go-tools/v2 v2.4.3/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= From 1fd798705f7d2716e1e60ed4ed856a48d4face36 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 5 Jun 2026 18:39:17 +0300 Subject: [PATCH 3/6] adopt changes in the engine --- router-tests/security/costs_test.go | 6 +++--- router/core/graphql_handler.go | 10 +++++----- router/core/header_rule_engine.go | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/router-tests/security/costs_test.go b/router-tests/security/costs_test.go index befa2330f2..adeb092a85 100644 --- a/router-tests/security/costs_test.go +++ b/router-tests/security/costs_test.go @@ -270,7 +270,7 @@ func TestOperationCost(t *testing.T) { }, func(t *testing.T, xEnv *testenv.Environment) { // Query 1: weight from both subgraphs is applied res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ - Query: `{ products { ... on Cosmo { upc repositoryURL engineers { id } } } }`, + Query: `{ products { __typename ... on Cosmo { upc repositoryURL engineers { id } } } }`, }) require.Contains(t, res.Body, `"data":`) require.NotContains(t, res.Body, `"errors":`) @@ -287,7 +287,7 @@ func TestOperationCost(t *testing.T) { require.Equal(t, "230", estimated) actual := res.Response.Header.Get(core.CostActualHeader) - require.Equal(t, "46", actual) // (7/3 + 13) * 3 + require.Equal(t, "24", actual) // 3 * (5.67 + 1*7/3 ) // Query 2: only employees-subgraph fields — Cosmo @cost(weight: 5) from employees applies res2 := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -300,7 +300,7 @@ func TestOperationCost(t *testing.T) { require.Equal(t, "150", estimated2) actual2 := res2.Response.Header.Get(core.CostActualHeader) - require.Equal(t, "22", actual2) + require.Equal(t, "14", actual2) }) }) diff --git a/router/core/graphql_handler.go b/router/core/graphql_handler.go index c31a25780c..f02817e8ac 100644 --- a/router/core/graphql_handler.go +++ b/router/core/graphql_handler.go @@ -229,11 +229,11 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if h.enableCostResponseHeaders && reqCtx.operation.costEstimatedSet { // ArrayStats is populated by the resolver after resolution completes, // and we need to set headers before actual write happens in the same resolver. - pw.costHeaderSetter = func(arrayStats map[string]resolve.ArrayStats) { + pw.costHeaderSetter = func(typeStats map[string]resolve.TypeNameStats) { pw.writer.Header().Set(CostEstimatedHeader, strconv.Itoa(reqCtx.operation.costEstimated)) - if arrayStats != nil { + if typeStats != nil { if costCalc := reqCtx.operation.preparedPlan.preparedPlan.GetCostCalculator(); costCalc != nil { - actual := costCalc.ActualCost(resolveCtx.VariablesView(), arrayStats) + actual := costCalc.ActualCost(resolveCtx.VariablesView(), typeStats) reqCtx.operation.costActual = actual reqCtx.operation.costActualSet = true pw.writer.Header().Set(CostActualHeader, strconv.Itoa(actual)) @@ -252,10 +252,10 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Compute actual cost for metrics/telemetry if not already set by the header callback - if !reqCtx.operation.costActualSet && resolveCtx.ArrayStats != nil && + if !reqCtx.operation.costActualSet && resolveCtx.TypeNameStats != nil && reqCtx.operation.preparedPlan != nil && reqCtx.operation.preparedPlan.preparedPlan != nil { if costCalc := reqCtx.operation.preparedPlan.preparedPlan.GetCostCalculator(); costCalc != nil { - reqCtx.operation.costActual = costCalc.ActualCost(resolveCtx.VariablesView(), resolveCtx.ArrayStats) + reqCtx.operation.costActual = costCalc.ActualCost(resolveCtx.VariablesView(), resolveCtx.TypeNameStats) reqCtx.operation.costActualSet = true } } diff --git a/router/core/header_rule_engine.go b/router/core/header_rule_engine.go index fe716b2478..babc53d2f4 100644 --- a/router/core/header_rule_engine.go +++ b/router/core/header_rule_engine.go @@ -104,7 +104,7 @@ type headerPropagationWriter struct { routerHeaderPropagation *HeaderPropagation reqCtx *requestContext didApplyRouterRespHeaders bool - costHeaderSetter func(arrayStats map[string]resolve.ArrayStats) + costHeaderSetter func(typeStats map[string]resolve.TypeNameStats) didSetCostHeaders bool } @@ -138,7 +138,7 @@ func (h *headerPropagationWriter) Write(p []byte) (n int, err error) { } if h.costHeaderSetter != nil && !h.didSetCostHeaders { h.didSetCostHeaders = true - h.costHeaderSetter(h.resolveCtx.ArrayStats) + h.costHeaderSetter(h.resolveCtx.TypeNameStats) } return h.writer.Write(p) } From 9176855264567410fd779490df4477014df381c0 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:58:10 +0300 Subject: [PATCH 4/6] bump to 2.4.4 --- router-tests/go.mod | 2 +- router-tests/go.sum | 4 ++-- router/go.mod | 2 +- router/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/router-tests/go.mod b/router-tests/go.mod index 99fe263320..b81cc273f6 100644 --- a/router-tests/go.mod +++ b/router-tests/go.mod @@ -31,7 +31,7 @@ require ( github.com/wundergraph/cosmo/router v0.0.0-20260330183556-dc4388d100a4 github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e github.com/wundergraph/cosmo/speedtrap v0.0.0-00010101000000-000000000000 - github.com/wundergraph/graphql-go-tools/v2 v2.4.3 + github.com/wundergraph/graphql-go-tools/v2 v2.4.4 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/sdk v1.43.0 go.opentelemetry.io/otel/sdk/metric v1.43.0 diff --git a/router-tests/go.sum b/router-tests/go.sum index 578459323d..12a78bfb31 100644 --- a/router-tests/go.sum +++ b/router-tests/go.sum @@ -381,8 +381,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.3 h1:ZaS96clCYlf/AyZmia+VSgfOmZK2Gae4yVcJcnou0QQ= -github.com/wundergraph/graphql-go-tools/v2 v2.4.3/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4 h1:VCvS9bku4ie7+St3+H5SNuVz6dtQiDKujqQ439yrMBM= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= diff --git a/router/go.mod b/router/go.mod index 24ea3bb5aa..1d099273e3 100644 --- a/router/go.mod +++ b/router/go.mod @@ -31,7 +31,7 @@ require ( github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/twmb/franz-go v1.16.1 - github.com/wundergraph/graphql-go-tools/v2 v2.4.3 + github.com/wundergraph/graphql-go-tools/v2 v2.4.4 // Do not upgrade, it renames attributes we rely on go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 go.opentelemetry.io/contrib/propagators/b3 v1.43.0 diff --git a/router/go.sum b/router/go.sum index 562ddf42ab..c0bb919835 100644 --- a/router/go.sum +++ b/router/go.sum @@ -333,8 +333,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.3 h1:ZaS96clCYlf/AyZmia+VSgfOmZK2Gae4yVcJcnou0QQ= -github.com/wundergraph/graphql-go-tools/v2 v2.4.3/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4 h1:VCvS9bku4ie7+St3+H5SNuVz6dtQiDKujqQ439yrMBM= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= From a4b8b6c015e6fa4954c520d6076b77fd18cbb468 Mon Sep 17 00:00:00 2001 From: Ahmet Soormally Date: Mon, 8 Jun 2026 14:08:21 +0200 Subject: [PATCH 5/6] test(router): update MCP input-schema goldens to union nullable form (#2926) Updates two stale goldens in router-tests/protocol/mcp_test.go to unblock the ./protocol job on #2925. The engine bump (graphql-go-tools #1518/#1528) changed nullable rendering from {"type":"x","nullable":true} to the union {"type":["x","null"]} (nullable enums gain a trailing null). These goldens hard-code the generated schema, so they went stale. Engine fix is correct; goldens just needed updating. --- router-tests/protocol/mcp_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/router-tests/protocol/mcp_test.go b/router-tests/protocol/mcp_test.go index ded7b0fc17..8321d292e4 100644 --- a/router-tests/protocol/mcp_test.go +++ b/router-tests/protocol/mcp_test.go @@ -138,7 +138,7 @@ func TestMCP(t *testing.T) { Description: "This is a GraphQL query that retrieves a list of employees.", InputSchema: mcp.ToolInputSchema{ Type: "object", - Properties: map[string]interface{}{"criteria": map[string]interface{}{"additionalProperties": false, "description": "Allows to filter employees by their details.", "nullable": false, "properties": map[string]interface{}{"hasPets": map[string]interface{}{"nullable": true, "type": "boolean"}, "nationality": map[string]interface{}{"enum": []interface{}{"AMERICAN", "DUTCH", "ENGLISH", "GERMAN", "INDIAN", "SPANISH", "UKRAINIAN"}, "nullable": true, "type": "string"}, "nested": map[string]interface{}{"additionalProperties": false, "nullable": true, "properties": map[string]interface{}{"hasChildren": map[string]interface{}{"nullable": true, "type": "boolean"}, "maritalStatus": map[string]interface{}{"enum": []interface{}{"ENGAGED", "MARRIED"}, "nullable": true, "type": "string"}}, "type": "object"}}, "type": "object"}}, + Properties: map[string]interface{}{"criteria": map[string]interface{}{"additionalProperties": false, "description": "Allows to filter employees by their details.", "properties": map[string]interface{}{"hasPets": map[string]interface{}{"type": []interface{}{"boolean", "null"}}, "nationality": map[string]interface{}{"enum": []interface{}{"AMERICAN", "DUTCH", "ENGLISH", "GERMAN", "INDIAN", "SPANISH", "UKRAINIAN", nil}, "type": []interface{}{"string", "null"}}, "nested": map[string]interface{}{"additionalProperties": false, "properties": map[string]interface{}{"hasChildren": map[string]interface{}{"type": []interface{}{"boolean", "null"}}, "maritalStatus": map[string]interface{}{"enum": []interface{}{"ENGAGED", "MARRIED", nil}, "type": []interface{}{"string", "null"}}}, "type": []interface{}{"object", "null"}}}, "type": "object"}}, Required: []string(nil)}, RawInputSchema: json.RawMessage(nil), Annotations: mcp.ToolAnnotation{ @@ -298,7 +298,7 @@ Description: This is a GraphQL query that retrieves a list of employees. Input Schema: ` + bt + `json -{"additionalProperties":false,"description":"This is a GraphQL query that retrieves a list of employees.","nullable":true,"properties":{"criteria":{"additionalProperties":false,"description":"Allows to filter employees by their details.","nullable":false,"properties":{"hasPets":{"nullable":true,"type":"boolean"},"nationality":{"enum":["AMERICAN","DUTCH","ENGLISH","GERMAN","INDIAN","SPANISH","UKRAINIAN"],"nullable":true,"type":"string"},"nested":{"additionalProperties":false,"nullable":true,"properties":{"hasChildren":{"nullable":true,"type":"boolean"},"maritalStatus":{"enum":["ENGAGED","MARRIED"],"nullable":true,"type":"string"}},"type":"object"}},"type":"object"}},"type":"object"} +{"additionalProperties":false,"description":"This is a GraphQL query that retrieves a list of employees.","properties":{"criteria":{"additionalProperties":false,"description":"Allows to filter employees by their details.","properties":{"hasPets":{"type":["boolean","null"]},"nationality":{"enum":["AMERICAN","DUTCH","ENGLISH","GERMAN","INDIAN","SPANISH","UKRAINIAN",null],"type":["string","null"]},"nested":{"additionalProperties":false,"properties":{"hasChildren":{"type":["boolean","null"]},"maritalStatus":{"enum":["ENGAGED","MARRIED",null],"type":["string","null"]}},"type":["object","null"]}},"type":"object"}},"type":"object"} ` + bt + ` GraphQL Query: From fbf6e9183391429637fdcfdecfe937708abb0580 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Mon, 8 Jun 2026 13:10:37 +0300 Subject: [PATCH 6/6] fix the comment --- router-tests/security/costs_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/router-tests/security/costs_test.go b/router-tests/security/costs_test.go index adeb092a85..c9601527df 100644 --- a/router-tests/security/costs_test.go +++ b/router-tests/security/costs_test.go @@ -270,7 +270,7 @@ func TestOperationCost(t *testing.T) { }, func(t *testing.T, xEnv *testenv.Environment) { // Query 1: weight from both subgraphs is applied res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ - Query: `{ products { __typename ... on Cosmo { upc repositoryURL engineers { id } } } }`, + Query: `{ products { ... on Cosmo { upc repositoryURL engineers { id } } } }`, }) require.Contains(t, res.Body, `"data":`) require.NotContains(t, res.Body, `"errors":`) @@ -287,7 +287,7 @@ func TestOperationCost(t *testing.T) { require.Equal(t, "230", estimated) actual := res.Response.Header.Get(core.CostActualHeader) - require.Equal(t, "24", actual) // 3 * (5.67 + 1*7/3 ) + require.Equal(t, "24", actual) // 3 * (5.67 + 1*7/3) // Query 2: only employees-subgraph fields — Cosmo @cost(weight: 5) from employees applies res2 := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{