Skip to content

feat(protocol)!: adopt the signed AdCP 3.3.0-beta.3 bundle - #1754

Merged
bokelley merged 8 commits into
nextfrom
claude/issue-1695-p1-adopt-beta3
Oct 31, 2026
Merged

bokelley merged 8 commits into
nextfrom
claude/issue-1695-p1-adopt-beta3

Conversation

@bokelley

@bokelley bokelley commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Slice P1a of the reporting-subscriptions DX plan (.context/dx-reporting-subscriptions.md §6.2, P1). It adopts and serves the signed AdCP 3.3.0-beta.3 bundle. Stacked on #1751. The engine's conformance to the beta.3 rules and the re-pinned coverage vectors are in the next PR, which is stacked on this one.

Breaking change: 3.3-beta.2 is dropped

The SDK no longer advertises or accepts AdCP 3.3-beta.2. This matches how 3.3-beta.0, 3.3-beta.1 and the 3.2 release candidates were dropped once superseded.

  • get_supported_adcp_versions() → ('3.0', '3.1', '3.2', '3.3-beta.3', '3.2-rc.7').
  • adcp_version="3.3-beta.2" or "3.3.0-beta.2" now raises ConfigurationError.
  • A peer that advertises only 3.3-beta.2 for the 3.3 line negotiates down to 3.2.
  • Adopters who pinned 3.3-beta.2 (the 10.0.0-beta.3 default) should re-pin to 3.3-beta.3 or remove the pin.

The commit is feat(protocol)!: with a BREAKING CHANGE: footer. The same footer also lists the reporting-subscription removals below.

Release pin

  • schemas/releases/3.3.0-beta.3.json records the four artifacts' sizes and sha256. They were cosign-verified (Verified OK) against release.yml@refs/heads/main.
  • The bundle sha256 is ba61919ca0c9d67874604fbbca8945c133eaceed65da1ac962f78d94d7879f00.
  • The source commit d5f8de383df13525e11721ee3d493ee76352210f comes from the Fulcio certificate (OIDs 1.3.6.1.4.1.57264.1.3 and .13). The v3.3.0-beta.3 tag (a6106149) is its Version Packages child, the same layout as beta.2.
  • I synced with scripts/sync_schemas.py --require-signed-release and ran the full regeneration chain: fix_schema_refs, bundle_schemas, the versioned stubs and bases, generate_types, consolidate_exports and generate_ergonomic_coercion. I then refreshed the public API snapshot. make validate-generated is clean.

Generate Python SDK failure in the spec repo

With beta.3 schemas, the spec repo's Generate Python SDK check failed on import with PackageRequest.targeting_overlay lost its TargetingOverlayInput | None annotation. That message came from a second failure; the first one was hidden.

  1. _reporting_subscription_preview raised AttributeError, because beta.3 renamed PriorVersion.
  2. adcp.types.__getattr__ reported that AttributeError as a missing name.
  3. The next access imported _forward_compat again, over models the first attempt had already patched.

The fix has three parts:

  • The preview module is removed (see below).
  • adcp.types.__getattr__ re-raises an AttributeError from the eager import as ImportError, so the first failure surfaces.
  • The targeting_overlay and bundled-clone patches are idempotent.

Two subprocess regression tests cover this.

Serving the reporting-subscription fields from generated types

  • adcp.types._reporting_subscription_preview is deleted. Every field it hand-modelled is generated.
  • The beta.1 and beta.2 wire projections are removed: project_offering, project_reporting_delivery, project_subscription_state, project_subscription_coverage, serves_settled_subscription_fields, and the principal readback gate.
  • One version predicate. adcp.reporting.subscriptions.serves_subscriptions(adcp_version) is true from SUBSCRIPTIONS_MIN_ADCP_VERSION = "3.3.0-beta.3". It replaces serves_settled_subscription_fields (beta.2), serves_inactive_coverage (beta.3) and the old service_subscriptions.serves_subscriptions (beta.1). It gates the capability block, get_reporting_status coverage (including status: "inactive" with deactivated_at) and the principal surface. ReportingSubscriptionEngine.subscription_coverage() no longer takes adcp_version.
  • The schema marks the reporting-dimension vocabulary x-extensible, but the generator closes it into an enum. _forward_compat reopens the five fields that carry it (the selector, the offering, the capability block and their bundled copies) to the vocabulary's x-pattern. This is a codegen limit, not a hand-modelled field. Without it, an offering advertising standard_dimensions: ["device"] would fail to parse.

Codegen for the adcp#8547 layout in beta.3

