agent_patch
runs/2026-02-28__13-38-10__gpt-5-4/flux-pr-870/flux-pr-870.1-of-1.2026-02-28__13-38-10__gpt-5-4/agent-logs/agent.patch
10494 bytes
diff --git a/tmp/agent-patch-flux-pr-870.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/extensions_test.go b/app/v2/pkg/engine/resolve/extensions_test.go
index 6d16e3e..c5a30d3 100644
--- a/tmp/agent-patch-flux-pr-870.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/resolve/extensions_test.go
+++ b/app/v2/pkg/engine/resolve/extensions_test.go
@@ -120,7 +120,7 @@ func TestExtensions(t *testing.T) {
ctx.ctx = SetTraceStart(ctx.ctx, true)
return res, ctx,
- `{"errors":[{"message":"Unauthorized request to Subgraph 'users' at Path 'query', Reason: test."},{"message":"Failed to fetch from Subgraph 'reviews' at Path 'query.me'.","extensions":{"errors":[{"message":"Failed to render Fetch Input","path":["me"]}]}},{"message":"Failed to fetch from Subgraph 'products' at Path 'query.me.reviews.@.product'.","extensions":{"errors":[{"message":"Failed to render Fetch Input","path":["me","reviews","@","product"]}]}}],"data":{"me":null},"extensions":{"authorization":{"missingScopes":[["read:users"]]},"rateLimit":{"Policy":"policy","Allowed":0,"Used":0},"trace":{"version":"1","info":{"trace_start_time":"","trace_start_unix":0,"parse_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""},"normalize_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""},"validate_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""},"planner_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""}},"fetches":{"kind":"Sequence","children":[{"kind":"Single","fetch":{"kind":"Single","path":"query","trace":{"raw_input_data":{},"single_flight_used":false,"single_flight_shared_response":false,"load_skipped":false}}},{"kind":"Single","fetch":{"kind":"Single","path":"query.me","trace":{"single_flight_used":false,"single_flight_shared_response":false,"load_skipped":false}}},{"kind":"Single","fetch":{"kind":"Single","path":"query.me.reviews.@.product","trace":{"single_flight_used":false,"single_flight_shared_response":false,"load_skipped":false}}}]}}}}`,
+ `{"errors":[{"message":"Unauthorized request to Subgraph 'users' at Path 'query', Reason: test."},{"message":"Failed to fetch from Subgraph 'reviews' at Path 'query.me'.","extensions":{"errors":[{"message":"Failed to render Fetch Input","path":["me"]}]}},{"message":"Failed to fetch from Subgraph 'products' at Path 'query.me.reviews.@.product'.","extensions":{"errors":[{"message":"Failed to render Fetch Input","path":["me","reviews","@","product"]}]}}],"data":{"me":null},"extensions":{"authorization":{"missingScopes":[["read:users"]]},"rateLimit":{"Policy":"policy","Allowed":0,"Used":0},"trace":{"version":"1","info":{"trace_start_time":"","trace_start_unix":0,"parse_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""},"normalize_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""},"validate_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""},"planner_stats":{"duration_nanoseconds":0,"duration_pretty":"","duration_since_start_nanoseconds":0,"duration_since_start_pretty":""}},"fetches":{"kind":"Sequence","children":[{"kind":"Single","fetch":{"kind":"Single","path":"query","data_source_id":"users","trace":{"raw_input_data":{},"single_flight_used":false,"single_flight_shared_response":false,"load_skipped":false}}},{"kind":"Single","fetch":{"kind":"Single","path":"query.me","data_source_id":"reviews","trace":{"single_flight_used":false,"single_flight_shared_response":false,"load_skipped":false}}},{"kind":"Single","fetch":{"kind":"Single","path":"query.me.reviews.@.product","data_source_id":"products","trace":{"single_flight_used":false,"single_flight_shared_response":false,"load_skipped":false}}}]}}}}`,
func(t *testing.T) {}
}))
}
diff --git a/tmp/agent-patch-flux-pr-870.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 138664e..650f089 100644
--- a/tmp/agent-patch-flux-pr-870.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
@@ -1,6 +1,7 @@
package resolve
import (
+ "bytes"
"encoding/json"
"slices"
@@ -291,6 +292,39 @@ type DataSourceLoadTrace struct {
Path string `json:"-"`
}
+func (d DataSourceLoadTrace) MarshalJSON() ([]byte, error) {
+ type dataSourceLoadTraceAlias DataSourceLoadTrace
+
+ alias := dataSourceLoadTraceAlias(d)
+ alias.RawInputData = normalizeTraceJSON(alias.RawInputData)
+ alias.Input = normalizeTraceJSON(alias.Input)
+ alias.Output = normalizeTraceJSON(alias.Output)
+
+ return json.Marshal(alias)
+}
+
+func normalizeTraceJSON(raw json.RawMessage) json.RawMessage {
+ raw = bytes.TrimSpace(raw)
+ if len(raw) == 0 {
+ return nil
+ }
+
+ var value any
+ if err := json.Unmarshal(raw, &value); err == nil {
+ normalized, err := json.Marshal(value)
+ if err == nil {
+ return normalized
+ }
+ }
+
+ var compact bytes.Buffer
+ if err := json.Compact(&compact, raw); err == nil {
+ return append(json.RawMessage(nil), compact.Bytes()...)
+ }
+
+ return append(json.RawMessage(nil), raw...)
+}
+
type LoadStats struct {
GetConn GetConnStats `json:"get_conn"`
GotConn GotConnStats `json:"got_conn"`
diff --git a/tmp/agent-patch-flux-pr-870.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 b246410..e9c68fa 100644
--- a/tmp/agent-patch-flux-pr-870.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
@@ -78,10 +78,12 @@ type FetchTreeTraceNode struct {
}
type FetchTraceNode struct {
- Kind string `json:"kind"`
- Path string `json:"path"`
- Fetch *DataSourceLoadTrace `json:"trace,omitempty"`
- Fetches []*DataSourceLoadTrace `json:"traces,omitempty"`
+ Kind string `json:"kind"`
+ Path string `json:"path"`
+ DataSourceID string `json:"data_source_id,omitempty"`
+ DataSourceIdentifier string `json:"data_source_identifier,omitempty"`
+ Fetch *DataSourceLoadTrace `json:"trace,omitempty"`
+ Fetches []*DataSourceLoadTrace `json:"traces,omitempty"`
}
func (n *FetchTreeNode) Trace() *FetchTreeTraceNode {
@@ -96,26 +98,34 @@ func (n *FetchTreeNode) Trace() *FetchTreeTraceNode {
switch f := n.Item.Fetch.(type) {
case *SingleFetch:
trace.Fetch = &FetchTraceNode{
- Kind: "Single",
- Fetch: f.Trace,
- Path: n.Item.ResponsePath,
+ Kind: "Single",
+ Fetch: f.Trace,
+ Path: n.Item.ResponsePath,
+ DataSourceID: fetchDataSourceID(f.Info),
+ DataSourceIdentifier: string(f.DataSourceIdentifier),
}
case *EntityFetch:
trace.Fetch = &FetchTraceNode{
- Kind: "Entity",
- Fetch: f.Trace,
- Path: n.Item.ResponsePath,
+ Kind: "Entity",
+ Fetch: f.Trace,
+ Path: n.Item.ResponsePath,
+ DataSourceID: fetchDataSourceID(f.Info),
+ DataSourceIdentifier: string(f.DataSourceIdentifier),
}
case *BatchEntityFetch:
trace.Fetch = &FetchTraceNode{
- Kind: "BatchEntity",
- Fetch: f.Trace,
- Path: n.Item.ResponsePath,
+ Kind: "BatchEntity",
+ Fetch: f.Trace,
+ Path: n.Item.ResponsePath,
+ DataSourceID: fetchDataSourceID(f.Info),
+ DataSourceIdentifier: string(f.DataSourceIdentifier),
}
case *ParallelListItemFetch:
trace.Fetch = &FetchTraceNode{
- Kind: "ParallelList",
- Fetches: make([]*DataSourceLoadTrace, len(f.Traces)),
+ Kind: "ParallelList",
+ DataSourceID: fetchDataSourceID(f.Fetch.Info),
+ DataSourceIdentifier: string(f.Fetch.DataSourceIdentifier),
+ Fetches: make([]*DataSourceLoadTrace, len(f.Traces)),
}
for i, t := range f.Traces {
trace.Fetch.Fetches[i] = t.Trace
@@ -130,3 +140,10 @@ func (n *FetchTreeNode) Trace() *FetchTreeTraceNode {
}
return trace
}
+
+func fetchDataSourceID(info *FetchInfo) string {
+ if info == nil {
+ return ""
+ }
+ return info.DataSourceID
+}
diff --git a/app/v2/pkg/engine/resolve/trace_test.go b/app/v2/pkg/engine/resolve/trace_test.go
new file mode 100644
index 0000000..c13a667
--- /dev/null
+++ b/app/v2/pkg/engine/resolve/trace_test.go
@@ -0,0 +1,44 @@
+package resolve
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestDataSourceLoadTraceMarshalJSONNormalizesEmbeddedJSON(t *testing.T) {
+ trace := DataSourceLoadTrace{
+ RawInputData: json.RawMessage(`{ "a": 1, "a": 2, "nested": { "b": 1, "b": 3 } }`),
+ Input: json.RawMessage("{\n \"query\": \"{ me }\"\n}"),
+ Output: json.RawMessage(`{ "data": { "me": { "id": 1, "id": 2 } } }`),
+ }
+
+ data, err := json.Marshal(trace)
+ if err != nil {
+ t.Fatalf("marshal trace: %v", err)
+ }
+
+ expected := `{"raw_input_data":{"a":2,"nested":{"b":3}},"input":{"query":"{ me }"},"output":{"data":{"me":{"id":2}}},"single_flight_used":false,"single_flight_shared_response":false,"load_skipped":false}`
+ if string(data) != expected {
+ t.Fatalf("unexpected trace json:\nexpected: %s\nactual: %s", expected, string(data))
+ }
+}
+
+func TestFetchTreeTraceIncludesDataSourceMetadata(t *testing.T) {
+ tree := SingleWithPath(&SingleFetch{
+ DataSourceIdentifier: []byte("graphql_datasource.Source"),
+ Info: &FetchInfo{
+ DataSourceID: "products",
+ },
+ }, "query.topProducts")
+
+ trace := tree.Trace()
+ if trace == nil || trace.Fetch == nil {
+ t.Fatal("expected fetch trace")
+ }
+ if trace.Fetch.DataSourceID != "products" {
+ t.Fatalf("expected data source id %q, got %q", "products", trace.Fetch.DataSourceID)
+ }
+ if trace.Fetch.DataSourceIdentifier != "graphql_datasource.Source" {
+ t.Fatalf("expected data source identifier %q, got %q", "graphql_datasource.Source", trace.Fetch.DataSourceIdentifier)
+ }
+}