From 34a4f03e378734ffb40a7513a37014ee8224a002 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 15:36:37 +0000 Subject: [PATCH] fix(adcp/v3): keep an empty approved_sellers list on the wire Plan.ApprovedSellers was tagged omitempty, so an empty list (no seller approved) was dropped on encode exactly like nil. The schema defines null as "any seller", so a plan meant to approve no sellers could reach the governance agent with no seller restriction at all. Drop omitempty on that one field: [] now encodes as [], and nil encodes as null, which the schema already defines as "any seller". The Go type is unchanged. The schema for this field is the same in 3.1, 3.2 and 3.3.0-beta.0. Refs #536 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019tMquArX2EYTUkWaEcowyx --- adcp/v3/governance_types.go | 2 +- adcp/v3/plan_validate_test.go | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/adcp/v3/governance_types.go b/adcp/v3/governance_types.go index 2ad012d7..f79d5230 100644 --- a/adcp/v3/governance_types.go +++ b/adcp/v3/governance_types.go @@ -21,7 +21,7 @@ type Plan struct { HumanReviewRequired bool `json:"human_review_required,omitempty"` HumanOverride *HumanOverride `json:"human_override,omitempty"` CustomPolicies []PolicyEntry `json:"custom_policies,omitempty"` - ApprovedSellers []string `json:"approved_sellers,omitempty"` + ApprovedSellers []string `json:"approved_sellers"` // No omitempty: nil encodes as null (any seller), [] as no seller approved. Delegations []PlanDelegation `json:"delegations,omitempty"` Portfolio *PlanPortfolio `json:"portfolio,omitempty"` Ext map[string]any `json:"ext,omitempty"` diff --git a/adcp/v3/plan_validate_test.go b/adcp/v3/plan_validate_test.go index 3ba81729..fcc278a4 100644 --- a/adcp/v3/plan_validate_test.go +++ b/adcp/v3/plan_validate_test.go @@ -329,3 +329,39 @@ func TestSyncPlansRequest_RoundTrip(t *testing.T) { require.Len(t, decoded.Plans, 1) assert.Empty(t, decoded.Plans[0].Validate()) } + +func TestPlan_ApprovedSellers_EmptyIsNotNull(t *testing.T) { + // The schema says null means any seller, so an empty list (no seller + // approved) must not be dropped on encode and read back as unrestricted. + cases := []struct { + name string + sellers []string + want string + }{ + {"nil is any seller", nil, `null`}, + {"empty is no seller", []string{}, `[]`}, + {"listed", []string{"https://seller.example"}, `["https://seller.example"]`}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + p := validPlan() + p.ApprovedSellers = tc.sellers + b, err := json.Marshal(SyncPlansRequest{IdempotencyKey: "req-0000000000000001", Plans: []Plan{p}}) + require.NoError(t, err) + + var raw struct { + Plans []map[string]json.RawMessage `json:"plans"` + } + require.NoError(t, json.Unmarshal(b, &raw)) + require.Len(t, raw.Plans, 1) + require.Contains(t, raw.Plans[0], "approved_sellers") + assert.JSONEq(t, tc.want, string(raw.Plans[0]["approved_sellers"])) + + var decoded SyncPlansRequest + require.NoError(t, json.Unmarshal(b, &decoded)) + require.Len(t, decoded.Plans, 1) + assert.Equal(t, tc.sellers == nil, decoded.Plans[0].ApprovedSellers == nil) + assert.Equal(t, tc.sellers, decoded.Plans[0].ApprovedSellers) + }) + } +}