Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 .changeset/calm-canvases-coordinate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@adcp/sdk': patch
---

Complete the AdCP 3.2.0-beta.6 adoption with updated migration guidance,
consistent prerelease fixtures, and A2A 1.0 request-signing method coverage.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ if (!artifact.name) { ... }

Used for receiving task completion/progress notifications. Placement differs by protocol:

- **AdCP 3.2.0-beta.5 on A2A**: Goes in skill parameters as
- **AdCP 3.2.0-beta.6 on A2A**: Goes in skill parameters as
`push_notification_config` (snake_case), including `operation_id`. The native
A2A `params.configuration.pushNotificationConfig` is a distinct transport
facility; the SDK may retain it for compatibility, but it does not replace
Expand Down
9 changes: 6 additions & 3 deletions docs/guides/MEDIA-BUY-3.2-COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ calls the established names.

The SDK is pinned to the signed `3.2.0-beta.6` bundle. That exact prerelease
supersedes beta.5 and adds delivery metric identities, requested-metric
narrowing, sortable breakdowns, and completeness echoes. Beta.5 introduced
the normative async identity, cross-channel convergence, webhook retry-horizon,
and continuation-generation contract.
narrowing, sortable breakdowns, completeness echoes, coordinated placements,
seller-rendered stateful display, creative component assets, and A2A 1.0
request-signing method names. Beta.5 introduced the normative async identity,
cross-channel convergence, webhook retry-horizon, and continuation-generation
contract; beta.4 introduced flexible-window availability and the products-only
legacy purchase-continuation contract.

## MCP surface comparison

Expand Down
8 changes: 5 additions & 3 deletions docs/migration-12-to-14.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Migrating from 12.x to 14 beta

This is the direct upgrade path for applications skipping SDK 13. SDK 14 includes both SDK 13's canonical-creative and security boundary changes and the AdCP `3.2.0-beta.5` preview surface. Treat it as two review checkpoints even if you deploy one package update.
This is the direct upgrade path for applications skipping SDK 13. SDK 14 includes both SDK 13's canonical-creative and security boundary changes and the AdCP `3.2.0-beta.6` preview surface. Treat it as two review checkpoints even if you deploy one package update.

AdCP prerelease pins are exact: beta.5 supersedes beta.4. Beta.2 added canonical
AdCP prerelease pins are exact: beta.6 supersedes beta.5. Beta.2 added canonical
compact proposal and direct-buy lifecycle storyboards through operational
control and MediaBuy readback; beta.4 adds flexible-window availability and
durable products-only legacy purchase continuations. Beta.5 defines stable
async identity, cross-channel terminal convergence, webhook retry horizons,
and crash-safe continuation generation replacement.
and crash-safe continuation generation replacement. Beta.6 adds coordinated
placements, seller-rendered stateful display, creative component assets, and
A2A 1.0 request-signing method names.

Install the beta explicitly:

Expand Down
13 changes: 7 additions & 6 deletions docs/migration-13-to-14.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Migrating from 13.x to 14 beta

SDK 14 adopts AdCP `3.2.0-beta.5` while preserving the canonical creative boundary introduced in SDK 13. Most SDK 13 applications can install the beta and continue using the established 3.x tools unchanged; adopt the compact 3.2 lifecycle only after the remote agent advertises it.
SDK 14 adopts AdCP `3.2.0-beta.6` while preserving the canonical creative boundary introduced in SDK 13. Most SDK 13 applications can install the beta and continue using the established 3.x tools unchanged; adopt the compact 3.2 lifecycle only after the remote agent advertises it.

AdCP 3.2 prereleases are exact protocol pins: beta.5 replaces beta.4 in the
AdCP 3.2 prereleases are exact protocol pins: beta.6 replaces beta.5 in the
SDK's compatible-version list rather than extending a rolling 3.2-beta range.
Beta.1 restored `adcp_major_version` on `buy_products`,
`accept_proposal`, and `control_media_buy`; the SDK now sends that field again
Expand All @@ -12,16 +12,17 @@ storyboards through operational control and MediaBuy readback; beta.4 adds
flexible-window availability and durable products-only legacy purchase
continuations. Beta.5 defines stable async identity, cross-channel terminal
convergence, webhook retry horizons, and crash-safe continuation generation
replacement.
replacement. Beta.6 adds coordinated placements, seller-rendered stateful
display, creative component assets, and A2A 1.0 request-signing method names.

