Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/wg/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ Every desk enforces these regardless of surface:
policy-wide decision, never a per-variant edit ([DR-0009]).
- Graded conformance assertions require a normative basis — a spec MUST or a
schema `required[]` field, never scenario prose ([DR-0001]).
- Minor releases are non-breaking and backward compatible: every change
targeting a minor states its pathway to the previous minor of the same major
(droppable, moves, refused, or identity-bearing), on 3.x now and on 4.x from
4.1 ([DR-0033]). SDKs translate between
releases and refuse lossy mutations rather than sending a different meaning
([DR-0032]).
- When the schema-literal reading and shipping SDK behavior diverge on wire
shape, the SDK is canon; codify, don't migrate ([DR-0008]). Wire shape only —
not semantic contracts.
Expand Down
5 changes: 5 additions & 0 deletions .changeset/adcp-major-version-removal-reminder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": patch
---

Docs: restate in the 3.3 release notes and the 3.2 → 3.3 migration guide that `adcp_major_version` and `adcp.major_versions`, deprecated since 3.1, are removed in 4.0 (DR-0035). Buyers send `adcp_version` on every request and keep the integer mirror through 3.x; in-house clients that send only the integer should switch now.
21 changes: 21 additions & 0 deletions LADON.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,27 @@ algorithms) change. A clarification is patch-eligible only when the prior spec
was demonstrably ambiguous *and* any conformant 3.1.0 implementation would
already satisfy the new MUST. "It's just docs" does not apply to normative docs.

### Mandatory: every minor-release change has a pathway back

Per DR-0033, a PR that changes `static/schemas/source/**` or normative docs for
a minor release (3.x now, and each later major's own minors from 4.1 onward)
must state, for each new or changed field, enum value, task, union variant,
requiredness or semantic rule, how it maps to the previous minor release of the
same major:

- droppable (and, for a mutating request, whether dropping changes what the
seller commits to);
- moves (with the mapping);
- refused (with the error); or
- identity-bearing (Breaking, with the reduction and migration rule from
DR-0032).

Look in the PR body, the changeset, or the docs migration page. A minor-release
change with no stated pathway is `high`. A stated pathway with no cross-version
conformance case and no tracked follow-up is `medium`. A mutation-side field
declared "droppable" whose loss would change spend, targeting, caps or account
identity is `high`, because it is a refusal mislabeled as droppable.

### Mandatory: cite governance precedent

When a PR decides a question of protocol policy or convention rather than
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/migration/3-2-to-3-3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Everything else is an optional declaration or a clarification. Agent identity (D
| 29 | Creative agents that advertise `creative.supports_refinement` | Retain each leaf's compliance inputs for as long as the leaf, apply them to every refinement, and never treat a refined leaf as approved ([details](#refinement-carries-compliance-forward)). |
| 30 | Sellers that hold buys for review | Keep a provisional buy (`confirmed_at: null`) in `pending_start` at flight start until you confirm or reject it ([details](#flight-start-activates-committed-buys-only)). |
| 31 | Sellers reporting qualified metrics | Omit a metric's flat carrier from buy and window `totals` when packages report it under more than one qualifier set ([details](#multi-qualifier-metrics-omit-flat-totals)). |
| 32 | Buyers and in-house clients | Send `adcp_version` on every request, with the deprecated `adcp_major_version` mirror through 3.x. 4.0 removes `adcp_major_version` and `adcp.major_versions` ([details](#deprecations)). |

## Required to claim 3.3 behavior

Expand Down Expand Up @@ -580,6 +581,8 @@ Adopters of these patches have already seen these; their changesets reappear in

Existing 3.2 deprecations keep their published removal windows.

One earlier deprecation is worth restating before 4.0. The integer request field `adcp_major_version` and the `adcp.major_versions` capability field, deprecated in 3.1 in favor of `adcp_version` and `adcp.supported_versions`, keep working through 3.x and are **removed in 4.0** ([DR-0035](https://github.com/adcontextprotocol/adcp/blob/main/governance/decisions/DR-0035-adcp-major-version-removed-in-4-0.md)). Buyers send `adcp_version` on every request and keep the integer mirror until then. Sellers keep emitting `adcp.major_versions` alongside `adcp.supported_versions` through 3.x. In-house clients that send only the integer should move to `adcp_version` now. The [v4 compatibility checks](/docs/reference/migration/v4-compatibility#major_versions_removed_in_4_0) flag sellers that still rely on the integer list.

## Compliance and grading

- `version_negotiation` gains an optional scenario for 3.1-pinned response shapes. Its checks stay advisory until runners can validate against the served release's schema bundle.
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/release-notes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ Each beta is immutable. The `3.3-beta` documentation selector advances to the la
- **Deprecated: RFC 9421 signing profiles (#8162).** `adcp/request-signing/v1` and `adcp/webhook-signing/v1` keep working through 3.x and are removed in 4.0. The replacement is the experimental Web Bot Auth profile; see the [agent identity migration guide](/docs/reference/migration/agent-identity-3-3).
- **Deprecated: `seller_id` on event source and audience results (#8290).** Use `platform_id`; the alias keeps working through 3.x, removal no earlier than 4.0.
- **Deprecated: natural-key account references after provisioning (#8123).** They keep working through 3.x and are planned for removal in 4.0; sellers SHOULD declare `account.stable_account_id`. See [Deprecations](/docs/reference/migration/3-2-to-3-3#deprecations).
- **Reminder, not new in 3.3: `adcp_major_version` is removed in 4.0 ([DR-0035](https://github.com/adcontextprotocol/adcp/blob/main/governance/decisions/DR-0035-adcp-major-version-removed-in-4-0.md)).** The integer request field and the `adcp.major_versions` capability field have been deprecated since 3.1. They keep working through 3.x, and 4.0 removes both. Send `adcp_version` on every request, keeping the `adcp_major_version` mirror through 3.x. In-house clients that still send only the integer should switch before 4.0. The compliance grader already blocks sellers that do not echo `adcp_version` or emit `adcp.supported_versions`, and at 4.0 rejects the legacy fields.

Each is detailed in the [migration guide](/docs/reference/migration/3-2-to-3-3#required-to-claim-3-3-behavior) and recorded in [3.3 exceptions to 3.x compatibility](/docs/reference/versioning#3-3-exceptions-to-3-x-compatibility-beta). Fields that a 3.2 validator rejects, and so are sent only on a negotiated 3.3 version, are listed in [Send only to 3.3 peers](/docs/reference/migration/3-2-to-3-3#send-only-to-3-3-peers). Schema-shape changes for SDK generators are in [SDK and code-generator maintainers](/docs/reference/migration/3-2-to-3-3#sdk-and-code-generator-maintainers). 3.3 deprecates no stable field, task, or enum value; it deprecates one usage and the RFC 9421 signing profiles, listed above.
Each is detailed in the [migration guide](/docs/reference/migration/3-2-to-3-3#required-to-claim-3-3-behavior) and recorded in [3.3 exceptions to 3.x compatibility](/docs/reference/versioning#3-3-exceptions-to-3-x-compatibility-beta). Fields that a 3.2 validator rejects, and so are sent only on a negotiated 3.3 version, are listed in [Send only to 3.3 peers](/docs/reference/migration/3-2-to-3-3#send-only-to-3-3-peers). Schema-shape changes for SDK generators are in [SDK and code-generator maintainers](/docs/reference/migration/3-2-to-3-3#sdk-and-code-generator-maintainers). 3.3 deprecates no stable field, task, or enum value; it deprecates one usage and the RFC 9421 signing profiles, listed above. The `adcp_major_version` reminder restates an existing 3.1 deprecation.

### Experimental surfaces in 3.3

Expand Down
49 changes: 49 additions & 0 deletions governance/decisions/DR-0032-sdks-translate-between-releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
id: DR-0032
title: SDKs translate between minor releases of a major; mutations refuse lossy conversion; reduced account keys stay one-to-one
class: normative
status: proposed
date: 2026-10-22
refs: ["#8373", "adcontextprotocol/adcp-client#3370", "adcontextprotocol/adcp-client#3372", "DR-0024"]
dissent: none recorded
---

## Decision

**Application code speaks one release.** An AdCP SDK lets an application build requests and read responses in the newest release the SDK supports. The SDK negotiates the highest release both sides support, per counterpart and per call, and converts between that release and the application's, one consecutive minor at a time within the major (3.2 ↔ 3.1 ↔ 3.0, chained; likewise 4.1 ↔ 4.0). This holds for client SDKs (convert requests down, responses up) and server frameworks (convert requests up, responses down, as [DR-0024] requires of responses).

**Every loss is reported.** A conversion that removes or rewrites a field reports it where the application can see it, with the field's JSON path and the two releases.

**Reads drop; mutations refuse.**
- On a read, a field the older release cannot express is removed and reported.
- On a mutation, the SDK removes only fields that do not change what the counterpart commits to: labels and hints (a buy's `name`, `pacing`, creative rotation hints). If any other field would be lost (a cap, a targeting exclusion, a bidding mode, any field of an update), the SDK refuses the call before sending, with a typed `UNSUPPORTED_FEATURE`-class error.
- A field with an exact older equivalent is moved, not dropped. Moves are defined by the shared conformance cases (see below), never by one SDK.

**A task the older release does not define** is refused before sending with a typed error, unless the counterpart lists the tool.

**Account keys.** When the older release's account natural key cannot express fields of the buyer's key (3.2's `operator_unit`, `currency`, `timezone` and `brand.countries`), the SDK may send the older, reduced key only when the reduction is one-to-one:
- It records which reduced key each buyer key reached the seller with.
- It refuses a second buyer key that would reach the same seller account, and refuses a request that sends that reduced key unchanged on behalf of another buyer key.
- It never merges two buyer accounts into one seller account.

**When the seller upgrades**, a reduced account keeps being addressed by its reduced key (or by `account_id`) until the buyer migrates it explicitly:
- An `operator_unit`-only reduction is rekeyed in place with a `sync_accounts` settings update (`account`, `operator_identity`, `revision`).
- An account reduced by `currency`, `timezone` or `brand.countries` cannot change those on an existing account, so the buyer opens a new account under the full key and decides which key keeps the history.
- `account_id` addressing is the recommended steady state for accounts first opened across a version gap.

**Shared conformance cases.** Every consecutive minor pair has cross-version cases in the compliance bundle. They cover each direction for both client and server: input, expected output, expected drops, or expected refusal. Every SDK runs them.

## Rationale

Without this, each buyer and seller rebuilds the same translation, inconsistently: pinning outbound clients to old releases, reducing account keys by hand, and branching handlers on the served release. Dropping fields from a mutation is the dangerous case. A buy sent without its budget cap or exclusion still succeeds, and spends differently from what was asked. Refusing is the only conversion that cannot misspend.

Sharing a reduced account key looks harmless but merges billing terms, spend, reporting and read access across buyer accounts, for example two buying desks. Encoding the dropped fields into `brand_id` invents brand identities the house never published. A one-to-one reduction keeps account counts honest without either cost, and fails closed exactly where the older release cannot tell keys apart.

## Implications

- Updates the "SDK consequences" section of `specs/version-negotiation.md`: auto-downshift is the default, and it is reported rather than silent.
- Supersedes nothing. [DR-0024]'s rule that responses conform to the served release is how the server side of this decision is graded. Its unpinned-request clause is addressed separately in DR-0034.
- The conformance cases are a follow-up deliverable. Until they exist, SDKs document their moves and refusals.
- Not settled here: a wire signal telling the counterpart that a conversion withheld something. Logs and callbacks on the converting side suffice for now.

[DR-0024]: ./DR-0024-responses-conform-to-served-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
id: DR-0033
title: Every minor-release change states its pathway to the previous minor of the same major
class: normative
status: proposed
date: 2026-10-22
refs: ["#8373", "DR-0032", "DR-0024"]
dissent: none recorded
---

## Decision

Every Normative or Breaking change targeting a minor release states, in the PR that introduces it, how it maps to the previous minor release of the same major: 3.3 to 3.2, and later 4.1 to 4.0. A minor release is non-breaking *and* backward compatible: an SDK on the new minor must still be able to reach a counterpart on any earlier minor of its major. That covers each new or changed field, enum value, task, union variant, requiredness and semantic rule. The statement takes exactly one of these forms:

1. **Droppable.** Older counterparts never see it, and nothing else changes. For a request field on a mutating task, say also whether dropping it changes what the seller commits to; if it does, the downgrade is a refusal (form 3).
2. **Moves.** It has an exact equivalent in the previous release (a renamed field, a restructured block, a narrower enum). Give the mapping, both directions where both exist.
3. **Refused.** The previous release cannot express it, and an SDK must refuse rather than send a different meaning. Name the error.
4. **Identity-bearing.** It changes account identity, resource identity or idempotency keys. Treat it as Breaking, and state the reduction and migration rule (see [DR-0032]).

New tasks keep using the manifest's `added_in` and `legacy_fallback`. The pathway also covers the reverse direction: how a server reads the previous release's payload in the new shape.

Each stated pathway ships with its cross-version conformance case, or with a tracked follow-up when the conformance harness for that pair is not ready.

Reviewers (Ladon and the expert seats) treat a minor-release schema or normative-doc change with no stated pathway as a `high` finding.

## Rationale

SDKs can only translate what the spec has defined how to translate. When a change ships without a pathway, every SDK invents one, and they disagree on exactly the calls that move money. Asking at design time costs one paragraph; reverse-engineering it after release cost the 3.2 cycle an account-identity break ([DR-0032]) and a set of response shapes older validators reject ([DR-0024]).

## Implications

- Applies to every change targeting a minor release from adoption onward, on 3.x now and on every later major's own minor cycle (4.1 maps to 4.0, 4.2 to 4.1). Existing 3.2 and 3.3 changes are backfilled through the conformance cases in [DR-0032].
- A change whose only honest pathway is "refused" is still allowed. The record makes the cost visible, so reviewers can weigh the additive alternative the constitution prefers.
- A major release (4.0) is the one place breaking changes land, and it is out of scope: cross-major bridging stays `VERSION_UNSUPPORTED` by design. Its own minors are in scope from 4.1 onward.

[DR-0024]: ./DR-0024-responses-conform-to-served-release.md
[DR-0032]: ./DR-0032-sdks-translate-between-releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
id: DR-0034
title: A request carrying only adcp_major_version is served the oldest supported release of that major
class: breaking
status: proposed
date: 2026-10-22
refs: ["#8373", "DR-0024", "DR-0032"]
supersedes: DR-0024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium: supersedes: DR-0024 here, and the Decision says this "replaces only the 'Unpinned requests' paragraph of DR-0024" — but DR-0024 is not touched by this PR. Its ## Decision still reads: "A request with no adcp_version, or with only adcp_major_version, is served at the default or highest release" (DR-0024:25). Two ratified records now give opposite answers for the major-only case (highest vs oldest), and DR-0024 carries no marker that it was superseded. Per the records README, supersession keeps the file but adds superseded_by and the superseding record replaces the stale text. Update DR-0024:25 to point at DR-0034 (or restate the oldest rule) and add superseded_by: DR-0034 so a reviewer citing DR-0024 §Unpinned requests doesn't get the superseded answer.

dissent: open, see Implications
---

## Decision

A request that carries `adcp_major_version` and no `adcp_version` is served the **oldest** release the server supports in that major, and the response echoes it. A buyer that wants a newer release's behavior sends `adcp_version`.

This replaces only the "Unpinned requests" paragraph of [DR-0024]. The rest of DR-0024 stands. A request carrying neither field is still served the server's default release.

## Rationale

Every 3.1+ SDK sends `adcp_version`, so in practice a major-only request comes from a 3.0 buyer, and its payload is 3.0-shaped. Serving it the oldest release reads the request in the shape it was written in and answers in the shape the buyer can validate. That is what [DR-0024] demands of every pinned response. Serving the highest release instead answers a 3.0 buyer with a 3.2 response that a strict 3.0 validator rejects (closed shared types, the removed media-buy `status` alias). With [DR-0032], servers convert between releases, so serving the oldest release costs the handler nothing.

## Implications

- **Changes a default.** Today's text (`specs/version-negotiation.md` §4, `docs/reference/versioning.mdx` "Omitted" and the major-only example) says a major-only request is served the highest supported release. This record classes the change as Breaking, so it needs WG or Board ratification.
- **Open concern.** The versioning docs explicitly allow in-house clients to stay on `adcp_major_version` through all of 3.x. Such a client written against 3.2 would now be served 3.0 and lose newer fields until it sends `adcp_version`. Ratification should decide whether that cost is acceptable. Possible mitigations: deprecation guidance, a grader warning, or keeping "highest" only for sellers that do not translate.
- **3.x only.** `adcp_major_version` is deprecated and removed in 4.0 ([DR-0035]), so this rule ends with the 3.x line. In 4.x a request without `adcp_version` is served the server's default release.
- The TypeScript server framework already serves the oldest release for major-only requests (`@adcp/sdk` `selectServedAdcpRelease`), so it is off-spec until this record or a contrary one is ratified.

[DR-0024]: ./DR-0024-responses-conform-to-served-release.md
[DR-0032]: ./DR-0032-sdks-translate-between-releases.md
[DR-0035]: ./DR-0035-adcp-major-version-removed-in-4-0.md
Loading
Loading