Repository navigation
fix(adcp/v3): keep an empty approved_sellers list on the wire - #594
garvitkaushik-123 wants to merge 1 commit into
Conversation
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
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean governance-boundary fix with no blocking, medium, or escalation triggers.
Checked:
adcp/v3/governance_types.go: DropsomitemptyonPlan.ApprovedSellersso[]string{}("no seller approved") no longer serializes identically to nil ("any seller"). This closes a fail-open on a governance boundary. Verified encode semantics: nil→null,[]→[], listed→unchanged.adcp/v3/plan_validate_test.go: New test covers encode plus round-trip across the three cases.- Schema↔generated-type coherence:
Planis hand-written, listed in both lintEXEMPTandKNOWN_TYPES, so no regen owed; noadcp/schemas/**touched. - Conventional-commit marker:
fix:(notfix!:) is justified — exported type and field name/meaning unchanged, only null-vs-empty encoding corrected. - Pathway-back (DR-0032/0033): v3 serves 3.0/3.1/3.2 from one struct with identical field schema across minors, so no translation layer is needed; test accompanies the change.
- Sibling root
adcpv2 module with the same bug is frozen/out of scope.
high_risk is false, gated_paths is false, no author team gate, no prior decision. Note: review_decision is REVIEW_REQUIRED, but row 2 only applies when gated_paths is true (it is false here), so no escalation is triggered. Zero findings → falls through to row 9.
torbenbrodt
left a comment
There was a problem hiding this comment.
Thanks @garvitkaushik-123, this is the fix we agreed on #536, and it looks right to me.
I checked:
approved_sellersis["array", "null"]with "null means any seller" ingovernance/sync-plans-request.jsonat both v3.2.2 and adcpmain, sonullforniland[]for an empty list is the schema's own encoding.Planis only reached throughSyncPlansRequestinadcp/v3, so no other payload changes shape.TestPlan_ApprovedSellers_EmptyIsNotNullpasses on a4d7be5 and fails withomitemptyput back (Go 1.26), so it guards the fix.
The 3.1 module has the same bug: adcp/governance_types.go:24 still tags ApprovedSellers omitempty, and its SyncPlansRequest carries []Plan the same way. #554 fixed both modules together; would you add the one-line change and the test there too, or should it be a follow-up?
One non-blocking note beyond your wire note: a buyer that retries a sync_plans request with the same idempotency_key across an SDK upgrade now sends null where it used to omit the key, so a seller hashing the payload (JCS) sees a different request. That's a narrow window and the meaning is unchanged, so I'd keep fix:.
The branch is behind main, so it needs an update before it can merge.
Refs #536 (agreed in the slice 3 design discussion there). Supersedes #593 (same change, moved to a new branch).
Problem
Plan.ApprovedSellerswas taggedomitempty, so[]string{}was dropped on encode exactly likenil:nil(any seller)null[]string{}(no seller approved)[][]string{"https://s.example"}["https://s.example"]The schema (
sync-plans-request.json) definesnullas "any seller" and says nothing about an omitted key. A Go buyer sending a plan that approves no sellers could therefore lose that meaning and look unrestricted to the governance agent. Decoding was already correct; only encoding was lossy.Change
adcp/v3/governance_types.go: dropomitemptyonapproved_sellers. The Go type is unchanged, so no caller code breaks.adcp/v3/plan_validate_test.go:TestPlan_ApprovedSellers_EmptyIsNotNullcoversnil,[]and a listed seller on encode, and thatnilversus empty survives a round trip. It fails onmain(theniland[]cases) and passes with the fix.Planis hand-written and in lintEXEMPT, so the generator-levelOptional[T]work in #536 slice B can't reach this field. Slice B will name it as a hand edit; this PR fixes the empty-list case now.Wire note for reviewers
Go buyers that never set
ApprovedSellersnow send"approved_sellers": nullinstead of omitting the key. That is the schema's own encoding of "any seller", so the meaning is unchanged, but the bytes differ. That matters for byte-level payload comparisons and for the JCS-basedplan_hashof those plans. The field's schema is identical in 3.1.29, 3.2.2 and 3.3.0-beta.0 (["array","null"], nominItems), so no version-translation pathway is needed. I've kept this asfix:, notfix!:, because the exported type and the field's meaning are unchanged.Validation
go test ./...inadcp/v3(Go 1.26.2): passgo vet ./...: passgofmt -lon the changed files: cleanpython3 lint.py --strictagainst the pinned 3.2.2 bundle: passgolangci-lintnot run locally: the installed binary was built with Go 1.25 and refuses the 1.26.2 module, so CI covers it.