Skip to content

spec: sync_creatives status, idempotency.supported, NOT_FOUND codes#2434

Merged
bokelley merged 3 commits intomainfrom
bokelley/issues-2428-2430
Apr 20, 2026
Merged

spec: sync_creatives status, idempotency.supported, NOT_FOUND codes#2434
bokelley merged 3 commits intomainfrom
bokelley/issues-2428-2430

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Three pre-4.0 DX fixes surfaced during Python SDK v4.0.0-rc validation (adcp-client-python#205). Bundled as one minor spec bump.

Summary

  • Closes Schema: clarify whether sync_creatives response items carry a status field #2428sync_creatives response now mirrors the create_media_buy three-shape pattern. Per-item: optional status: CreativeStatus (so buyers learn approval/review state without a follow-up list_creatives). Top-level: new SyncCreativesSubmitted shape (status: "submitted" + task_id) for when the whole sync is queued async (batch ingestion, governance-gated). Per-item async review (one creative in pending_review while the rest resolves) stays on the synchronous success branch via per-item status — no fabricated sentinels. Resolves the doc/schema mismatch where sync_creatives.mdx was already teaching a submitted/completed async workflow that didn't exist in the schema.
  • Closes Schema: adcp.idempotency.replay_ttl_seconds required-when-present creates an ergonomic trap #2429 — Added adcp.idempotency.supported: boolean (required), mirroring the request_signing.supported pattern. replay_ttl_seconds is conditionally required only when supported: true, so sellers without replay dedup can declare it positively ({ "supported": false }) instead of emitting an ambiguous empty block.
  • Closes Schema: add CREATIVE_NOT_FOUND and SIGNAL_NOT_FOUND to error-code enum #2430 — Added CREATIVE_NOT_FOUND and SIGNAL_NOT_FOUND to the error-code enum to match the existing PRODUCT_NOT_FOUND / MEDIA_BUY_NOT_FOUND / PACKAGE_NOT_FOUND / ACCOUNT_NOT_FOUND / SESSION_NOT_FOUND pattern. Both classified as correctable recovery.

Docs updated

  • docs/protocol/get_adcp_capabilities.mdx — new idempotency subsection with the supported: bool contract, plus updated every in-doc capabilities example across the repo to use the new shape (signals, media-buy, creative, migration, etc.)
  • docs/creative/task-reference/sync_creatives.mdx — three-shape response documented explicitly; per-item status field table entry; async-approval workflow split into per-creative review vs operation-level async
  • docs/building/implementation/error-handling.mdx — added CREATIVE_NOT_FOUND and SIGNAL_NOT_FOUND to the reference tables

Test plan

  • npm run test:schemas — 483 schemas, structural + registry + $ref validation
  • npm run test:json-schema — 249 in-doc examples with $schema directives validate after idempotency-shape updates
  • npm run test:composed — discriminated union shapes (including the new three-branch oneOf on sync_creatives)
  • npm run test:error-handling — reference/SDK/doc consistency on error codes
  • npm run test:examples + test:extensions + test:extension-schemas
  • Pre-commit: test:unit (587 tests) + typecheck

🤖 Generated with Claude Code

… codes

Three pre-4.0 DX fixes surfaced during Python SDK v4.0.0-rc validation.

- sync_creatives response (#2428): add optional per-item `status: CreativeStatus`
  so buyers learn approval/review state without a follow-up list_creatives; add
  third `SyncCreativesSubmitted` top-level shape mirroring the create_media_buy
  three-shape pattern for when the whole sync is queued asynchronously.
- get_adcp_capabilities (#2429): add `adcp.idempotency.supported: boolean`
  mirroring the `request_signing.supported` pattern; `replay_ttl_seconds` is
  conditionally required only when `supported: true`, letting sellers without
  replay dedup declare it explicitly.
- error-code enum (#2430): add `CREATIVE_NOT_FOUND` and `SIGNAL_NOT_FOUND` to
  match the existing PRODUCT_NOT_FOUND / MEDIA_BUY_NOT_FOUND / PACKAGE_NOT_FOUND
  pattern.

Docs updated across capabilities, sync_creatives, error-handling, and all
examples referencing the capabilities idempotency block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 19, 2026

Schema Link Check Results

Commit: e161c3e - fix(training-agent): update idempotency capability to new supported:true shape

⚠️ Warnings (schema not yet released)

These schemas exist in source but haven't been released yet. The links will be broken until the next version is published:

  • https://adcontextprotocol.org/schemas/v3/enums/specialism.json
    • Schema exists in latest (source) but not yet released in v3
    • Action: This link will work after next 3.x release is published

To fix: Either:

  1. Wait for the next release and merge this PR after the release is published
  2. Use latest instead of a version alias if you need the link to work immediately (note: latest is the development version and may change)
  3. Coordinate with maintainers to cut a new release before merging

…rors

Addresses findings from ad-tech-protocol-expert, dx-expert, adtech-product-expert,
code-reviewer, and security-reviewer on PR #2434:

Schema tightening:
- sync_creatives: forbid `status` on items when `action` is `failed` or `deleted`
  via `allOf/if/then` conditional (protocol-expert #1, code-reviewer #2).
- sync_creatives: success and error branches now also exclude `task_id` in their
  `not.anyOf` clauses for defense-in-depth discrimination (code-reviewer #3).
- sync_creatives + create_media_buy: `message` on submitted envelope gets
  `maxLength: 2000` and explicit "untrusted seller input — escape for UI,
  sanitize for LLM" guidance to prevent XSS and prompt-injection via the async
  task channel (security #3).

Schema wording (security):
- sync_creatives item `status` reframed as advisory UI/polling hint, NOT a
  spend-authorization gate. Buyers MUST NOT gate spend on `approved` from sync
  response — reconcile via list_creatives or signed webhook before committing
  (security #2).
- error-code: `CREATIVE_NOT_FOUND` and `SIGNAL_NOT_FOUND` now require uniform
  return on any unknown-or-unauthorized ID to prevent cross-tenant enumeration
  (security note #4).

Doc strengthening:
- `idempotency.supported: false` documented as trap: sending idempotency_key
  is a no-op, naive retry will double-process (dx-expert #3).
- New "Verifying the declaration" paragraph: sellers declaring `supported: true`
  MUST pass a payload-mutation replay probe returning IDEMPOTENCY_CONFLICT, as
  a baseline compliance storyboard assertion (security #1).
- sync_creatives quick-start examples (JS + Python) now show three-branch
  discrimination and per-item pending_review handling (dx-expert #2).
- Python examples use `getattr` instead of misleading `hasattr` on Pydantic
  models (code-reviewer #1).
- Response section explicitly documents the new "status omitted when
  action=failed/deleted" conditional (code-reviewer #2).

Changeset:
- Calls out RC-breaking nature of adcp.idempotency.supported requirement.
- Generator note covers Zod via zod-to-json-schema alongside openapi-typescript,
  datamodel-code-generator, quicktype (code-reviewer #4).
- Migration note: strict validation is now the safe default for sync_creatives
  responses (security note #5).

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

Training-agent's get_adcp_capabilities handler was emitting the old pre-rc.4
idempotency shape (just replay_ttl_seconds). Schema now requires `supported`
at the top of the block; CI caught this via collection-lists storyboard and
training-agent capability tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 8bf720d into main Apr 20, 2026
16 checks passed
bokelley added a commit that referenced this pull request Apr 20, 2026
…loses #2436)

Swap the draft-07 if/then conditional for a two-branch discriminated union
on `supported`: `IdempotencySupported` carries `replay_ttl_seconds` (required);
`IdempotencyUnsupported` forbids it via `not: {required: [replay_ttl_seconds]}`.
Wire format unchanged. Code generators that silently drop `if/then`
(openapi-typescript, zod-to-json-schema, pre-0.25 datamodel-code-generator,
quicktype) now emit two named types with the invariant at the type level.

Safe to fold into #2434 because `supported` is brand-new in that PR — no SDKs
have generated against the interim `if/then` shape yet. Deferring to 4.0 per
the original #2436 proposal would have meant shipping a known-bad shape for
one whole release cycle.

Compliance storyboard narrative and validations updated to reference the
new shape; `supported: false` sellers skip the replay storyboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 20, 2026
…loses #2436)

Swap the draft-07 if/then conditional for a two-branch discriminated union
on `supported`: `IdempotencySupported` carries `replay_ttl_seconds` (required);
`IdempotencyUnsupported` forbids it via `not: {required: [replay_ttl_seconds]}`.
Wire format unchanged. Code generators that silently drop `if/then`
(openapi-typescript, zod-to-json-schema, pre-0.25 datamodel-code-generator,
quicktype) now emit two named types with the invariant at the type level.

Safe to fold into #2434 because `supported` is brand-new in that PR — no SDKs
have generated against the interim `if/then` shape yet. Deferring to 4.0 per
the original #2436 proposal would have meant shipping a known-bad shape for
one whole release cycle.

Compliance storyboard narrative and validations updated to reference the
new shape; `supported: false` sellers skip the replay storyboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 20, 2026
* spec(idempotency): refactor adcp.idempotency to discriminated oneOf (closes #2436)

Swap the draft-07 if/then conditional for a two-branch discriminated union
on `supported`: `IdempotencySupported` carries `replay_ttl_seconds` (required);
`IdempotencyUnsupported` forbids it via `not: {required: [replay_ttl_seconds]}`.
Wire format unchanged. Code generators that silently drop `if/then`
(openapi-typescript, zod-to-json-schema, pre-0.25 datamodel-code-generator,
quicktype) now emit two named types with the invariant at the type level.

Safe to fold into #2434 because `supported` is brand-new in that PR — no SDKs
have generated against the interim `if/then` shape yet. Deferring to 4.0 per
the original #2436 proposal would have meant shipping a known-bad shape for
one whole release cycle.

Compliance storyboard narrative and validations updated to reference the
new shape; `supported: false` sellers skip the replay storyboard.

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

* spec(idempotency): negative-path fixtures + tone fix on const:true description

Address review feedback on PR #2447:

- Replace meta-phrasing on IdempotencySupported.supported with wire-behavior
  description, matching the tone of IdempotencyUnsupported.supported.
- Add five schema fixtures exercising the oneOf discriminator: two positive
  (supported:true with TTL, supported:false alone) and three negative paths
  (TTL on unsupported, missing TTL on supported, empty block). The negative
  cases lock the `not: {required}` invariant that Ajv's default error text
  obscures.

Codegen spot-check verified externally: openapi-typescript 7.13 emits
`{supported: true; replay_ttl_seconds: number} | {supported: false}` and
datamodel-code-generator 0.54 emits two Pydantic classes with
`Literal[True]/Literal[False]` and required `replay_ttl_seconds` on the
supported branch — exactly the type-level invariant the refactor promises.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <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

1 participant