Repository navigation
feat(adcp/v3): generate list_products request and response types - #616
torbenbrodt merged 2 commits into
Conversation
Add media-buy/list-products-request.json and -response.json to TOOL_SCHEMAS: ListProductsRequest, ListProductsResponse and the inline ListProductsIncompleteItem. This adds generated types only; no tool is registered or wired, so nothing changes for a server's tool list. The response oneOf only switches which top-level properties are required (outcome: listed or unchanged), so the generator now takes a narrow new shape as ONE flat struct: a top-level response oneOf with a discriminator whose branches add only required, a const on the discriminator and not.required, over top-level properties. The match refuses rather than skips: the top-level keywords are an allowlist, so a rule the generator does not model (schema-form dependencies, if/then, patternProperties, a not that names a declared field) means no match and a bundle that adds one fails loudly. Array-form dependencies are modelled (3.3 adds collections requiring products): Validate reports each listed field missing when the key field is present. A top-level allOf that only composes core/protocol-envelope.json or core/version-envelope.json by whole-file reference is tolerated, so the rule survives the 3.3 bundles that compose the envelope into every response. get_reporting_status and list_account_changes do not match and keep their existing paths. The rule applies to tool responses only. - Outcome is a generated enum (ListProductsResponseOutcome), and a generated Validate(opts ...ValidationOption) []ValidationIssue states the per-outcome rules: feed_version and cache_scope always, products for listed, no products for unchanged. An empty string counts as missing (stricter than the schema, as in validation.go). Under WithStrictEnums an unknown outcome and a cache_scope outside the schema enum are reported. - products is tagged omitzero, not omitempty, so a non-nil empty slice is sent as [] (an empty page for listed) and nil omits the key. The field doc says so. There is no MarshalJSON, so a type that embeds the response keeps its own fields. - ListProductsRequest.adcp_major_version is *int like the other requests; fields is []ProductField, an enum generated from the items of media-buy/product-fields.json. - incomplete gets its own item type: its scope enum has no proposals value, so GetProductsIncompleteItem does not fit. - Tests: schema-valid fixtures round-tripped for the request and both outcomes (including an empty page and an embedding wrapper), Validate cases checked against the schema as well, and generator tests for the rule, its refusals, the two excluded responses, an allOf-envelope fixture and a sweep that only list_products matches. list_products takes no required idempotency_key, so it is not in mutatingTools. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — additive v3 codegen with no blocking or medium findings.
What I checked:
- Schema↔generated-types coherence:
generate.pychanged ANDtypes_gen.goregenerated in the same PR; VERSION stays 3.2.2; schema bundle downloaded (not committed); generated banner not hand-edited. flat_required_unionis fail-closed — top-level allowlist returns None on unmodeled keywords so Validate never silently drops a rule;_flat_union_presenceraises on undetectable zero-value checks.- Generated
ListProductsResponse.Validate(types_gen.go L1848-1880) matches schema branches; all helper signatures exist;omitzerodistinguishes nil from empty slices. ListProductsIncompleteItemoptionality matchesGetProductsIncompleteItem.- No TMP/TEE/skills changes; additive new task (no breaking marker required); DR-0032/0033 pathway-back inapplicable (no version-translation layer here).
Gate checks: gated_paths=false, high_risk=false, no no-auto-approve team match, no prior decision. review_decision is REVIEW_REQUIRED but the gated-paths hard gate (row 2) only fires when gated_paths is true. With zero findings, no row 1–8 triggers — falls through to row 9.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve.
Clean pass. This PR adds a generator rule that flattens a discriminated required-only oneOf (list_products' outcome: listed | unchanged) into a single struct with a generated Validate, regenerates types_gen.go, and adds thorough tests.
Verified by the reviewer:
- Schema↔generated-type coherence maintained —
generate.pyTOOL_SCHEMAS andtypes_gen.goregenerated together (mandatory coherence rule satisfied). - The
Validateswitch matches the schema's per-branchrequired/not:{required}(types_gen.go:1867-1878). omitzerocorrectly preserves the empty-vs-nilproductsdistinction.- All referenced types/validation helpers exist; the recognizer is a refuse-not-skip allowlist.
No Critical/High/Medium findings. high_risk is false; gated_paths is false; review_decision is APPROVED; no no-auto-approve team match. Prior decision was also approve. None of decision-table rows 1–8 fire, so the outcome is approve (row 9).
Generates
list_productsrequest and response types, as agreed on #600. Types and tests only: noConfighook, noAddTool/Registerwiring, no response builder, sotools/listdoesn't change. Same basis as #589 and #596.What changes
Generator rule (
flat_required_union). A top-level responseoneOfbecomes one flat struct when:discriminatorwhose property is a stringenumin the top-levelproperties;required(naming the discriminator), aconstfor the discriminator, and optionallynot: {required}naming declared properties;allOfis a bare$reftocore/protocol-envelope.jsonorcore/version-envelope.json;dependencieslists property names (its array form), whichValidate()enforces;dependencies,if/then,patternPropertiesand the like mean no match, so a rule the generator doesn't model is refused rather than silently not enforced.Across every schema in the pinned 3.2.2 bundle (nested directories included), and across all of 3.3.0-beta.3, only
list-products-response.jsonmatches;get_reporting_statusandlist_account_changeskeep their current shape, and tests pin both. For #602: 3.3.0-beta.3 adds the envelopeallOfanddependencies: {"collections": ["products"]}to this response, and its real schema still matches, as does 3.3.0-beta.5's. A test applies both changes to the 3.2.2 schema and expects a match, withValidate()requiringproductswhencollectionsis present.ListProductsResponse. One struct with a typedOutcome(listed,unchanged). Fields every branch requires are required in the struct.Productsis taggedomitzero: nil omits the key, a non-nil empty slice sends[].Validate():listedwithoutproducts, orunchangedwith them (even an empty list), fails;outcome,feed_versionorcache_scopefails; an empty string counts as missing, which is stricter than the schema forfeed_versionand matchesvalidation.go;outcomeis accepted, and reported asUNKNOWN_VARIANTunderWithStrictEnums, which also reports acache_scopeoutsidepublic/account.For an empty
listedpage a seller sendsProducts: []CanonicalProduct{};nilomits the key andValidate()reports it. The field's doc says so.ListProductsRequest.adcp_major_versionis*intlike the other requests, andfieldsis[]ProductField, an enum generated frommedia-buy/product-fields.json.ListProductsIncompleteItemis its own type because itsscopeenum has noproposals, unlikeGetProductsIncompleteItem.Tests
Schema-valid fixtures, validated against the pinned bundle before decoding, round-trip for
listed(all optional fields), an empty page, a last page andunchanged, and for the request. EveryValidate()failure case is also checked against the schema. A struct embeddingListProductsResponsekeeps its own fields. Generator tests cover the rule, the excluded responses, the envelopeallOf, refused keywords and the sweep. Each new check was broken in a copy to show its test fails.Not in this PR
get_productsfallback (x-legacy-fallback) and no translation, sincelist_productsis new in 3.2.Validate()is opt-in and doesn't check the product items orincomplete.scope.Validate()for itsdependencies/if-thenrules,max_resultsbounds or thefieldsitem constraints.list_productsis not inmutatingTools: itsidempotency_keyis optional.include_collections,collections, two newProductFieldvalues, the envelopestatus) arrive with feat(adcp/v3): adopt the signed AdCP 3.3.0-beta.3 bundle (WIP) #602's bundle bump.Checks run locally
Generator tests (163); regeneration reproduces
types_gen.go;lint.py --strict;--coverage-max-unreviewed-any 0;go vetandgo test -race ./...inadcp/v3on Go 1.26 and 1.27; thelist_productstests underGOEXPERIMENT=jsonv2;golangci-lint.Related: #600, #564.
🤖 Generated with Claude Code