#1741 handled the composed envelope. Beta.3 also composes core/task-submitted.json (and task-working / task-input-required) into branches and centralizes task_id and message. Three regressions showed up only on the real tree:

  • Submitted arms lost status and the required task_id. CreateMediaBuySubmittedResponse(status="working") validated. The response-arm emitter now folds the composed task-state schema into each arm.
  • GetTaskStatusResponse.task_id and .status became Any. Only the envelope declares them now. A pre-generation pass copies the envelope's declaration onto a root that requires it.
  • About 40 task_id and message fields would have become TaskId / TaskMessage scalar classes. Callers would have had to wrap plain strings, and the adopter mypy --strict suite failed. References to those two scalar aliases now generate as str with the same constraints (min_length=1, max_length=2000). The classes are still generated.

The composed envelope on compact responses (CI follow-up)

The first CI run on this branch caught where #8547 changes behavior on the wire. These are fixed in follow-up commits:

  • list_products and decline_proposals now need status. Through 3.2 their compact roots forbid it, and from 3.3.0-beta.3 the composed envelope requires it. uses_task_status_envelope(tool, version) is now version-aware, and the server, the validators and the converter pass the release. A 3.3 response is stamped status: completed, and the 3.3 → 3.2 conversion still drops it for 3.2 callers. This was the multi_platform_seller storyboard failure.
  • Re-pinned tests.
    • The lattice residues are empty: every response composes both envelopes now.
    • The error-code count is 124 (CREATIVE_LIVE_UPDATE_UNSUPPORTED).
    • replayed defaults to false and accepts an explicit false.
    • The governance handlers name their decision in verdict / outcome_state, since status is the task status.
    • The round-trip corpus's check_governance response carries status.
  • Webhook results carry the task status. create_mcp_webhook_payload gives a result without status the webhook's status, and replaces a model's defaulted envelope status with it, so a failed task's result never reads completed. The TypeScript SDK made the same change.
  • Readback counts. core/reporting-subscription-state.json requires prior_versions_count, accounts_with_exceptions_count and accounts_unauthorized_count whenever their lists are present. The readback lists at most 16 prior versions and 1,000 accounts per list inline, each with its total. Paging the rest is in feat(reporting): apply the AdCP 3.3.0-beta.3 seller rules for subscriptions #1756.
  • Checked, and fine in Python. The TypeScript SDK's generator dropped status: submitted / task_id on the proposal tools' submitted branches, and generated destination_overrides items as empty objects. Neither happens here: every submitted arm keeps Literal['submitted'] and a required task_id, and DestinationOverride has its four fields.
  • Controller task_id. It narrows core/task-id.json through a one-arm allOf (maxLength: 128). The generator's merge dropped minLength: 1, so the alias now folds into the narrowing node.

Other beta.3 surface

  • comply_test_controller adds reporting_subscription_probe, with operations revoke_grant, regrant, reduce_grant, set_account_timezone and advance_time. TestControllerStore gains the hook (not implemented by default) and the dispatcher validates its parameters. SCENARIOS matches the packaged schema again.
  • Generated with no wiring needed: live creative updates, account-request money terms, flight_history, cancellation_effective_at, regranted_at, destination_overrides, the paged readback counts, reporting_subscription_page, offering available_from / withdrawn_at, and obligation stub.
  • Generated renames: PriorVersion → ReportingSubscriptionPriorVersion, AccountsWithException → ReportingSubscriptionAccountExceptions, AccountsUnauthorizedItem → ReportingSubscriptionUnauthorizedAccount. Numbered names also renumber.
  • adcp.types._generated.AllowedTask (internal) now resolves to the controller's allowed_tasks item. Both are task-name tokens. adcp.types.domains.core.AllowedTask is unchanged.

Packaging

  • The wheel and sdist ship _schemas/3.3.0-beta.3 and _compliance/3.3.0-beta.3.
  • The beta.2 cache and compliance trees stay in the repo as history and are pruned from distributions.

Tests and docs

  • The adoption test is renamed to test_adcp_3_3_0_beta_3_adoption.py. It checks the signed identity values: 4684 manifest files, 247 provenance files and 1768 schemas. It also checks that beta.0, beta.1 and beta.2 are rejected.
  • The version sweep, the 3.3 downgrade test, and the packaging and historical tests point at the new pin.
  • The reporting tests that asserted beta.1 and beta.2 projections now assert the unprojected beta.3 shape.
  • MIGRATION_v9_to_v10.md has the pin, the breaking note and rows for the new surface. docs/adcp-3.3-sdk-inventory.md has a new §13. Pin references are updated in RELEASING.md, docs/cross-version-conversion.md, docs/agent-identity-3-3-migration.md and docs/reliable-reporting-service.md.

Proof

