Repository navigation
test(compliance): cover the absent measurement_terms_acceptance gate for measurement_terms_accepted - #8539
Conversation
…provenance truth-of-claim (adcontextprotocol#8218) (adcontextprotocol#8405) Closes adcontextprotocol#8199. Points provenance_truth_of_claim and provenance_audit_observation at the public conformance verifier and the served fixture JPEGs, and adds depends_on [discover_verifier] to accept_consistent_claim. Backport to 3.1.x: protocol content only. The storyboards, fixtures, docs section and changeset apply cleanly. The training-agent route, CSRF exemption, JSON body cap and their tests are dropped. Production serves the verifier from the main deployment, 3.1.x is never deployed, and 3.1.x predates the json-body-limit util and the restructured CSRF exemption lists the route depends on. The 3.1.x seller's accepted_verifiers check is URL-agnostic, so the storyboards run against the 3.1.x training agent unchanged. (cherry picked from commit fbd3543) Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…1.x (adcontextprotocol#8462) GitHub runs a push-triggered workflow from the pushed branch's own copy, so adcontextprotocol#8439's fix to forward-merge-3.1.yml only takes effect once 3.1.x carries it. Take main's copy: CHANGELOG.md keeps main's file plus the missing 3.1.x release blocks, published-versions.json is unioned, and the schema discovery pointers are regenerated after every merge. The workflow checks out main and runs scripts/forward-merge-release-records.cjs from there. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…2.x (adcontextprotocol#8461) GitHub runs a push-triggered workflow from the pushed branch's own copy, so adcontextprotocol#8439's fix to forward-merge-3.2.yml only takes effect once 3.2.x carries it. Take main's copy: CHANGELOG.md keeps main's file plus the missing 3.2.x release blocks, published-versions.json is unioned, and the schema discovery pointers are regenerated after every merge. The workflow checks out main and runs scripts/forward-merge-release-records.cjs from there. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… erratum) (adcontextprotocol#8354) The AAO reference catalog tagged display_html and display_WxH_html as canonical html5. That contradicts canonical-projection-ref rule 5: these formats require one html asset (html_creative), while the html5 slot is a zip bundle. Re-tag them as display_tag with an html_creative (html) slot override, matching display_js, and publish display_320x50_html and display_300x50_html with the same mapping. Catalog data only; no schema changes. Refs adcontextprotocol#8339 Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… on 3.2.x (adcontextprotocol#8501) (adcontextprotocol#8532) * fix(schemas): reopen the proposal-flow schemas closed against DR-0019 on 3.2.x 3.2 published the proposal-flow tasks closed from their request and response roots down, and closed the containers they use. DR-0019 keeps request roots, response roots and container objects open, and allows closure only on join-key objects or row envelopes with an ext slot. DR-0036 on main records the reopening as a correction that ships in 3.3 and in a 3.2.x patch (adcontextprotocol#8501). Set additionalProperties to true at the same 24 locations as main: - the request roots of list_products, request_proposals, refine_proposals, buy_products, accept_proposal, decline_proposals and control_media_buy; - the response roots of list_products, request_proposals, refine_proposals and decline_proposals, and the refine_proposals result row; - the three branches of media-buy-commitment-response and of control-media-buy-response; - canonical-proposal, canonical-product, product-discovery-criteria, proposal-refinement, package-control and opportunity-context. Keep the rejections that closure provided by naming the keys: a root not on canonical-product forbids the legacy format_ids, and the root not of request-proposals-response now also forbids feed_version and pricing_version. Each rejects exactly what 3.2.7 rejected. Adapt the tests that assumed closure: the training agent parity test compares closure with source, the MCP projection test ignores the format_ids prohibition, and the products-only brief test also rejects a fabricated pricing_version. Add a correction note to the 3.1 to 3.2 migration guide and a patch changeset. No fields are added, and commercial-terms stays closed. * fix(schemas): name the keys closure rejected on reopened 3.2.x schemas Reopening the proposal-flow schemas dropped three rejections that tests rely on: - canonical-product rejected every field that only the legacy Product defines, because its description keeps the legacy Product graph absent. Forbid each of those 21 keys by name, including format_ids, is_custom and installments. - The error and submitted branches of media-buy-commitment-response and control-media-buy-response rejected warnings, which only the completed branch carries. Forbid warnings there by name, as create-media-buy and update-media-buy responses already do. Derive the projection test's canonical Product prohibition from the source schema, and check the dual-shape fixture by which schema rejects it rather than by closure's error text. Update the changeset and the migration note to list the named rejections.
… including the live capability fetch
Preview approval requiredThis pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit |
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — test-only PR adding coverage for the measurement_terms_accepted capability gate (#7344).
Checked:
- Single file touched:
tests/sdk-runner-capability-gates.test.cjs(+57/-0), test-only. No protocol surface (static/schemas/source/**,docs/reference/**, dist artifacts, task defs) touched, so changeset correctly omitted. - No high-risk paths,
gated_paths: false.review_decisionis REVIEW_REQUIRED but the gated-paths gate (row 2) does not apply since gated_paths is false. - No critical/high findings.
Medium findings
- tests/sdk-runner-capability-gates.test.cjs:1429 — "Live capability fetch" block gates on a pre-injected
_profile.raw_capabilities, so the runner never calls_client.getAdcpCapabilities; thedeepEqual(dispatchedTasks, ['get_adcp_capabilities'])only reflects the test's own manual call, leaving the production fetch-and-gate path uncovered.
Decision: 1 medium finding, no escalation trigger (no data-loss/schema/infra category, no high-risk modified file, no deleted sensitive file, no team gate, no prior escalation). Falls through to row 9 — approve. The uncovered-path concern is worth the author's attention but does not block.
Medium findings
- tests/sdk-runner-capability-gates.test.cjs:1429 — "Live capability fetch" block gates on injected profile, not a runner fetch
… gate is profile-resolved in the pinned SDK
|
Thanks — you are right about the mechanism, and I verified it experimentally before rewording (head c6cb3f0):
Local re-run on c6cb3f0: the renamed test passes 1/1. |
…97-62hf-6gxq (adcontextprotocol#8540) The MCP Node transport 2.0.0 (pinned by @adcp/sdk) does not bound request body size or JSON-RPC batch length (high). 2.1.1 fixes it, satisfies the pinned @modelcontextprotocol/server 2.3.1 peer, and has the same @hono/node-server dependency the existing override already covers. npm audit --omit=dev --audit-level=high: 0 vulnerabilities. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tion is a task error (adcontextprotocol#8475) * fix(webhooks): let the buyer choose the signing mode, and make the HMAC rejection a task error The buyer's webhook registration selects the signing mode again: the seller signs with the profile signing_profile names, and an authentication block asks for legacy signing. legacy_hmac_fallback is a discovery flag, and no onboarding record selects the mode. A seller that does not support legacy signing rejects the registration with the existing UNSUPPORTED_FEATURE task error. Remove the unreleased webhook_hmac_not_supported from the request-signing enum, which holds the WWW-Authenticate challenge codes. * docs(webhooks): tighten the signing-mode notes and changeset Rename the migration-guide link to the narrowing that remains, list condition (b) under Deprecations, and restore the rule that a deprecation window does not reset. Drop removed-code history from the changeset. Rewrite the security page sentences in present tense and under 26 words. * docs(webhooks): drop restated and rationale-only signing-mode text Delete the "Condition (b) is deprecated" paragraph, which restated the taxonomy row and the downgrade bullet, and fold its reason into that bullet. Delete the rejected-alternative rationale from the security page and the changeset, and the per-counterparty seller SHOULD in the HMAC to 9421 migration note, which no longer fits a buyer-selected mode. Split two long sentences. * docs(webhooks): split long signing-mode sentences and align the rejection wording Cut the reporting and artifact webhook paragraph in security.mdx and the matching changeset paragraph to one sentence. Split the long signing-mode sentences in security.mdx and the migration overview, and name the condition (b) deprecation in the overview. The push-notification-config and agent-notification-config authentication descriptions now say the rejection points error.field at the block, matching notification-config.
…and collections (adcontextprotocol#6135) (adcontextprotocol#7948) * feat(schema): an empty product roster means undisclosed, for properties and collections A non-empty publisher_properties or collections is the complete roster; an empty list means the seller does not disclose it. No new field and no partial state. publisher_properties and collections lose minItems: 1. Sellers serving a release below 3.3 do not return an empty roster. Allow lists may be accepted and run on matches; exclusion lists may be accepted if applied against the seller's full internal roster, otherwise rejected with UNSUPPORTED_FEATURE. Refs adcontextprotocol#6135 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * test(schema): an empty product collections array is an undisclosed roster, not invalid Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(schema): keep targeting-input list descriptions in step with targeting.json Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…ccount_id (adcontextprotocol#8474) * spec(accounts): make brand identity the domain and account identity the account_id A brand is its domain, plus brand_id where its brand.json lists several brands. An account is the seller-issued account_id. Countries, currency, timezone, sandbox, and operator_unit become account attributes that a settings update changes on the same account. A provisioning entry without account opens an account, and idempotency_key covers retries. A natural-key reference resolves only when exactly one account matches, otherwise ACCOUNT_AMBIGUOUS. Deprecate account.identity_updates, operator_identity reconciliation, ACCOUNT_MOVED, ACCOUNT_IDENTITY_CONFLICT, and account.stable_account_id. No wire field is removed. - Define how a natural-key filter matches: each supplied attribute equals the account's value, countries as a set and operator_unit by id. - Count an account as included for delete_missing only when an entry names it or the seller returns its account_id. - A seller that opens buyer-declared accounts through sync_accounts accepts settings-update entries keyed by account_id, and declines an attribute change it cannot make with UNSUPPORTED_FEATURE. - A provisioning entry that the account scope maps to an existing account returns that account_id. Only the 3.2 exact-or-reject rules for billing and payment_terms still apply. - currency, timezone, and sandbox are set at opening. A seller can review a countries change with status "pending_approval". - Training agent: return UNSUPPORTED_FEATURE for a settings-update attribute change and INVALID_REQUEST for an operator. Refs adcontextprotocol#8280, adcontextprotocol#8442. Supersedes adcontextprotocol#8457. * spec(accounts): align with merged request_account Bring the account_id model in line with adcontextprotocol#8293 as merged: - account_id is unconditional and returned once the account is resolved. A seller never issues one it might later change, so a pending_approval row that may resolve to a different account carries request_id instead. Drop the remaining stable_account_id-conditional wording from the request_account changeset and the stable_account_id storyboard narrative. - Provisioning-entry matching references the request_account deduplication guidance: brand and operator, attributes as filters, billing terms never filter. - ACCOUNT_AMBIGUOUS lists matches in error.details.candidates, per error-details/account-ambiguous.json, instead of available_accounts. - request_account currency and timezone are account attributes set when the account opens, not immutable. --------- Co-authored-by: Brian O'Kelley <bokelley@scope3.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ntextprotocol#8546) Fenced badge revocation and degradation failed with PostgreSQL syntax errors because the generation predicates were missing closing parentheses. Close both predicates while preserving the independent generation, grading revision, selected profile, and source-run guards. Add the nullable `history_start` column required by the installed reporting storage package without changing existing history boundaries. Include adapter-owned failure kind/HTTP status and nested PostgreSQL SQLSTATE in operational alerts, retaining isolated-execution logging and provider-payload redaction. Refresh the client's generated registration fingerprint. Run the badge mutation regressions in a private schema within the existing CI test database, and verify SDK-style reporting configuration inserts against the migrated table under rollback. The destructive GCS end-to-end suite retains its dedicated database requirement. Validation: - Correctness, security, and Node.js testing expert reviews; addressed the silently skipped badge regression suite. - Full repository pre-commit checks (root/server unit suites, static guards, and TypeScript checks). - Badge and reporting migration regressions plus the dedicated GCS end-to-end suite against Docker PostgreSQL 16. - Generated Addie tool artifact validation and inventory portability check. - Migration validation and production dependency audit. The underlying Google provider failures and intermittent reporting notification recovery failures remain unconfirmed; the added diagnostics make subsequent incidents actionable. This PR fixes the reproduced SQL and migration defects. No protocol changeset is needed.
…textprotocol#8550) * fix(compliance): run shared seller checks on auction inventory * test(compliance): cover maintenance bundles and run auction regressions in CI * fix(compliance): pin the published runner and preserve stale-cache scope * test: isolate unit checks from repository indexing * docs(sdk): align the prerelease guide with the published runner * test(sdk): advance unchanged response evidence to beta.3
…tprotocol#8554) Backport adcontextprotocol#8550 with native 3.1 account and FormatId contracts. Use the modern runner only in the private development fixture; leave production dependencies unchanged. (cherry picked from commit cdf4c7f)
…tprotocol#8555) Backport adcontextprotocol#8550 with native 3.2 account and format contracts. Use the modern runner only in the private development fixture; leave production dependencies unchanged. (cherry picked from commit cdf4c7f)
…0019 (adcontextprotocol#8531) * fix(schemas): reopen the 3.2 proposal-flow schemas closed against DR-0019 3.2 published the proposal-flow tasks closed from their request and response roots down. DR-0019 keeps response roots, capability blocks and container objects open, and allows closure only on class A or class B rows. No record explains the closure. Reopen these locations as correction (a), which DR-0036 records: - the request roots of list_products, request_proposals, refine_proposals, buy_products, accept_proposal, decline_proposals and control_media_buy; - the response roots of list_products, request_proposals, refine_proposals and decline_proposals, and the refine_proposals result row; - the three branches of media-buy-commitment-response (buy_products and accept_proposal) and of control-media-buy-response; - canonical-proposal, canonical-product, product-discovery-criteria, proposal-refinement, package-control and opportunity-context. Each change sets additionalProperties to true at the listed pointer and nothing else. commercial-terms and the purchases inside it stay closed. List the 24 locations in scripts/lint-additive-minor-corrections.json, each with property "*", release 3.3.0, and the DR-0019 sentence that required it. The baseline shrinks from 70 to 41 entries: the 29 removed entries are 3.3 fields on reopened schemas. definition_pins on commercial-terms and installments and rights on product-purchase still need their entries. drifted_pins, installment_changes and unsatisfied_installment_changes also wait for 4.0, but sit on reopened schemas. A later PR removes all of them. The adcp_version echo now validates on the control_media_buy, decline_proposals, buy_products and accept_proposal responses (adcontextprotocol#8518). The training agent derives its discovery tool schemas from source, so its parity test now compares their closure with source instead of expecting false. Add a correction note to the 3.2 to 3.3 migration guide and the versioning page, and a patch changeset. * fix(schemas): keep rejecting the legacy keys the closed proposal flow rejected Closure did two jobs on two reopened schemas. It rejected the legacy format_ids on a canonical Product, and a fabricated root feed_version on a request_proposals response. Tests assert both rejections. DR-0019 requires a loosening to name the validation that replaces what closure provided. Forbid both keys by name: a root not on canonical-product, and one more entry in the existing root not of request-proposals-response. Each rejects exactly what 3.2 rejected, so no payload changes validity against 3.2. The MCP projection test checks that the list_products output never mentions format_ids. It now ignores the prohibition itself, which forbids the key rather than exposing it. * fix(schemas): keep rejecting a fabricated pricing_version on request_proposals The version-adaptation guide says a compatibility projection never synthesizes feed_version or pricing_version. The closed 3.2 request_proposals response enforced both at its root. The reopening kept feed_version forbidden by name but not pricing_version, so a projection that fabricates a root pricing fence now validates. Add pricing_version to the root not of request-proposals-response, and assert in the products-only brief test that a fabricated pricing fence fails, next to the feed fence check. * docs(media-buy): say the 3.2 discovery requests reject unknown fields The include_collections descriptions and docs said the list_products, request_proposals and refine_proposals requests reject unknown fields. After the reopening only their 3.2 schemas do. Say so; the advice to send the flag only to sellers that declare media_buy.collection_cards stands. The pricing-models page listed the proposal among the closed schemas that carry accepted terms. The proposal is reopened; the commercial terms that carry them stay closed. * test(schemas): require the corrections list to match DR-0036's tables The check already fails on an entry that DR-0036's correction tables don't list. Now that correction (a) is listed, check the other direction too: every location in the tables is in the corrections list. Correction (b) has no rows yet; adcontextprotocol#8523 and adcontextprotocol#8524 add its rows and entries together. * fix(schemas): name the keys closure rejected on the reopened schemas Reopening the proposal-flow schemas dropped rejections that tests rely on: - canonical-product rejected every field that only the legacy Product defines, because its description keeps the legacy Product graph absent. Forbid each of those 19 keys by name, including format_ids, is_custom and product_card. - The error and submitted branches of media-buy-commitment-response and control-media-buy-response rejected warnings, which only the completed branch carries. Forbid warnings there by name, as create-media-buy and update-media-buy responses already do. Derive the projection test's canonical Product prohibition from the source schema. The include_collections test now expects every discovery request to accept unknown fields, and the three proposal-flow flags to warn that their 3.2 schemas reject it. Update the changeset and the migration note to list the named rejections. --------- Co-authored-by: Brian O'Kelley <bokelley@scope3.com>
…geset-release/3.1.x Version Packages
…auction-only-compliance # Conflicts: # package-lock.json # package.json # static/compliance/source/protocols/media-buy/scenarios/measurement_terms_rejected.yaml # static/compliance/source/specialisms/sales-broadcast-tv/index.yaml # static/compliance/source/universal/stale-response-advisory.yaml # static/schemas/source/media-buy/decline-proposals-request.json # static/schemas/source/media-buy/product-discovery-criteria.json # tests/auction-only-seller-compliance.test.cjs
…uction-only-compliance # Conflicts: # .changeset/reopen-proposal-flow-schemas.md
…auction-only-compliance # Conflicts: # CHANGELOG.md # dist/schemas/index.json # dist/schemas/latest.json # package-lock.json # package.json # server/src/creative-agent/reference-formats.json # server/src/creative-agent/task-handlers.ts # server/tests/unit/creative-agent.test.ts # static/compliance/source/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml # static/compliance/source/protocols/media-buy/scenarios/measurement_terms_rejected.yaml # static/compliance/source/universal/stale-response-advisory.yaml # static/schemas/source/index.json # tests/auction-only-seller-compliance.test.cjs # tests/sdk-runner/README.md # tests/sdk-runner/package.json # tests/sdk-runner/testing.cjs
…dcontextprotocol#8518) (adcontextprotocol#8547) * fix(schemas): reopen the 3.2 proposal-flow schemas closed against DR-0019 3.2 published the proposal-flow tasks closed from their request and response roots down. DR-0019 keeps response roots, capability blocks and container objects open, and allows closure only on class A or class B rows. No record explains the closure. Reopen these locations as correction (a), which DR-0036 records: - the request roots of list_products, request_proposals, refine_proposals, buy_products, accept_proposal, decline_proposals and control_media_buy; - the response roots of list_products, request_proposals, refine_proposals and decline_proposals, and the refine_proposals result row; - the three branches of media-buy-commitment-response (buy_products and accept_proposal) and of control-media-buy-response; - canonical-proposal, canonical-product, product-discovery-criteria, proposal-refinement, package-control and opportunity-context. Each change sets additionalProperties to true at the listed pointer and nothing else. commercial-terms and the purchases inside it stay closed. List the 24 locations in scripts/lint-additive-minor-corrections.json, each with property "*", release 3.3.0, and the DR-0019 sentence that required it. The baseline shrinks from 70 to 41 entries: the 29 removed entries are 3.3 fields on reopened schemas. definition_pins on commercial-terms and installments and rights on product-purchase still need their entries. drifted_pins, installment_changes and unsatisfied_installment_changes also wait for 4.0, but sit on reopened schemas. A later PR removes all of them. The adcp_version echo now validates on the control_media_buy, decline_proposals, buy_products and accept_proposal responses (adcontextprotocol#8518). The training agent derives its discovery tool schemas from source, so its parity test now compares their closure with source instead of expecting false. Add a correction note to the 3.2 to 3.3 migration guide and the versioning page, and a patch changeset. * fix(schemas): keep rejecting the legacy keys the closed proposal flow rejected Closure did two jobs on two reopened schemas. It rejected the legacy format_ids on a canonical Product, and a fabricated root feed_version on a request_proposals response. Tests assert both rejections. DR-0019 requires a loosening to name the validation that replaces what closure provided. Forbid both keys by name: a root not on canonical-product, and one more entry in the existing root not of request-proposals-response. Each rejects exactly what 3.2 rejected, so no payload changes validity against 3.2. The MCP projection test checks that the list_products output never mentions format_ids. It now ignores the prohibition itself, which forbids the key rather than exposing it. * fix(schemas): keep rejecting a fabricated pricing_version on request_proposals The version-adaptation guide says a compatibility projection never synthesizes feed_version or pricing_version. The closed 3.2 request_proposals response enforced both at its root. The reopening kept feed_version forbidden by name but not pricing_version, so a projection that fabricates a root pricing fence now validates. Add pricing_version to the root not of request-proposals-response, and assert in the products-only brief test that a fabricated pricing fence fails, next to the feed fence check. * docs(media-buy): say the 3.2 discovery requests reject unknown fields The include_collections descriptions and docs said the list_products, request_proposals and refine_proposals requests reject unknown fields. After the reopening only their 3.2 schemas do. Say so; the advice to send the flag only to sellers that declare media_buy.collection_cards stands. The pricing-models page listed the proposal among the closed schemas that carry accepted terms. The proposal is reopened; the commercial terms that carry them stay closed. * test(schemas): require the corrections list to match DR-0036's tables The check already fails on an entry that DR-0036's correction tables don't list. Now that correction (a) is listed, check the other direction too: every location in the tables is in the corrections list. Correction (b) has no rows yet; adcontextprotocol#8523 and adcontextprotocol#8524 add its rows and entries together. * fix(schemas): name the keys closure rejected on the reopened schemas Reopening the proposal-flow schemas dropped rejections that tests rely on: - canonical-product rejected every field that only the legacy Product defines, because its description keeps the legacy Product graph absent. Forbid each of those 19 keys by name, including format_ids, is_custom and product_card. - The error and submitted branches of media-buy-commitment-response and control-media-buy-response rejected warnings, which only the completed branch carries. Forbid warnings there by name, as create-media-buy and update-media-buy responses already do. Derive the projection test's canonical Product prohibition from the source schema. The include_collections test now expects every discovery request to accept unknown fields, and the three proposal-flow flags to warn that their 3.2 schemas reject it. Update the changeset and the migration note to list the named rejections. * build(schemas): compose the protocol envelope into closed task responses Draft-07 additionalProperties does not see properties declared through allOf, so a closed response root or oneOf/anyOf branch rejects every envelope field even when the response composes core/protocol-envelope.json. Add scripts/compose-response-envelope.cjs. For each schema that lists the protocol envelope in its root allOf, it declares every envelope property on each closed root and inline allOf/anyOf/oneOf member as a $ref to the property's declaration. The property list comes from the envelope and the schemas it composes. Only missing keys are added, so a local declaration that narrows an envelope field is kept. The documentary payload property is left out. The flat tree, the bundled tree and the MCP projection read every source document through the transform. The additive-minor check reads its source candidate the same way, and counts names declared through nested allOf, so it compares what a release publishes. * fix(schemas): compose the protocol envelope on every task response The envelope notes say task response schemas define only body fields and the envelope supplies the rest, but 47 of the 124 tool response and async status schemas did not compose core/protocol-envelope.json, and adcp_version and adcp_major_version reached responses only through a direct core/version-envelope.json composition or a hand-copied $ref. - core/protocol-envelope.json composes core/version-envelope.json, so the served version fields are envelope fields on every response. Both descriptions now say so; the version envelope is part of the payload only on a request. - Every tool response and async status schema, and the shared compact components, compose the protocol envelope through the root allOf. Response schemas no longer compose the version envelope directly. - The closed compact schemas drop their hand-copied replayed and adcp_version $refs; the schema build declares envelope fields on closed roots and branches. Narrowing declarations stay. - decline_proposals let its completed branch carry status completed, as refine_proposals and request_proposals do, instead of forbidding status. - Governance responses, async status payloads and webhook result examples carry the envelope status the envelope already required. Validation tests and doc-example checks read the published composition; the collection-copies and products-only compliance vectors, test fixtures and examples carry status; list_products adcp_version leaves the 3.3 x-added-in sweep, because the envelope supplies it; and the MCP projection cap moves to 23 MiB. * test(schemas): check envelope composition on every tool response Enumerate every tool's response and async status schemas from discoverTools and assert that each composes core/protocol-envelope.json through its root allOf, and that the built schema accepts every envelope property on every closed root and oneOf/anyOf branch. Each closed node is compiled on its own, so every branch is evaluated. A vector adds one envelope property, with a value its envelope declaration and any local narrowing accept, to a control instance without it. The built node must raise no new error; the source node must raise exactly one, its additionalProperties rejecting that property, unless the source declares the property itself. The test also checks that source schemas carry no hand-copied envelope $refs and that the build keeps narrowing declarations unchanged. Run it from npm test as test:response-envelope-composition. * test(schemas): use an opaque governance_context in the envelope vectors The envelope value table used a JWT-shaped literal for governance_context, which secret scanning reports as a hardcoded token. The schema only requires printable ASCII, so an opaque compact-JWS-shaped string grades the same property without resembling a credential. * fix(training-agent): keep task outcomes out of the envelope status from 3.1 Every task response now composes the protocol envelope, so a top-level status must be a task status. Three handlers still put a domain outcome there: check_governance (the verdict), report_plan_outcome (the outcome state) and creative_approval (the approval decision). 3.1 renamed those to verdict, outcome_state and approval_status. From 3.1 on, the handlers send status: completed with the outcome only in its renamed field. A served 3.0 release keeps the outcome in status, as the 3.0 schemas define it. The served release comes from the SDK request context on the governance tenant, from the dispatcher on the legacy route, and from the negotiated release on the creative_approval custom tool, which now declares adcp_version and adcp_major_version so the pin reaches the SDK. Stored report_plan_outcome responses stay release-neutral and replays are projected the same way. The renames landed in adcontextprotocol#4897. Fixes adcontextprotocol#8553. * build(schemas): follow nested allOf when a property moves into a composition A response that composes core/protocol-envelope.json declares adcp_version through the envelope's own allOf of core/version-envelope.json. The additive-minor check followed only one level of allOf when it decided that a property had moved rather than been removed, so an open response root that now takes adcp_version from the envelope read as removing it. compositionDeclares now resolves allOf entries the way declaredNames does. * fix(schemas): let the envelope govern replayed and message on compact responses The compact proposal-flow responses pinned replayed to const true, and the compact branches and core/compact-task-* payloads capped message at 2,000 characters. Both narrowed envelope fields without a reason. The envelope's replayed is a boolean that defaults to false, so a seller that always sends replayed: false conforms, and the envelope's message has no bound in 3.x (x-max-length-4.0 is 1024 from 4.0). The schemas rejected conforming responses. The local declarations are removed, so the envelope's declarations govern. The composition test adds edge-value vectors (replayed: false and a 2,500-character message) to every closed root and branch and to the compact task payloads, and checks that no response node pins replayed. * docs(governance): record envelope composition as DR-0036 correction (b) Correction (b) recorded the adcp_version echo on closed task responses, pointing at adcontextprotocol#8523 and adcontextprotocol#8524. adcontextprotocol#8547 supersedes both by composing the whole protocol envelope into every task response. (b) now names that change and quotes the text that already required it: the REQUIRED status and the body-fields-only note in core/protocol-envelope.json as published in 3.2.7, and the adcp_version echo in docs/reference/versioning.mdx. Every closed location that gains an envelope field is a proposal-flow response root or branch that correction (a) reopens, so (b) adds no check entries. * chore(changeset): note the compact replayed and message normalization * fix(schemas): let the envelope govern message on every response node Sixteen open async submitted roots and branches still capped message at 2,000 characters: build_creative, create_media_buy, update_media_buy, get_creative_features, get_products, get_signals, preview_creative, sync_audiences, sync_catalogs and sync_creatives. The envelope defines message once, with no bound in 3.x, so the cap rejected conforming responses. The local maxLength is removed; descriptions stay. The composition test now walks every response node, open or closed: each must accept replayed: false and a 2,500-character message, and none may narrow message length or pin replayed. A const pin on message stays allowed, as on the unavailable get_reporting_status lookup. The changeset gives the final count: replayed at 10 locations, message at 23. * build(schemas): validate responses against the built schema tree only Response validators read raw source schemas, sometimes with the envelope composition applied at runtime. A closed source response node puts status in required through the envelope allOf and rejects it through additionalProperties, so raw source accepts no conforming response, and the training agent loaded scripts/compose-response-envelope.cjs at runtime, which the production image does not ship. Every response validator now reads dist/schemas/latest, the tree the build publishes and the runtime image ships: - scripts/built-schemas.cjs builds the tree when its inputs changed (a digest stamp the build writes) and reads it with /schemas/ ids. Builds take a lock, so concurrent test processes and npm run build:schemas build one at a time. - The training agent's validator (source-schema.ts) compiles the built tree; the second, composing validator is gone, and so is the injected status: a response without status now fails. The server unit setup builds the tree first. - The storyboard sample_response lint and the tests that validated response instances against source now read the built tree. * fix(schemas): declare envelope fields only on the envelope Response schemas still restated envelope fields inline: task_id at 23 nodes, message at 21, replayed at 11, governance_context on check_governance, status and task_id on get_task_status, and adcp_version, adcp_major_version, context_id, timestamp and adcp_error on the get_reporting_status failure branches. Some copies disagreed with the envelope. They are removed, so the envelope's declarations govern. A local declaration stays only where it narrows: a status const or enum, a const pin (the fixed message and adcp_error of an unavailable reporting lookup), or context as a $ref to core/context.json. Two constraints carried only by copies move to the envelope: minLength 1 on task_id, and the untrusted-input handling of message. The envelope's replayed text now says that an absent replayed means false and that a response may carry replayed: false. The root status enums of decline_proposals, request_proposals and refine_proposals are removed; their branch consts discriminate. The composition test flags any other local redeclaration, not only $ref copies. Its single-fault vectors now start from a control that passes the built node: the node's required fields, with envelope values its declarations accept. An addition vector adds exactly one error on source and none on built; a removal vector of a required envelope field adds exactly the missing-required error on built and removes exactly the field's additionalProperties error on source. The test refuses to run below Node 24 and checks that every vector ran. The changeset gives the corrected count: replayed was pinned at 12 locations, so the normalization covers 35. * refactor(training-agent): one table for outcomes renamed out of status Three handlers each decided whether to put their outcome in status for a served 3.0, and acquire_rights did not, so a served 3.0 got status: completed where the 3.0 schema requires acquired, pending_approval or rejected. served-outcome-status.ts holds the one table of the 3.1 renames found by comparing the 3.0.28 and 3.1.29 schemas: acquire_rights rights_status, check_governance verdict, creative_approval approval_status and report_plan_outcome outcome_state. (The create_media_buy and update_media_buy media_buy_status split is already handled by the SDK for a served 3.0.) Handlers emit the 3.1 shape; the transport adapters (the governance and brand platforms, custom tools and the legacy dispatcher) project it onto a served 3.0 before the SDK shapes the response. Caches keep the release-neutral result. Custom tools now always declare adcp_version and adcp_major_version and forward the served release to their handlers, instead of the per-tool opt-in. servesOutcomeInEnvelopeStatus uses the shared parseAdcpReleaseVersion, moved to types.ts. The smoke test adds report_plan_outcome at a 3.0 pin with its replay, and acquire_rights at the current release and at 3.0. * docs: carry status in list examples and complete the envelope field list The list_products and list_proposals response examples now carry status, which every task response requires. The MCP guide's envelope field list adds message, push_notification_config, adcp_version and adcp_major_version. The versioning page no longer says adcp_version rides on the payload: it is an envelope field on a response. * fix(training-agent): project renamed outcomes on the MCP dispatcher too The legacy route has a second dispatcher, the in-process MCP server, that the outcome table did not reach, so an unpinned check_governance (served 3.0) answered status: completed. It now projects fresh and replayed results the same way. A served 3.0 response keeps the 3.1 field next to status: the 3.0 schemas are open and in-process readers use it. * fix(schemas): open the remaining response roots and drop the envelope build step Response schemas compose core/protocol-envelope.json through their root allOf. A closed response root or status branch rejects the fields that allOf composes, so the build used to merge the envelope's properties into each closed node, which made source and published schemas differ. DR-0019 keeps response roots open, so the closed ones open instead: - list_collections, list_proposals, get_media_buy_issue and report_media_buy_issue, new in 3.3; - the get_reporting_status failure branches, experimental in 3.2.7. The 3.2 proposal-flow response roots and branches are reopened by DR-0036 correction (a) (adcontextprotocol#8531). Where closure carried a branch exclusion, the exclusion is now explicit: a found or recorded issue carries no errors and a failed one no issue, a failed reporting read carries no ledger result, and list_collections carries no products. Closed value objects and rows inside response bodies stay closed. scripts/compose-response-envelope.cjs and its callers in the build, the MCP projection and the additive-minor lint are removed; the lint keeps following nested allOf. Source and published schemas agree with no generator, and validators keep reading the one published tree. The composition test now requires that no response root or status branch is closed, and runs acceptance vectors on every response node: a control that passes the node conjoined with the envelope, and the control plus each envelope field, including replayed: false and a 2,500-character message. It keeps the inline-redeclaration rule. The task_id minLength that the compact copies carried is dropped rather than moved: on the envelope it would tighten every 3.2 response, which the additive-minor guard rejects against 3.2.7. DR-0036 correction (b) now records the contract reason only, with no check entries. * test(schemas): restate why the MCP projection cap is 23 MiB Without the build-time merge into closed nodes, the projection's growth comes from every response bundling the envelope: 23,426,967 bytes, still over 22 MiB. * build(schemas): record bound-tightening corrections in the additive-minor check DR-0036 corrections could only let a closed schema accept more. A schema can also contradict its text the other way, by accepting a value the published text already excludes, such as an empty identifier. A correction entry may now name a bound and its value instead of a property ({file, pointer, bound, value}). The check accepts exactly that bound at that location from the entry's release on, requires the candidate to carry it there (through a $ref to a shared definition), and requires the entry in the decision record's tables like any other correction. Any other tightening, or another value, still fails. Tests cover a recorded tightening, an unrecorded one, a different value or bound next to a recorded one, an unapplied entry, and an entry the record does not list. * fix(compliance): resolve context_outputs paths through a root's allOf and arms together The context_outputs path lint read only the first of oneOf, anyOf and allOf on a node, and when expected_arm selected a branch it dropped the root's allOf. A response root composes the protocol envelope through allOf and lists its arms in oneOf, so a capture of task_id from a submitted response stopped resolving once the arms no longer restated task_id. The lint now walks all three keywords and checks a selected arm together with the root's allOf. * fix(schemas): give task_id one non-empty definition, required on submitted through the envelope A task_id is the handle a caller polls and a webhook references, yet most task_id declarations accepted an empty string, and adcontextprotocol#8547 had dropped the minLength: 1 that 3.2.7 published on core/compact-task-submitted.json. - core/task-id.json is the one definition: type string, minLength 1, x-entity task, x-max-length-4.0 128. Every task_id declaration refers to it: the protocol envelope, tasks/get and get_task_status requests and responses, the tasks/list and list_tasks responses and task_ids filters, the MCP webhook payload, and the compliance controller's request and response, which keep their own maxLength 128. - The envelope requires task_id when status is submitted (if/then in its allOf). The 19 submitted roots and branches no longer list task_id themselves; completed branches keep not: {required: [task_id]}. - DR-0036 records the bound as correction (c), "task_id identifies a task", at the nine locations the additive-minor check reports, with matching entries in scripts/lint-additive-minor-corrections.json. The baseline is not used. The composition test checks that task_id: "" adds exactly its minLength error on every response node and on every request that carries a task_id, and that a submitted response without task_id fails on the envelope rule alone. Structural tests and the MCP schema analysis counts follow the shared definition. * docs(schemas): describe open task response roots in the envelope notes The envelope notes still described the deleted build step that merged envelope fields into closed schemas, and allowed any local narrowing of an envelope field. Response roots and status branches are now open, and a task schema restates no envelope field other than a status discriminator, an identical context reference or a fixed-value pin. * fix(schemas): require task_id on submitted responses through core/task-submitted.json The envelope required task_id through an if/then keyed on status. Code generators ignore conditionals, so generated types left task_id optional even after a caller narrowed on status: submitted. core/task-submitted.json is now the one place the rule lives: status const submitted and a required task_id (core/task-id.json). The envelope loses its if/then; its task_id description and notes point at the new schema. Every submitted body and branch composes it through allOf: core/compact-task-submitted.json, the eight *-async-response-submitted.json bodies that do not go through it, and the ten inline submitted oneOf branches. They no longer restate status: submitted or a required list, except control_media_buy and the buy_products/accept_proposal commitment, whose OpenAPI discriminator on status needs the local const. Completed branches keep their not: {required: [task_id]} exclusions. The oneOf audit fuses a variant's allOf members before classifying it, as code generators do, so the submitted branches stay discriminated; its baseline is refreshed with no new entries (get_content_standards improves from dangerous to narrowable). The composition test requires every submitted body and branch to compose core/task-submitted.json, forbids an if/then that keys task_id on status, and checks that a submitted response without task_id fails on core/task-submitted.json's required alone. DR-0036 correction (c) now names core/task-id.json (non-empty) and core/task-submitted.json (required on submitted). * build(schemas): compare composed required sets in the additive-minor check required-added compared only a node's own required list, so a name required through a new allOf entry was invisible. Composing the protocol envelope made status required on 35 responses and async status payloads while the check reported 0 new findings. The check now compares the composed required set, a node's own names plus those of its allOf entries through $refs into composed documents, against the base's composed set. A correction may name a required name ({file, pointer, required}); the check accepts exactly that name at that location and requires the candidate's composed set to contain it. A regression test covers a name required through a new allOf entry, a recorded correction, an unrecorded name next to it, and an unapplied entry. DR-0036 correction (b) records the 35 status additions, quoting 3.2.7's "The status field is REQUIRED on every task response envelope", with matching entries in scripts/lint-additive-minor-corrections.json. The baseline is not used. * fix(schemas): pin status on working and input-required payloads Add core/task-working.json and core/task-input-required.json, mirroring core/task-submitted.json. Every working and input-required async body, and the compact working and input-required responses, compose the schema for their status through allOf, so a payload accepts only its own status. The composition test requires every status body to compose its core/task-<status>.json. The MCP webhook example that carried a create_media_buy result in a working payload now carries a complete result. * fix(schemas): bound task-level message at 2,000 in core/task-message.json Give a task-level message one definition, core/task-message.json (type string, maxLength 2000, x-max-length-4.0 1024, untrusted input to the receiver). The protocol envelope, the MCP webhook payload and the response wrapper refer to it, so the bound does not depend on the delivery path. A seller with longer text truncates it rather than fail the task. No task schema restates the bound. DR-0036 records the change as correction (d), at the 2,000 that 3.2.7 published on the compact and submitted responses, with one check entry per location the additive-minor check flags. The composition test rejects a 2,001-character message on every response node and on both other carriers with exactly the maxLength error, and accepts 2,000. * fix(schemas): drop the 4.0 annotation from the enforced message bound core/task-message.json enforces maxLength 2000, so an x-max-length-4.0 of 1024 annotated a limit below the keyword that enforces it. The annotation is for fields without an enforced bound; remove it and the 4.0 wording from the definition, DR-0036 (d) and the changeset. Any 4.0 tightening of message is a separate decision. The capabilities worst case grows from 91,262,597 to 91,266,501 bytes (+3,904). The capabilities response composes the envelope, whose message used to count at the 4.0 annotation of 1,024 characters and now counts at the enforced 2,000: 976 more characters of free text at four bytes each. security.mdx and the pinned figure in tests/capabilities-structural-bounds.test.cjs move with it. * chore(deps): bump @adcp/sdk to 17.0.0-beta.6 17.0.0-beta.6 carries adcp-client#3493: the SDK no longer strips status from the compact responses on a fixed list. It keeps status where the served release's response schema composes the protocol envelope and omits it where that release's schema has none. The SDK now packages 3.3.0-beta.1 and no longer 3.3.0-beta.0, which the creative 3.3 beta sandbox serves. The training agent retains it from the committed dist/schemas bundle, as it does 3.2 beta.6 and the last 3.2 RC, and the sandbox route registers the retained bundles before it handles a request. The tests that build the sandbox or read the 3.3 beta.0 enums directly register them first. tenant-smoke's list_products case is served at 3.2, whose published list_products schema is closed and has no status, so the SDK omits it. The case grades the response against the newest published 3.2 schema, the release it was served at, rather than the 3.3 source. The SDK page states the installed prerelease and its 3.3.0-beta.1 pin. * fix(training-agent): register the built schemas under the candidate release @adcp/sdk decides whether list_products and decline_proposals keep the envelope status from the served release's published response schema (adcp-client#3493). Its pinned 3.3.0-beta.1 bundle predates adcontextprotocol#8547, so a candidate run served 3.3-beta.1 without status and failed the schemas it grades against. At startup, a trusted candidate process (NODE_ENV=test and candidate mode, see candidate-version.ts) registers the built dist/schemas/latest tree under the candidate's release key, when that tree was built for it. Production and ordinary processes keep the SDK's published pin. The SDK's ESM status rule reads schemas through its CommonJS schema loader, a separate module instance, so the tree is registered with both instances; the storyboard runner's scoped root does not reach that instance either. Remove the registration once the SDK pins a protocol release that contains adcontextprotocol#8547. * test(sdk-conformance): record the reviewed findings against @adcp/sdk 17.0.0-beta.6 The probe run with 17.0.0-beta.6 matches the reviewed set except for the recorded TypeScript SDK version: 125 cases, one with findings. The one TypeScript finding is unchanged. At the pinned 3.2.1 GA artifact, list_products carries adcp_version, which that release's closed response schema forbids. Neither adcp-client#3493 nor this PR clears it: the SDK still sends no status at a served 3.2, and the artifact is immutable. The Python and Go entries are unchanged. The README names the current SDK and its 3.3.0-beta.1 pin. --------- Co-authored-by: Brian O'Kelley <bokelley@scope3.com>
…dcontextprotocol#8606) * docs(governance): ratify DR-0027 to DR-0030 (WG Session C consent) Objections closed 2026-10-30 with none raised. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(governance): drop stale pending decided_by lines Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ard-merge/3.1.x-3.1.31 chore(release): forward-merge AdCP 3.1.31 and documentation
Co-authored-by: aao-release-bot[bot] <280565558+aao-release-bot[bot]@users.noreply.github.com> Co-authored-by: Brian O'Kelley <bokelley@scope3.com>
… grant (adcontextprotocol#8607) sync_reporting_receipts and sync_reporting_status no longer accept receipts and statuses after the caller loses authority. A seller that wants reconciliation to finish after a relationship ends keeps a reduced grant (reconciliation tasks only) instead of revoking; coverage continues while it lists get_reporting_status. Owner decision. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ental) (adcontextprotocol#8488) * feat(creative): live creative updates with staged activation (experimental) A buyer changes a creative that is serving by syncing it again under the same creative_id. Today the spec refuses updates to creatives on active packages with INVALID_STATE and tells buyers to pause the package, which does not work on ad servers where a trafficked creative is fixed, and the documented zero-gap path (new creative_id, swap after approval) breaks on sellers that review only in package context. - creative.live_updates (experimental): per change kind (click_through, trackers, asset_content, format) the seller declares uninterrupted, staged (prior approved version serves until the change is approved and promoted; on_rejection keeps_prior | stops), or paused_for_review. The capability is a floor; list_creatives returns exact per-creative live_updates; dry_run returns the exact outcome. Name and tags are always accepted. Live = approved creative assigned to a non-canceled, non-ended package on a non-terminal buy, paused or not. - staged_update readback on sync_creatives and list_creatives; review is one decision across assignments; other transitions discard it. Folds in the core of staged activation (adcontextprotocol#6780). - CREATIVE_LIVE_UPDATE_UNSUPPORTED (+ error details) for declaring sellers; non-declaring sellers keep INVALID_STATE. - creative.status_changed carries staged_update.outcome and permits approved -> approved for staged outcomes. - force_creative_status resolves a pending staged update. - Training agent /sales implements the surface; storyboards creative_live_updates and creative_live_update_refusal exercise it. Refs adcontextprotocol#8404, adcontextprotocol#6780 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(creative): released schema alias in live update example; record media-buy context growth Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(creative): report staged outcomes on a new event, keep the original rule for non-declaring sellers, drop per-creative readback Addresses review on adcontextprotocol#8488. - creative.status_changed is unchanged. Staged outcomes ride on the new experimental creative.staged_update_changed notification type, fired only to subscribers that register it, so an older buyer never receives an approved -> approved fire its closed validator rejects. - Non-declaring sellers keep the original "cannot be updated" rule and INVALID_STATE; only sellers that declare creative.live_updates get the new behavior. - list_creatives no longer returns a per-creative live_updates policy or lists it in fields; the capability is the general floor and sync_creatives dry_run answers for one change. - Training agent gates live updates to the 3.3 wire, emits the new event, and the storyboard drops the readback phase. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(creative): bound live_updates.in_place and repin the capabilities worst case The capabilities structural-bounds test (adcontextprotocol#8198) requires every array in the response to carry maxItems. in_place lists at most one entry per change kind, so it holds at most 4. The pinned worst-case figure and open-position count move with the new capability block. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * docs(creative): state the edit-or-swap line, and that dynamic creatives are out of scope Addresses pkras's review on adcontextprotocol#8488: say where an in-place edit ends and a swap begins, say explicitly that dynamic (DCO) creatives are out of scope for the experiment, and keep audience, rotation, and weight out of the change kinds. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dcontextprotocol#8613) subscription_coverage[].status gains inactive with a required deactivated_at; an inactive subscription is listed only while it has retained obligations for the account. Before, a deactivated subscription still in scope could only read as active. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nd reference model (adcontextprotocol#8616) * test(compliance): canonical reporting-subscription coverage vectors and reference model Adds the SDK-independent conformance foundation for AdCP 3.3 reporting-subscription coverage (experimental media_buy.reporting_subscriptions): - static/compliance/source/test-vectors/reporting-subscriptions/: 113 cases with case.schema.json, pinned to IANA tzdata 2026f. 81 are ported from the adcp-client#3505 parity table and re-derived from the spec text. 32 are new: midnight DST in Santiago, Havana, and Cairo; Lord Howe's 30-minute DST; Kathmandu sub-daily; month-end clamps; P7D, P1W, and P1Y; compound changes at one instant; four-grant chains; max_accounts per offering; fractional seconds and offsets; empty flights; the confirmation period. - scripts/reference/reporting-coverage/: a brute-force reference model that enumerates every period and intersects one predicate per clause. Its time zone evaluator reads the pinned zic source itself and is checked against zic output; it uses no ICU, Intl, or zoneinfo. - tests: the model reproduces every vector; each case's wire objects validate against the AdCP schemas. Seeded property tests cover exception monotonicity, nothing owed twice, cutover stretch bounds, prefix stability, no retroactive extension, and calendar invariants. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(compliance): read tzdata.json without a check-then-read race Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(compliance): drop an unused loop binding in the coverage model Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(compliance): freeze reporting-subscription error and reason codes Adds error-codes.json (SDK error codes with recovery, dispatched, and fix) and reason-codes.json (why a period is or isn't owed, with the fixed rule order) beside the coverage vectors. The case schema carries both vocabularies, and expected_error now gives a frozen COVERAGE_INPUT_* code and detail. Tests check the vocabularies against the case schema, the wire exclusion and gap reasons, and every error the reference model raises. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the Version Packages PR (adcontextprotocol#8623) * fix(release): generation-only pushes defer storyboard verification to the Version Packages PR On main, an ordinary push only regenerates the Draft Version Packages PR, which publishes nothing. verify-release still ran the ~2h storyboard matrix first, and generation (which rightly requires exact current main) was then staled by the next unrelated merge, so beta.3 could not generate while main was busy. Skip the matrix for a main push that commits no release artifacts (generation_only). The Version Packages PR gets its own storyboard run in PR CI, and publication still requires that exact-head run (verified-merge) or full verification at the release merge. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(release): enforce the generation-only invariant across jobs (security review) Classify under set -e so a read failure emits no output; export generation_only and make the release job refuse to publish a push that commits release artifacts while classified generation_only. Correct the docs: the Version Packages PR's storyboards run once it is ready, main coverage is path-filtered, and generation signs candidates before any storyboard run. Tests for other-version artifacts and an unreadable package.json. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dcontextprotocol#8495) media_buy_seller now sends a distinct context.buyer_ref on each package of its create_media_buy and requires check_buy_status to return each package's own product_id and context, in any order. get_media_buys already makes this a MUST; only a capability-gated scenario checked it. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dcontextprotocol#8634) npm audit --omit=dev --audit-level=high now fails Build Check on every PR: follow-redirects 1.16.0-1.16.1 drops cross-realm sensitiveHeaders and can forward credentials across redirects (high). 1.16.2 fixes it. Lockfile-only. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the coverage vectors (adcontextprotocol#8622) Rulings on ambiguities the canonical coverage vectors surfaced (adcontextprotocol#8616): the local day start is always a boundary (PT24H equals P1D and days nest in sub-daily schedules where midnight does not exist); Lord Howe's 90-minute hour stands; E never precedes the outgoing side's own start; reactivation resumes after the last period owed before deactivation; UTC aliases are exactly UTC and Etc/UTC; billing_cycle needs a date component; P7D weeks start on Thursday; status_retention_days counts 24-hour days. The reference model and vectors pin each. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-valid bodies (adcontextprotocol#8572) Five routed vectors still carried the {"plan_id":"plan_001"} body adcontextprotocol#7567 reported, so grading them depended on whether a verifier validates the request before the signature. - profile-3.2/positive/001, profile-3.2/negative/001 and profile-3.2/negative/002 are hand-authored and skipped by the profile-3.2 generator, so they never received adcontextprotocol#7580's bodies. Each now carries the create_media_buy body its generated siblings use, under its own idempotency_key, with Content-Digest and signature re-derived from the test key. negative/001 keeps a cryptographically valid signature spelled in Base64URL, and negative/002 a signature valid for the canonical host, so each still has exactly one fault. - positive/002 and negative/018 are restored to the bodies and signatures adcontextprotocol#7580 gave them; adcontextprotocol#7581 merged after adcontextprotocol#7580 from an older base and put the stub versions back. - The check that every routed vector carries an operation-valid body, which adcontextprotocol#7581 also removed, is restored and now covers profile-3.2/ as well. Fixes adcontextprotocol#8571. Co-authored-by: Brian O'Kelley <bokelley@scope3.com>
…ery stop (adcontextprotocol#8617) * feat(reporting): billing stubs at cutovers and the cancellation delivery stop Billing feeds no longer leave an unowed stretch at a cutover: when the incoming billing schedule has no boundary at E, [E, first incoming boundary) is owed as one stub obligation (stub: true). This covers account timezone changes on billing offerings. The coverage stop time uses the new cancellation.effective_at, the delivery stop, instead of the instant the cancellation was recorded. A subscription seller does not deliver outside a buy's flight; make-goods extend end_time first or go on a subsequent buy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(reporting): move the delivery stop out of the closed cancellation object cancellation has additionalProperties: false, so adding effective_at inside it is non-additive (3.2 validators would reject 3.3 responses). The delivery stop becomes the media-buy-level sibling cancellation_effective_at instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(reporting): exact empty-stretch condition; billing stubs in the coverage vectors The cutover stretch is empty exactly when E is also an incoming boundary, which moving to a finer schedule guarantees and moving to a coarser one does not. The reference model emits billing stub rows (stub: true), and the billing-cutover-owes-a-stub vector pins a UTC-day to New York-day billing cutover. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(reporting): account-level billing configurations get stubs and delivery within the flight Owner decision: the billing stub at cutovers and the delivery-within-flight rule extend to sync_accounts reporting_delivery_configs billing generations (x-adcp-validation.billing_continuity), so subscription and account-level billing behave the same. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(reporting): reference model reads cancellation_effective_at The coverage vectors and reference model now take the delivery stop from cancellation_effective_at when present, and a new case pins delivery through a cancellation notice period. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aging, in-place membership (adcontextprotocol#8619) * feat(reporting): deterministic admission and resolution, overrides, paging, in-place membership Makes normative what the TS and Python SDKs implement for experimental reporting subscriptions: sticky max_accounts admission ordered by authorized_at then account_id byte order (accounts without buys count), per-package resolution fixed per (buy, version) until withdrawal, and billing subscriptions on match exact with dimensions. For scale it adds destination_overrides, bounded inline readback with counts and a paged get_principal reporting_subscription_page read, and in-place membership edits recorded as excluded_by_buyer intervals. Several pacing or analytics subscriptions may now apply to one account and purpose; billing stays one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(reporting): partial grants keep coverage; delivered rows declare account_id Coverage depends only on get_reporting_status in allowed_tasks. A grant without sync_reporting_receipts or sync_reporting_status keeps the account covered, and the seller refuses the missing task with SCOPE_INSUFFICIENT rather than as an unknown identifier. A seller advertising subscriptions publishes every managed-delivery offering with a row schema that requires account_id and keys on it, as both SDKs already assume. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(reporting): pin sticky resolution in the coverage vectors; a buy owes its packages' union The reference model now resolves each (buy, version) pair once, at the later of accepted_at and confirmed_at, and re-resolves only when the resolved offering is withdrawn. resolution-change-is-a-cutover becomes a-newly-available-offering-never-re-resolves, and withdrawal-of-the-resolved-offering-is-a-cutover is new. The selection rule says a buy owes every offering any of its packages resolves, so it can owe two even under exact or finest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lity on the wire (adcontextprotocol#8618) * feat(reporting): put flight history, re-grants, and offering availability on the wire Coverage of experimental reporting subscriptions is meant to be computable by the buyer without asking. This adds the inputs that were missing: authorization.regranted_at; flight_history listing every retroactive flight edit with the flight in force before it; a per-package reporting_delivery_offering_ids snapshot on get_media_buys; and available_from / withdrawn_at on offerings. It also clarifies delivery_config_generations and consumer-status chains when one version resolves several offerings (offering_id), next_expected_at for owed sets, and when an owed set is final (period end before ledger_as_of). The capabilities worst case is repinned for the two offering timestamps. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(reporting): coverage vectors use flight_history and published offering availability With flight_history and available_from/withdrawn_at on the wire, the vectors need no buyer-retained input: prior_flights becomes flight_history, the reference model reads it, non_wire_inputs is removed, and the vector test validates offering availability and flight history against the schemas. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(reporting): bound flight_history by retention and refuse edits past 64 entries Ladon finding: maxItems 64 contradicted 'list every retroactive edit'. Entries older than status_retention_days may be dropped, since they govern only periods that ended before them; past 64 retained entries a seller refuses a retroactive edit with INVALID_STATE, and applies an edit it cannot refuse from the instant it records it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…timezone changes (adcontextprotocol#8620) * feat(accounts): money terms on resolved account requests; consistent timezone changes Account requests (experimental): matched and created resolutions must carry the target account with its money terms; an updated request that changed billing, billing_entity, or payment_terms carries effective_at; a change request that resolves as a separate account lists not_carried_over. currency and timezone change only where account.requests.changeable_attributes lists them, so request_account and sync_principal docs now agree. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(accounts): the created walkthrough record carries account with its money terms Ladon finding: the request_account walkthrough resolved as created without the now-required account. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dles (adcontextprotocol#8636) The Validate schema PR bundle workflow only runs for PRs to main, whose bundles are AdCP 3.3 candidates. The Python SDK's default branch is the 9.x line for AdCP 3.2; 3.3 bundles generate only on its next branch (SDK 10). Check out next explicitly. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r grants, timezone, and clock (adcontextprotocol#8621) Adds the experimental comply_test_controller scenario reporting_subscription_probe (media_buy.reporting_subscriptions) with revoke_grant, regrant, reduce_grant, set_account_timezone, and advance_time, plus the allowed_tasks and timezone params. Storyboards can then test reporting-subscription coverage across grants, wind-downs, and timezone cutovers against a remote seller on one virtual clock. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(reference): curated 3.3.0-beta.3 notes What's new in 3.3 gains a "New in beta.3" section: account change outcomes and opt-in, money terms on resolved requests, revocation meaning no access with the reduced-grant wind-down and partial grants, and the reporting subscription convergence (owed confirmation period, inactive coverage, billing stubs, wire data, determinism, scale, period arithmetic, vectors, and the controller probe), plus live creative updates. The adopter-attention table and the beta program's merged-so-far columns are updated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: empty changeset for the beta.3 notes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: aao-release-bot[bot] <280565558+aao-release-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated paths touched without required approval.
This PR modifies files under .github/workflows/**, which are under a hard, deterministic approval gate (gated_paths: true), and review_decision is REVIEW_REQUIRED (not APPROVED). Per decision rule row 2, the outcome must be escalate regardless of how clean the diff is.
Escalation reasons
- Modifies
.github/workflows/release-docs.yml,.github/workflows/release.yml,.github/workflows/training-agent-storyboards.yml, and.github/workflows/validate-schema-bundle.yml— gated workflow paths requiring human/CODEOWNERS review. Gate is not satisfied becausereview_decisionisREVIEW_REQUIRED.
Context
No new reviewer findings this pass. The authored delta since the prior approve (head 3878fc3) is empty — the 99 new commits are forward-merges of already-landed main; the PR's own contribution (tests/sdk-runner-capability-gates.test.cjs) is unchanged. The prior Medium (sdk-runner-capability-gates.test.cjs:1429 — gate test drives an injected profile, not a live runner fetch) stands, non-blocking. Despite the clean/unchanged authored diff, the gated-paths gate is unsatisfied and takes precedence over the prior approve.
Medium findings
- tests/sdk-runner-capability-gates.test.cjs:1429 — "Live capability fetch" block gates on injected profile, not a runner fetch (non-blocking)
Why human review
- Modifies .github/workflows/release-docs.yml, release.yml, training-agent-storyboards.yml, and validate-schema-bundle.yml — gated workflow paths; review_decision is REVIEW_REQUIRED, so human/CODEOWNERS approval is required (decision rule row 2).
- This PR touches a path under a hard, non-overridable approval gate (.github/workflows/release-docs.yml (modified) matches
.github/workflows/**; .github/workflows/release.yml (modified) matches.github/workflows/**; .github/workflows/training-agent-storyboards.yml (modified) matches.github/workflows/**; .github/workflows/validate-schema-bundle.yml (modified) matches.github/workflows/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
Refs #7344. Add coverage for the absent
measurement_terms_acceptancedeclaration when evaluating themeasurement_terms_acceptedstoryboard gate.The test covers both an injected profile with
media_buy: {}and a wire-shaped capabilities response payload without that feature. It checks phase/stepcapability_unsupportedskips and guards against dispatching dependentget_productsorcreate_media_buytasks.The response-payload case obtains the fixture response explicitly, then supplies it through
_profile.raw_capabilities. It tests gate evaluation. Production capability discovery and fetch-and-gate wiring remain separate work under #7344.Validation on current
mainwith the patch, in Docker using the repository's pinned@adcp/sdk17.0.0-beta.8:sdk-runner-capability-gates.test.cjs: 63 passed, zero failures or skips.The branch includes a normal merge with current
main; fresh CI and final-head review remain required before merging.