Repository navigation
fix(adcp,adcp/v3): align CreativeResult with sync-creatives-response schema - #545
sujanchalla0510 wants to merge 4 commits into
Conversation
…schema Adds the 8 spec-defined fields missing from CreativeResult (account, assigned_to, assignment_errors, changes, expires_at, platform_id, preview_url, warnings), deprecates the non-spec rejection_reason, and adds revision_id in adcp/v3 (pinned bundle is 3.2.0-rc.3). Fixes adcontextprotocol#365.
| Status string `json:"status,omitempty"` | ||
| PlatformID string `json:"platform_id,omitempty"` | ||
| Changes []string `json:"changes,omitempty"` | ||
| Errors []string `json:"errors,omitempty"` |
There was a problem hiding this comment.
Medium: Errors []string is still off-shape for the schema this PR claims to align to. Across the generated types every response errors[] is []AdcpError (AdcpError = map[string]any) — including the sibling sync task SyncGovernanceAccountResult.Errors []AdcpError (adcp/v3/types_gen.go:11154), and per-item errors carry error-code.json code/message objects. A real failed-creative payload "errors":[{"code":"invalid_format","message":"..."}] will fail json.Unmarshal (cannot unmarshal object into string), taking down the entire sync-creatives response parse on the failure path.
The PR description notes re-typing is breaking and deferred per triage — fair, and the fix needs a fix!/major marker + human sign-off, so it doesn't belong in this additive PR. But the "align CreativeResult with the schema" claim leaves the failure path unaligned, and the round-trip test only exercises "errors": [], so nothing here catches it. Worth a follow-up issue with a populated-errors payload pinning the decision.
| Status string `json:"status,omitempty"` | ||
| PlatformID string `json:"platform_id,omitempty"` | ||
| Changes []string `json:"changes,omitempty"` | ||
| Errors []string `json:"errors,omitempty"` |
There was a problem hiding this comment.
Medium: Same errors shape gap as the adcp module. In v3 the AdcpError-object convention is even more load-bearing: AdcpError = map[string]any (adcp/v3/types_gen.go:13) and every generated response types errors as []AdcpError, e.g. sibling SyncGovernanceAccountResult.Errors []AdcpError (:11154). []string here will fail to unmarshal a real failed-creative payload with structured {code,message} error objects, breaking the sync-creatives failure path.
Deferring the re-type is defensible (breaking; needs fix!/major + human sign-off), but flag it as a known remaining drift rather than folding it under "aligned." The round-trip test uses "errors": [] and never exercises the object case.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean, additive fix closing the CreativeResult drift in #365.
What I checked:
- Adds the 8 missing spec fields (plus revision_id in v3) to the hand-written CreativeResult struct, deprecates rather than removes RejectionReason (no exported-symbol removal → no breaking-change/conventional-commit trigger).
- Schema↔generated-types coherence rule structurally satisfied: CreativeResult is the sanctioned hand-written escape hatch (listed in KNOWN_TYPES/EXEMPT), so edits in adcp/types.go are within the supported escape hatch — no adcp/types_gen.go hand-edit, no adcp/schemas change without regen.
- No TMP signing, identity-agent TEE, or protocol-managed-skill paths touched.
- Round-trip tests shipped for both call sites; keyed literals used; testify already declared.
Decision path: no critical/high/medium findings (row 1/4/5 do not fire). gated_paths is false (row 2 n/a). high_risk is true but the only match, adcp/types.go, is (modified) with zero medium-or-higher findings — per the high_risk guidance a modification with no medium concern is presumed safe, and row 5 requires a medium finding to escalate. No no-auto-approve team match (row 7 n/a). Fewer than three medium findings (row 8 n/a). Falls through to row 9 → approve.
Non-blocking notes from the reviewer: field-shape fidelity could not be independently verified (no network access to upstream schemas); Errors remains []string per the #365 triage.
|
Thanks for the careful review — the I've opened a follow-up issue pinning the decision: #546 — it documents the unmarshal failure path ( |
torbenbrodt
left a comment
There was a problem hiding this comment.
Thanks for this, @sujanchalla0510. The eight fields from #365 and revision_id are typed correctly against the bundle main pins now (3.2.2), and creative_id/action stay required. I merged the branch onto current main locally: it merges cleanly, regeneration produces no diff, and lint.py --strict, the generator's Python tests, go vet and go test pass in both modules. Four additive things I'd suggest before merge, none of them breaking:
-
localizationandmacro_resolution_resultscan go in now, and v3 drops them on decode until they do. Since #555, main generatesMacroResolutionResult, andCreativeCapabilities.Localizationinadcp/v3/types.goalready holds a localization object asany. So this is two lines:Localization any `json:"localization,omitempty"` MacroResolutionResults []MacroResolutionResult `json:"macro_resolution_results,omitempty"`
The schema makes
localization"Required for every accepted creative whose request included a non-null localization object". This item validates against 3.2.2:{"creative_id":"summer_image_localized","action":"created","status":"pending_review","localization":{"default_locale_variant_id":"loc_en_us","unmatched_locale_action":"do_not_serve","locale_matching":"rfc4647_lookup","variants":[{"locale_variant_id":"loc_en_us","locale":"en-US","role":"source","assets":{"image":{"asset_type":"image","url":"https://cdn.example.com/summer-en.jpg","width":1080,"height":1080}}}]}}Unmarshalled into this PR's
CreativeResultand marshalled back, it comes out as{"creative_id":"summer_image_localized","action":"created","status":"pending_review"}. With the two fields added, it round-trips unchanged. -
Pair
CreativeResultwith its schema in lint so it can't drift silently again (the triage on #365 asked for it to leaveEXEMPT). Add'CreativeResult': 'creative/sync-creatives-response.json#/oneOf/0/properties/creatives/items'toHAND_WRITTEN_SCHEMA_SPECSand remove it fromEXEMPTinlint.py. On main, that check reports exactly the fields #365 listed (plusrevision_id,localization,macro_resolution_results). On this branch it reports onlylocalization,macro_resolution_resultsand the extrarejection_reason. After (1), only the deprecated field is left, so lint needs a small allowance for it. The same pairing inadcp/schemas(v1) reports onlyrejection_reason. -
Tests: use a schema-valid fixture and compare the whole JSON.
"status": "active"isn't aCreativeStatus(processing,pending_review,approved,suspended,rejected,archived), so both fixtures fail validation against the schema. Withapprovedthey pass. The re-encode step checks only that keys are present, and its list leaves outerrors, which the round trip drops ("errors": []in, absent out). An equality check on decode-then-encode, plus anaction: "failed"item with a structured error, would pin #546. -
Reword the deprecation comment. "Per-item failure detail belongs in Errors" currently leads to invalid output:
Errors: []string{"Video duration 31s exceeds the 30s maximum"}encodes as"errors":["Video duration 31s exceeds the 30s maximum"], which fails the schema because the items arecore/error.jsonobjects.rejection_reasonon its own still validates, because the items allow additional properties. Until #546 lands, something like: "Deprecated: not part of the AdCP schema. Failure detail belongs in errors[] as core/error.json objects; see #546." On #546's point 3:warningsitems aretype: string, soWarnings []stringis already right.
Smaller:
- The v3 test comment says the package pins 3.2.0-rc.3; main moved to 3.2.2 in #553. The 15-property count still holds.
assignment_errorsallows non-string values for keys outside^[a-zA-Z0-9_-]+$(additionalProperties: true). So{"pkg.std":{"code":"PACKAGE_NOT_FOUND"}}is schema-valid, but it fails to decode intomap[string]string.- Outside this PR:
CreativeInputcarries norevision_idorlocalization, so a seller using the typedSyncCreativeshook can't see what it has to echo back.
For context, #564 proposes a CI check that round-trips schema examples through the Go types. As it stands it wouldn't reach CreativeResult, because sync-creatives-response.json has no examples and the type is hand-written, so here the lint pairing in (2) is the guard.
Disclosure: we're building a creative agent and a seller on adcp/v3, so sync_creatives results are what our buyers will read.
- v3 CreativeResult gains Localization (any, as CreativeCapabilities holds it) and MacroResolutionResults []MacroResolutionResult (generated on main since adcontextprotocol#555); localized items now round-trip instead of dropping both fields - Pair CreativeResult with creative/sync-creatives-response.json#/oneOf/0/properties/creatives/items in HAND_WRITTEN_SCHEMA_SPECS (both modules), remove it from EXEMPT in lint.py, and add ALLOWED_EXTRA_FIELDS so the deprecated rejection_reason is a documented allowance, not drift - Tests: fixtures use a valid CreativeStatus (approved, not active); new whole-JSON round-trip test for a localized creative result; stale 3.2.0-rc.3 pin comment dropped - Deprecation comment reworded in both modules: failure detail belongs in errors[] as core/error.json objects; see adcontextprotocol#546
|
Thanks for the detailed review, @torbenbrodt — addressed in b9829f3:
Not touched, as you flagged them: the |
| // The two deeply-nested 3.2 additions (localization and | ||
| // macro_resolution_results) are intentionally excluded here; their object | ||
| // graphs belong in the generated types. |
There was a problem hiding this comment.
(non-blocking) Comment contradicts the code directly below it. It says localization and macro_resolution_results "are intentionally excluded here," but this same revision adds both as fields — Localization any and MacroResolutionResults []MacroResolutionResult. What's actually true is their object graphs aren't hand-expanded (localization is any, macro uses the generated type). Reword to say the fields are present but kept shallow, not excluded.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — schema↔generated-types coherence and wire-shape fidelity verified; no blocking findings.
What I checked
- Pairs
CreativeResulttocreative/sync-creatives-response.json#/oneOf/0/properties/creatives/itemsin both the root and v3 modules, removes it fromEXEMPT, and adds a scopedALLOWED_EXTRA_FIELDSallowance for the deprecatedrejection_reason. - Struct changes are additive (all
omitempty, no wire break) — no breaking-change / conventional-commit marker required. - CI Build & Test (1.27) and Lint pass at HEAD, confirming the schema-pairing lint matches and
python3 adcp/schemas/lint.py --strictwould not surface drift. - Round-trip tests added; localization round-trips whole-document in the test.
high_risk: truebut reasons are(modified)onadcp/schemas/**andadcp/types.gowith no medium-or-higher finding — presumed safe per the high-risk reading rules.
Decision path: No critical/high findings (row 1 n/a); gated_paths: false (row 2 n/a); no (deleted) reasons (row 3 n/a); no medium findings (rows 4/5/8 n/a); prior decision was approve (row 6 n/a); no no-auto-approve team match (row 7 n/a). Falls through to row 9 → approve.
Medium findings: none.
Nits (non-blocking):
adcp/v3/types.go:1269— v3CreativeResultdoc comment claimslocalization/macro_resolution_resultsare "intentionally excluded" while the same revision adds both fields. Worth a one-line doc fix.
|
Thanks, @sujanchalla0510. I checked b9829f3 merged onto current main: both modules regenerate with no diff and pass Two small things left:
Agreed, the failed-item structured-error test belongs with #546. On the follow-ups: yes please to an issue for |
…fields are present but shallow, not excluded
There was a problem hiding this comment.
Ladon verdict: Approve
Approve.
This pass is a delta of one file / 16 lines: a doc-comment correction in adcp/v3/types.go. The prior comment claimed localization and macro_resolution_results were "intentionally excluded," but the struct now carries both (Localization as any, MacroResolutionResults as the generated MacroResolutionResult type). The stale comment was updated to match the struct — no behavior or wire-shape change.
Checks:
- No critical/high findings (schema↔generated-type coherence, TMP signing, TEE boundary, protocol skills, breaking-wire-change markers all unaffected — this is comment-only).
- No medium findings.
high_riskis true (modified files underadcp/schemas/**andadcp/types.go), but the reviewer surfaced no medium-or-higher concerns on those modifications, so the flag alone does not escalate (no deletions, no medium on modified files).gated_pathsis false; no no-auto-approve team match.- Prior decision was
approvewith no findings; this remains clean.
Decision table: rows 1–8 do not fire → row 9 approve.
|
Thanks @torbenbrodt — the two small things are done:
|
There was a problem hiding this comment.
Approving the code. Merged onto today's main (188cdcf), CI's core checks pass locally: generator tests, regeneration with no diff, lint.py --strict and the any-coverage gate in both modules, and go vet with go test -race for root, adcp, adcp/v3 and the reference agents. #546 tracks the Errors and assignment_errors re-type.
Not tracked yet:
- The description still says
adcp/v3pins 3.2.0-rc.3 (main pins 3.2.2), that v3 excludeslocalizationandmacro_resolution_results, and that it covers 13 of 15 fields. Please refresh it, since the squash body is built from it. - The main round-trip tests check that each wire key is present after re-encoding, not that its value survives.
ALLOWED_EXTRA_FIELDShas no check for stale entries, sorejection_reasonstays allowed after the field is removed.- #567 should cover
adcp/v3only: theadcpmodule's 3.1.9 schemas have neitherrevision_idnorlocalization.
|
@bokelley, one question before this merges. My approval covers the code, not the decisions #365's triage left to you:
Are you happy with both as they stand here? |
|
Thanks @torbenbrodt — approving review actioned, including the 'not tracked yet' list:
Your two questions for @bokelley (deprecating |
|
@torbenbrodt @sujanchalla0510 — maintainer decisions, posted by Brian's coding agent at his direction:
The decisions above resolve the maintainer questions; adding the missing comments is the remaining action on this PR. This does not approve the breaking changes tracked by #546. |
|
@torbenbrodt — Brian has explicitly authorized implementing the SDK delegation. The guidance is now proposed in #582, in AGENTS.md with a contributor-facing README link. It is prospective: a complete PR notice starts a fresh five-calendar-day window, states the proposed answer and UTC deadline, and records the head SHA. A hold pauses the clock until explicitly released; material changes restart it. The author records the question and outcome in a tracked issue before merging. All six constitutional escalation categories, required reviews/checks, and blocking findings remain in force. It takes effect when #582 is merged, and does not clear the explicit security hold on #554. Posted by Brian's coding agent at his direction. |
…orm_id/preview_url Maintainer decision on adcontextprotocol#545 (bokelley, 2026-10-19): the additive fields are approved subject to adding the requested field-level documentation before merge. Adds doc comments in both modules: account as the owning account; assigned_to as successfully assigned package IDs; platform_id as the seller's creative identifier; the three are account/resource metadata, not containers for end-user identifiers; preview_url as a preview link that may carry sensitive access tokens (do not promise token-free URLs); callers must be authorized for the account and resources returned. Comment-only. Signed-off-by: sujan kumar reddy challa <sujanchalla0510@gmail.com>
7a58d26
|
Thanks @bokelley — both decisions actioned in 7a58d26 (comment-only):
Local: adcp module tests pass; adcp/v3 suite passes against main's generated |
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — documentation-only delta since prior approve (8aa637d), no new findings.
Checked:
- Delta since prior approve is doc-comment-only on CreativeResult in
adcp/types.goandadcp/v3/types.go. Comments verified accurate (account/resource metadata, PlatformID = seller creative id, PreviewURL token caveat, AssignedTo = package IDs). - No semantic, wire-shape, type, or schema/lint-pairing changes in the delta — schema↔generated-type coherence preserved.
- No breaking exported-symbol changes, so no conventional-commit marker required.
- high_risk is true only via (modified) entries, but the reviewer surfaced no medium-or-higher concerns, so modifications are presumed safe per the high-risk reasoning rules.
Decision table: no critical/high findings (rows 1 skip), gated_paths false (row 2 skip), no deletions (row 3 skip), no medium findings (rows 4/5/8 skip), prior decision was approve (row 6 skip), no no-auto-approve team match (row 7 skip). Falls through to row 9 → approve.
torbenbrodt
left a comment
There was a problem hiding this comment.
Re-approving at 7a58d26: the commit adds only the field documentation Brian asked for (no field or tag changes), so his decisions on this PR are met.
* test(adcp/v3): validate wire fixtures against their JSON schema Add assertSchemaValid, which validates a test fixture against a schema (or a JSON Pointer into one) in the downloaded bundle with the existing github.com/google/jsonschema-go dependency. Every $ref resolves from adcp/v3/schemas by its $id; nothing is fetched. Go's RE2 cannot compile 8 distinct bundle patterns (lookarounds and \uXXXX escapes, at 23 locations in 11 files), and Resolve fails on any schema that reaches one. re2Translations lists each location with an exact RE2 equivalent, so no pattern and no schema is skipped. TestRE2TranslationsMatchBundle fails on a new uncompilable pattern, a stale location, or an original that compiles now. The helper rejects the first-head fixtures of #545 (status "active") and #581 (localization in the readback shape), and accepts all 334 top-level examples in the bundle's 113 source schemas that have any. Refs #564 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(adcp/v3): run wire fixtures through assertSchemaValid Validate the request and response fixtures in the adcp/v3 tests, and the encodings of the response and pricing round-trip tests, against their schemas. Nine were invalid and are corrected. No correction weakens an assertion; the reach_unit one changes the expected value from the typo to the enum value: - create_media_buy: idempotency_key missing (the broadcast storyboard fixture, whose storyboard step generates one) or shorter than its 16-character minimum - check_governance with verdict conditions: the encoded response lacked expires_at; the two decoded fixtures lacked plan_id, expires_at and explanation - get_plan_audit_logs, get_media_buy_delivery and get_adcp_capabilities responses: status missing (the two encoded ones sent "status":"") - optimization goal: reach_unit "household" is not in enums/reach-unit; the value is "households". The same typo in the marshal table of TestGeneratedCoreRefsAcrossSurfacesMarshalTypedFields is corrected too. AGENTS.md says when a fixture goes through the helper. Refs #564 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(adcp/v3): pin RE2 translations with probes; fix #581 SHA Add probes to three re2Translations entries so a mutation of each now fails TestRE2Translations*: P1M2D (match) and PY (no match) on the first ISO duration, P0Y1D (match) on the non-zero duration, and a U+FEFF inside a macro-bearing URL (no match). Each probe agrees with the original ECMA-262 pattern in V8. #581's first head is 7643ff1, not dd5a97a; fix the comment in TestAssertSchemaValidRejectsKnownBadFixtures. Fix the wrap of the walkSchemaPatterns doc comment. Refs #564 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #365.
CreativeResultdrifted fromstatic/schemas/source/creative/sync-creatives-response.json: it was missing the 8 spec-defined fields (account,assigned_to,assignment_errors,changes,expires_at,platform_id,preview_url,warnings) and definedrejection_reason, which exists nowhere in the spec.Changes (current head 8aa637d; description refreshed 2026-10-16 per @torbenbrodt's review):
adcp/types.go: add the 8 missing fields, allomitempty(non-breaking, per the triage on CreativeResult struct missing 8 spec fields, defines 1 not in spec #365). The pinned 3.1.9 bundle has exactly the 12 spec properties from CreativeResult struct missing 8 spec fields, defines 1 not in spec #365, and this change covers all 12.adcp/v3/types.go: the same 8 fields plusrevision_id,localization, andmacro_resolution_results— all 15 spec properties of the bundle this branch pins (3.2.0-rc.3; main has since moved on to 3.2.2, this branch's pin is unchanged).localization/macro_resolution_resultsare present but kept shallow:LocalizationisanyandMacroResolutionResultsuses the generatedMacroResolutionResulttype, rather than hand-expanding their object graphs.CreativeResultis paired with thesync-creatives-responseitem schema inHAND_WRITTEN_SCHEMA_SPECSin both modules and removed fromEXEMPT; a scopedALLOWED_EXTRA_FIELDSallowance documents the deprecatedrejection_reason.RejectionReason— removing it would break Go callers. Its deprecation comment now notes failure detail belongs inerrors[]ascore/error.jsonobjects (see Follow-up: CreativeResult.Errors []string vs []AdcpError drift (breaking re-type deferred from #545) #546).Errorsstays[]string— re-typing to structured errors would be breaking; tracked in Follow-up: CreativeResult.Errors []string vs []AdcpError drift (breaking re-type deferred from #545) #546 together with theassignment_errorsdecode gap.CreativeStatusvalueapproved.How tested:
go build ./...,go vet ./..., andgo test ./...in both theadcpandadcp/v3modules — all pass. JSON round-trip tests (adcp/creative_result_test.go,adcp/v3/creative_result_test.go) cover the new wire fields and backward compat of the deprecated field, including a whole-JSON localization round-trip test in v3. @torbenbrodt additionally verified 8aa637d merged onto main (188cdcf): generator tests, regeneration with no diff,lint.py --strictand the any-coverage gate in both modules, andgo vet+go test -racefor root,adcp,adcp/v3and the reference agents.Follow-ups: #546 (the
Errors/assignment_errorsre-type), #567 (CreativeInputrevision_id+localization, v3 only — the 3.1.9 schemas carry neither field).