>>> adcp.get_adcp_version()                                   # deprecated alias of the spec pin
'3.3.0-beta.3'
>>> adcp._version.resolve_adcp_version(None)                  # wire pin
'3.3-beta.3'
>>> adcp._version.get_supported_adcp_versions()
('3.0', '3.1', '3.2', '3.3-beta.3', '3.2-rc.7')
>>> negotiate_version(["3.2", "3.3-beta.3"], maximum="3.3-beta.3")
'3.3-beta.3'
>>> negotiate_version(["3.2", "3.3-beta.2"], maximum="3.3-beta.3")   # superseded beta
'3.2'
>>> ADCPClient(AgentConfig(id="a", agent_uri="https://example.com/mcp", protocol="mcp")).adcp_version
'3.3-beta.3'

Local verification

  • These pass: ruff check src/, mypy src/adcp/, the mypy --strict adopter checks, the pyright adopter checks, the type-ignore contract, the versioned stub and base checks, and make validate-generated.
  • Focused suites pass: 1,006 tests covering codegen, the version pin, the public API, the adoption test, server DX, targeting_overlay compatibility, every reporting-subscription and principal test, and lazy types. Codegen, version and historical tests add another 1,236.
  • The full suites run in CI (dispatched manually, since stacked PRs don't trigger ci.yml).

🤖 Generated with Claude Code

bokelley and others added 2 commits October 31, 2026 11:02
Bring the 3.3.0-beta.2 adoption, the adcp#8547 envelope codegen fix and the
PyPI publish pin into the reporting-subscription stack before it adopts
3.3.0-beta.3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pin src/adcp/ADCP_VERSION to 3.3.0-beta.3 from the audited, cosign-verified
release: schemas/releases/3.3.0-beta.3.json, source commit d5f8de383df1 from
the Fulcio certificate, bundle sha256 ba61919ca0c9. Resync the schema cache,
packaged compliance inputs and request-signing vectors with
--require-signed-release, then regenerate the types and the public API
snapshot. The wheel ships the 3.3.0-beta.3 schemas and compliance tree; the
beta.2 trees stay in the repository as history and leave distributions.

Serve the reporting-subscription surface from generated types. The
_reporting_subscription_preview module and the beta.1 and beta.2 wire
projections are removed, and serves_subscriptions, from
SUBSCRIPTIONS_MIN_ADCP_VERSION 3.3.0-beta.3, is the one predicate for
capabilities, coverage including status inactive with deactivated_at, and
the principal readback. The extensible reporting-dimension vocabulary is
reopened on the five generated fields that carry it.

Fix the spec repository's Generate Python SDK import failure on beta.3. The
preview module raised AttributeError on the renamed PriorVersion,
adcp.types.__getattr__ read that as a missing name, and the next access
imported _forward_compat again, so the targeting_overlay patch ran twice
and reported a misleading lost annotation. The eager import now surfaces
the first failure as ImportError, and the targeting and bundled-clone
patches are idempotent.

Generate the adcp#8547 response layout faithfully. The response-arm emitter
folds core/task-submitted.json and its siblings into each arm, so submitted
arms keep status submitted and a required task_id. get_task_status takes
the envelope's declarations for its required task_id and status instead of
Any. References to core/task-id.json and core/task-message.json generate as
constrained str, so callers keep passing plain strings.

Add the comply_test_controller reporting_subscription_probe scenario as a
TestControllerStore hook with parameter validation.

BREAKING CHANGE: the SDK no longer advertises or accepts AdCP 3.3-beta.2.
get_supported_adcp_versions is 3.0, 3.1, 3.2, 3.3-beta.3 and 3.2-rc.7; an
adcp_version pin of 3.3-beta.2 or 3.3.0-beta.2 raises ConfigurationError, and
a peer that advertises only 3.3-beta.2 for the 3.3 line negotiates down to
3.2. Re-pin to 3.3-beta.3 or omit the pin. adcp.reporting.subscriptions drops
project_offering, project_reporting_delivery, project_subscription_state,
project_subscription_coverage and serves_settled_subscription_fields, and
ReportingSubscriptionEngine.subscription_coverage no longer takes
adcp_version. The generated PriorVersion, AccountsWithException and
AccountsUnauthorizedItem are now ReportingSubscriptionPriorVersion,
ReportingSubscriptionAccountExceptions and
ReportingSubscriptionUnauthorizedAccount.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…3.0-beta.3

AdCP 3.3.0-beta.3 composes core/protocol-envelope.json on every task
response, so list_products and decline_proposals now require status. Through
3.2 their compact roots forbid it. uses_task_status_envelope takes the
release the response is shaped for, and the server, the validators and the
version converter pass it. A 3.3 response is stamped status completed, and
the 3.3 to 3.2 conversion still drops it for 3.2 callers. This fixes the
multi_platform_seller storyboard's list_products status failure.

Re-pin the tests the composed envelope moved. The task-message lattice
residues are empty, and the error-code count is 124 with
CREATIVE_LIVE_UPDATE_UNSUPPORTED. replayed defaults to false and accepts an
explicit false. The governance test handler names its decision in verdict
and outcome_state. The property-list test validates the response as served
to a 3.2.1 caller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley and others added 5 commits October 31, 2026 13:15
…orpus valid

The comply_test_controller task_id narrows core/task-id.json through a
one-arm allOf with maxLength 128. Inlining the scalar alias inside that
allOf let the generator's merge drop minLength 1, so the model accepted an
empty task_id the schema refuses. The alias's keywords now fold into the
narrowing node, so the field is str with min_length 1 and max_length 128.

The round-trip corpus's check_governance response carries status completed,
which 3.3.0-beta.3 requires on every governance response.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every task response and async status payload carries its task status from
3.3.0-beta.3 (adcp#8547), and a webhook's result is that payload. A result
without status takes the webhook's status. A model result's defaulted
envelope status is replaced with it, so a failed task's result never reads
completed. An explicit status is kept. The TypeScript SDK made the same
change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…equires

core/reporting-subscription-state.json requires prior_versions_count,
accounts_with_exceptions_count and accounts_unauthorized_count whenever their
lists are present, so a readback without them fails 3.3.0-beta.3 validation.
The registry now lists at most the 16 newest prior versions inline, with the
total. The readback lists at most 1,000 accounts inline per list, in
account_id byte order, with the total. Paging the rest follows in the engine
slice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…results

The 3.3.0-beta.3 webhook result carries its task status, as
create_mcp_webhook_payload now sets. The Postgres delivery supervisor test,
which skips without a database, asserted the bare result.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — PR #1754 adopts the signed AdCP 3.3.0-beta.3 bundle with a correctly-signaled breaking change.

The reviewer surfaced no critical, high, or medium findings. Walking the decision table top-down:

  • Row 1 (critical/high): no such findings — not triggered.
  • Row 2 (gated_paths): gated_paths is false — not triggered.
  • Row 3 (high-risk deletion): high_risk is true but every high-risk reason is (added) — new schemas/cache/3.3.0-beta.3/** files (scaffolding of the new schema bundle), no (deleted) entries — not triggered.
  • Row 4 (medium + data-loss/schema/infra): no medium findings — not triggered.
  • Row 5 (high-risk modified + medium): all high-risk matches are additions and there are no medium findings — not triggered.
  • Row 6 (sticky escalate): no prior decision — not triggered.
  • Row 7 (no-auto-approve team): no team match — not triggered.
  • Row 8 (≥3 medium): zero medium findings — not triggered.
  • Row 9: approve.

The high_risk flag fires only on newly-added schema cache files, which is normal bundle-scaffolding and not escalation-worthy per the change-kind rules. The reviewer verified the load-bearing hand-written changes: version-aware uses_task_status_envelope, idempotent _forward_compat.py patchers, the eager-import ImportError re-raise, the breaking serves_subscriptions collapse, webhook task-status stamping, and required *_count fields — all covered by tests. The breaking change carries feat(protocol)!: + a BREAKING CHANGE footer plus MIGRATION_v9_to_v10.md and a refreshed public_api_snapshot.json, satisfying the semver-signal mandate.

No blocking findings. Ready to merge.

Base automatically changed from claude/issue-1695-ts-parity to next October 31, 2026 19:35
@bokelley
bokelley merged commit 62002bd into next Oct 31, 2026
41 checks passed
@bokelley
bokelley deleted the claude/issue-1695-p1-adopt-beta3 branch October 31, 2026 19:35
bokelley added a commit that referenced this pull request Oct 31, 2026
…se's schema (#1761)

#1754 stamps status on list_products and decline_proposals from 3.3.0-beta.3
by comparing version strings. That leaves three defects for a 3.2.x peer, and a
fourth for the next 3.2.x release:

- A typed ListProductsResponse dumps its defaulted `status: completed`. A server
  serving 3.2.x sent it to a closed root that forbids it, so strict response
  validation failed on a handler that returned the typed model.
- Converting a synchronous decline_proposals response from 3.3 to 3.2 raised
  ADCPVersionConversionError: the 3.2 root permits `status` only as `submitted`,
  and the converter had no rule for the status that 3.3 requires.
- Converting up from 3.2 to 3.3 did not supply the `status` 3.3 requires, so the
  converted list_products / decline_proposals payload failed 3.3 validation.
- A 3.2.x release that composes the envelope (the backport) would still be
  treated as one that does not.

uses_task_status_envelope(tool, version) now asks the served release's own response
schema whether it composes core/protocol-envelope.json, as the TypeScript SDK does
(adcontextprotocol/adcp-client#3493). The version comparison from #1754 stays as the
answer for a release that ships no schema for the tool. The server drops only the
typed default `completed` where the served release has none for the tool (a submitted
arm keeps its status), and convert_payload supplies `completed` going up and drops
it, recorded as a loss, going down. Nothing is stripped for any other tool or release.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant