Repository navigation
feat(client): refuse start_requires_all_creatives_approved: true to a seller known not to declare it - #1878
Merged
Conversation
… seller known not to declare it AdCP 3.3 forbids a buyer from sending packages[].start_requires_all_creatives_approved: true on control_media_buy to a seller without media_buy.features.creative_start_policy: true. On a session negotiated at 3.3 or later, the client now checks its cached get_adcp_capabilities response (the one version negotiation already uses) and, when the seller does not declare the feature, returns a failed TaskResult before anything is sent: dispatched=False, adcp_error.code UNSUPPORTED_FEATURE, adcp_error.field packages[N].start_requires_all_creatives_approved for the first opted-in package, and adcp_error.details with feature and every opted-in field, the vocabulary the seller's own refusal uses (#1872). A client that has not discovered the seller (a pinned server_version) sends the request and lets the seller decide. false and omission always pass, and a session below 3.3 behaves as before: version conversion refuses true there. ADCPFeatureUnsupportedError gains optional field and details, which the pre-send failed result carries. The shared helpers move from adcp.server._creative_start_policy to adcp._creative_start_policy so the client does not import the server package. Closes #1876 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — buyer-side creative start policy gate (#1876), clean implementation with full test coverage.
What I checked:
- No critical/high findings (row 1 clear).
gated_paths: false(row 2 n/a).high_risk: trueonly fromsrc/adcp/server/mcp_tools.py (modified), but with zero medium-or-higher findings the modification is presumed safe — row 5 requires a medium finding, which is absent. No(deleted)reasons (row 3 clear). No(added)-only concern.- No medium findings at all (rows 4, 8 clear).
- No no-auto-approve team match (row 7 clear).
- No prior decision (row 6 n/a).
The gate is correctly placed after capability discovery in _convert_outgoing_request, version-gated by is_adcp_version_at_least(target, "3.3-beta.0"), and the additive field/details on ADCPFeatureUnsupportedError are backward compatible (no semver break). The DR-0032/0033 "Refused" pathway is covered with tests across MCP/A2A, declared/undeclared/false/omitted, 3.2 sessions, and unknown capabilities — satisfying the mandatory pathway-plus-test rule.
Falls through to row 9 → approve.
This was referenced Nov 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AdCP 3.3: buyers MUST NOT send
packages[].start_requires_all_creatives_approved: trueoncontrol_media_buyto a seller that does not declaremedia_buy.features.creative_start_policy: true. #1872 added the seller half. This PR adds the buyer half toADCPClient.On a session negotiated at 3.3 or later, the client checks its cached
get_adcp_capabilitiesresponse. That is the same cache version negotiation already fills. When the seller doesn't declare the feature, the client returns a failedTaskResultbefore anything is sent:The
code,field,details.featureanddetails.fieldsmatch the seller's refusal from #1872. The client doesn't addmutation_outcome, because that is the seller's claim;dispatched=Falsealready says nothing was sent.client.supports("creative_start_policy")answers the question before the call.Not blocked
server_versionskips discovery, so the client sends and the seller decides. A conforming seller refuses with the same error.falseand omission: always sent.trueexactly as it did before (a test pins this).How
_convert_outgoing_requestraises the typedADCPFeatureUnsupportedError(dispatched=False)right after negotiating the target version._prepare_versioned_taskturns that into the failedTaskResultdocumented indocs/cross-version-conversion.md, and that result carrieswire_response_received: falseand the idempotency key.ADCPFeatureUnsupportedError: gains optionalfieldanddetails(additive), and_prepare_versioned_taskcopies them intoadcp_error. Existing preflight errors don't set them, so their shape doesn't change.unsupported_featuresis["creative_start_policy"], the same nameclient.supports()/client.require()use.adcp.server._creative_start_policytoadcp._creative_start_policy, so the client doesn't import the server package. Both halves now live in one module.Not marked breaking
The only requests now refused are ones AdCP 3.3 forbids buyers to send. A conforming seller, including this SDK's server since #1872, refuses them with the same code, and the client only refuses when it already knows the seller lacks the feature. The public change is additive: two optional keyword arguments. There is still a
MIGRATION_v9_to_v10.mdrow because the behaviour is visible to users.TypeScript parity
adcontextprotocol/adcp-client#3649 covers the server side only. I've added a comment there noting buyer-side parity as a follow-up.
Docs
docs/media-buy-action-rights.md: new "Creative start policy" subsection on buyer behaviour, including the unknown-capabilities case.docs/adcp-3.3-sdk-inventory.md: the row now records the client half.MIGRATION_v9_to_v10.md: new row.Test plan
tests/test_control_media_buy_creative_start_policy.py:false, across several packages; the wire is never called.falseand omission: always sent.tools/call, because the SDK client would refuse first. It pins the seller's full wire error, includingmutation_outcome.test_version_client_boundaries,test_list_proposals_collections,test_control_request_intent,test_account_change_feed): 133 passed.make typecheck-allmake lintCloses #1876
🤖 Generated with Claude Code