### Beta.5 task webhook registration and polling

`push_notification_config` is now an AdCP application-layer field across MCP,
A2A, and REST. On A2A it is carried in skill parameters and remains distinct
from native `TaskPushNotificationConfig`. Every beta.5 registration must carry
from native `TaskPushNotificationConfig`. Every beta.5-or-later registration must carry
a buyer `operation_id`; SDK clients generate and reuse one identity across the
authorized request, registration provenance, route, and webhook envelope.
Beta.5 sellers return `INVALID_REQUEST` before handler dispatch when the field
Beta.5-or-later sellers return `INVALID_REQUEST` before handler dispatch when the field
is missing or malformed. Explicitly negotiated older bundles keep their prior
wire behavior.

Expand Down Expand Up @@ -83,7 +84,7 @@ loading; keep using `requires_capability` for a singular predicate.
10. Upgrade durable idempotency storage before application traffic: add the nullable PostgreSQL `retain_until` column/index, preserve `IdempotencyCacheEntry.retainUntil`, and add atomic `putIfAbsent()`, `replaceIfPayloadHash()`, `replaceIfPayloadHashAndExpired()`, and `deleteIfPayloadHash()` to every custom backend.
11. Upgrade custom deferred-task storage with `putForSettlementOperationIfAbsent()`, `getBySettlementOperationId()`, and `replaceForSettlementOperationIfVersion()`. The initial token/index write and nested A→B index move must each be atomic.
12. Replace webhook emitter `operation_id` arguments with SDK-local `delivery_id` values and upgrade custom stores to `WebhookDeliveryStore`. One delivery ID binds one canonical payload and key; use a fresh delivery ID for each changed status observation while retaining the AdCP `operation_id` inside the payload.
13. Ensure custom beta.5 buyers include `push_notification_config.operation_id`, and update A2A integrations to keep the AdCP registration in skill parameters even when native A2A push configuration is also present.
13. Ensure custom 3.2 buyers include `push_notification_config.operation_id`, and update A2A integrations to keep the AdCP registration in skill parameters even when native A2A push configuration is also present.
14. Treat failed/rejected task results as canonical terminal artifacts when `include_result` is requested; do not discard them while preserving only the summary error.

### Webhook delivery identity and retry horizons
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -485,13 +485,13 @@
"pretest": "npm run schemas:ensure",
"test": "npm run test:node && npm test --workspace=packages/eslint-plugin --if-present && npm test --workspace=@adcp/reference-renderers",
"test:node": "npm run test:node:fast && npm run test:node:slow",
"test:node:fast": "NODE_ENV=test node --test-timeout=60000 --test-force-exit --test $(find test -maxdepth 1 -name '*.test.js' ! -name 'canonical-creatives-a2a-e2e.test.js' ! -name 'generate-zod-object-intersections.test.js' -print) $(find test/lib -maxdepth 1 -name '*.test.js' ! -name 'cli-auth-scheme.test.js' ! -name 'cli-soft-fail.test.js' ! -name 'cli-webhook-receiver-flag.test.js' ! -name 'conformance-cli.test.js' ! -name 'storyboard-notices.test.js' -print)",
"test:node:fast:shard": "test -n \"$TEST_SHARD\" && NODE_ENV=test node --test-timeout=60000 --test-force-exit --test-shard=$TEST_SHARD --test $(find test -maxdepth 1 -name '*.test.js' ! -name 'canonical-creatives-a2a-e2e.test.js' ! -name 'generate-zod-object-intersections.test.js' -print) $(find test/lib -maxdepth 1 -name '*.test.js' ! -name 'cli-auth-scheme.test.js' ! -name 'cli-soft-fail.test.js' ! -name 'cli-webhook-receiver-flag.test.js' ! -name 'conformance-cli.test.js' ! -name 'storyboard-notices.test.js' -print)",
"test:node:slow": "NODE_ENV=test node --test-timeout=180000 --test-force-exit --test test/canonical-creatives-a2a-e2e.test.js test/generate-zod-object-intersections.test.js test/lib/cli-auth-scheme.test.js test/lib/cli-soft-fail.test.js test/lib/cli-webhook-receiver-flag.test.js test/lib/conformance-cli.test.js test/lib/storyboard-notices.test.js",
"test:node:fast": "NODE_ENV=test node --test-timeout=60000 --test-force-exit --test $(find test -maxdepth 1 -name '*.test.js' ! -name 'canonical-creatives-a2a-e2e.test.js' ! -name 'generate-zod-object-intersections.test.js' -print) $(find test/lib -maxdepth 1 -name '*.test.js' ! -name 'cli-auth-scheme.test.js' ! -name 'cli-soft-fail.test.js' ! -name 'cli-webhook-receiver-flag.test.js' ! -name 'conformance-cli.test.js' ! -name 'media-buy-lifecycle-release-gate.test.js' ! -name 'storyboard-notices.test.js' -print)",
"test:node:fast:shard": "test -n \"$TEST_SHARD\" && NODE_ENV=test node --test-timeout=60000 --test-force-exit --test-shard=$TEST_SHARD --test $(find test -maxdepth 1 -name '*.test.js' ! -name 'canonical-creatives-a2a-e2e.test.js' ! -name 'generate-zod-object-intersections.test.js' -print) $(find test/lib -maxdepth 1 -name '*.test.js' ! -name 'cli-auth-scheme.test.js' ! -name 'cli-soft-fail.test.js' ! -name 'cli-webhook-receiver-flag.test.js' ! -name 'conformance-cli.test.js' ! -name 'media-buy-lifecycle-release-gate.test.js' ! -name 'storyboard-notices.test.js' -print)",
"test:node:slow": "NODE_ENV=test node --test-timeout=180000 --test-force-exit --test test/canonical-creatives-a2a-e2e.test.js test/generate-zod-object-intersections.test.js test/lib/cli-auth-scheme.test.js test/lib/cli-soft-fail.test.js test/lib/cli-webhook-receiver-flag.test.js test/lib/conformance-cli.test.js test/lib/media-buy-lifecycle-release-gate.test.js test/lib/storyboard-notices.test.js",
"pretest:lib": "npm run schemas:ensure",
"test:lib": "npm run test:lib:fast && npm run test:lib:slow",
"test:lib:fast": "NODE_ENV=test node --test-timeout=60000 --test-force-exit --test $(find test/lib -maxdepth 1 -name '*.test.js' ! -name 'cli-auth-scheme.test.js' ! -name 'cli-soft-fail.test.js' ! -name 'cli-webhook-receiver-flag.test.js' ! -name 'conformance-cli.test.js' ! -name 'storyboard-notices.test.js' -print)",
"test:lib:slow": "NODE_ENV=test node --test-timeout=180000 --test-force-exit --test test/lib/cli-auth-scheme.test.js test/lib/cli-soft-fail.test.js test/lib/cli-webhook-receiver-flag.test.js test/lib/conformance-cli.test.js test/lib/storyboard-notices.test.js",
"test:lib:fast": "NODE_ENV=test node --test-timeout=60000 --test-force-exit --test $(find test/lib -maxdepth 1 -name '*.test.js' ! -name 'cli-auth-scheme.test.js' ! -name 'cli-soft-fail.test.js' ! -name 'cli-webhook-receiver-flag.test.js' ! -name 'conformance-cli.test.js' ! -name 'media-buy-lifecycle-release-gate.test.js' ! -name 'storyboard-notices.test.js' -print)",
"test:lib:slow": "NODE_ENV=test node --test-timeout=180000 --test-force-exit --test test/lib/cli-auth-scheme.test.js test/lib/cli-soft-fail.test.js test/lib/cli-webhook-receiver-flag.test.js test/lib/conformance-cli.test.js test/lib/media-buy-lifecycle-release-gate.test.js test/lib/storyboard-notices.test.js",
"pretest:examples": "npm run build",
"test:examples": "NODE_ENV=test node --test-timeout=180000 --test-force-exit --test-concurrency=1 --test test/examples/*.test.js",
"test:e2e": "node --test test/e2e/registry-live.test.js",
Expand Down
1 change: 1 addition & 0 deletions test/lib/adcp-version-projection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test('isAdcpVersionAtLeast compares release-precision prerelease pins', () => {
assert.equal(isAdcpVersionAtLeast('3.2-beta.5', '3.2.0-beta.5'), true);
assert.equal(isAdcpVersionAtLeast('3.2-beta.6', '3.2.0-beta.5'), true);
assert.equal(isAdcpVersionAtLeast('3.2.0', '3.2.0-beta.5'), true);
assert.equal(isAdcpVersionAtLeast('3.3.0', '3.2.0-beta.5'), true);
});

test('sellerAdvertises31: buildVersion is advisory and never used for negotiation', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/lib/preview-creative-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function runtimeClient(executeTask, config = {}) {
client.cachedCapabilities = {
version: 'v3',
majorVersions: [3],
supportedVersions: ['3.2.0-beta.5'],
supportedVersions: ['3.2.0-beta.6'],
protocols: ['creative'],
features: {},
extensions: [],
Expand Down
2 changes: 1 addition & 1 deletion test/lib/proposal-negotiation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function revise(overrides = {}) {
}

function request(refinements = [revise()]) {
return { adcp_version: '3.2-beta.5', adcp_major_version: 3, idempotency_key: KEY, refinements };
return { adcp_version: '3.2-beta.6', adcp_major_version: 3, idempotency_key: KEY, refinements };
}

function completed(data) {
Expand Down
4 changes: 2 additions & 2 deletions test/lib/v2-getproducts-autowire.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ async function buildMockSeller(getProductsResponse, clientConfig = {}, { adverti
server.registerTool('get_adcp_capabilities', { inputSchema: {} }, async () => {
const capabilities = {
status: 'completed',
adcp_version: '3.2.0-beta.5',
adcp: { major_versions: [3], supported_versions: ['3.2.0-beta.5'] },
adcp_version: '3.2.0-beta.6',
adcp: { major_versions: [3], supported_versions: ['3.2.0-beta.6'] },
supported_protocols: ['media_buy'],
specialisms: [],
};
Expand Down
30 changes: 30 additions & 0 deletions test/request-signing-verifier-api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,36 @@ describe('verifier API v3: operation optional + VerifyResult discriminated union
);
});

it('matches A2A 1.0 PascalCase protocol methods exactly', async () => {
const request = {
method: 'POST',
url: 'https://seller.example.com/a2a',
headers: { 'Content-Type': 'application/json' },
body: '{"jsonrpc":"2.0","method":"CancelTask","params":{"id":"task_conformance_001"},"id":1}',
};
const options = {
...baseStores(),
capability: {
supported: true,
covers_content_digest: 'either',
required_for: [],
protocol_methods_required_for: ['CancelTask'],
},
now: () => 1_776_520_800,
};

await assert.rejects(
() => verifyRequestSignature(request, options),
err => err instanceof RequestSignatureError && err.code === 'request_signature_required' && err.failedStep === 0
);

const mismatch = await verifyRequestSignature(request, {
...options,
capability: { ...options.capability, protocol_methods_required_for: ['tasks/cancel'] },
});
assert.strictEqual(mismatch.status, 'unsigned', '0.3 names must not normalize to A2A 1.0 names');
});

it('unsigned JSON-RPC batch with protocol method in protocol_methods_required_for throws request_signature_required', async () => {
await assert.rejects(
() =>
Expand Down
Loading