From 28dbb296481d9b32e8ec110fd9896583b6095844 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 29 Apr 2026 19:14:16 -0400 Subject: [PATCH] =?UTF-8?q?docs(migration)!:=20add=20v4.0=20=E2=86=92=20v4?= =?UTF-8?q?.1=20migration=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the three breaking changes folded into the 4.1 release so upgraders have a single artifact to consult: 1. ``ADCPClient.pending_task_id`` → ``ADCPClient.active_task_id`` (and the same on ``A2AAdapter``); ``context_id=`` / ``reset_context()`` raise ``TypeError`` instead of ``ValueError`` on non-A2A protocols. 2. ``FormatId.__name__`` is now ``"FormatReferenceStructuredObject"`` because AdCP 3.0.1 polished ``core/format-id.json``'s title. Public ``FormatId`` keeps working via the aliases module; pickled instances and reflection on ``__name__`` break. 3. ``MEDIA_BUY_STATE_MACHINE`` no longer has ``pending_activation`` — replaced with the spec-correct ``pending_creatives`` and ``pending_start``. BREAKING CHANGE: ``FormatId.__name__`` and ``__qualname__`` change from ``"FormatId"`` to ``"FormatReferenceStructuredObject"`` because AdCP 3.0.1 polished the schema title on ``core/format-id.json``. The public ``adcp.FormatId`` alias keeps working — ``Format(format_id= FormatId(...))`` and ``isinstance(x, FormatId)`` are unchanged. Two niche cases break: pickled ``FormatId`` instances from 4.0 fail to unpickle on 4.1, and snapshot tests / log scrapers asserting on ``__name__`` see the rename. See MIGRATION_v4.0_to_v4.1.md. BREAKING CHANGE: ``MEDIA_BUY_STATE_MACHINE`` no longer accepts ``"pending_activation"`` as a key — that string is not part of the AdCP v3 ``enums/media-buy-status.json``. ``valid_actions_for_status ("pending_activation")`` now returns ``[]``. Use ``"pending_creatives"`` (creatives still awaiting approval) or ``"pending_start"`` (creatives approved, awaiting flight start) per the spec. See MIGRATION_v4.0_to_v4.1.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- MIGRATION_v4.0_to_v4.1.md | 98 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 MIGRATION_v4.0_to_v4.1.md diff --git a/MIGRATION_v4.0_to_v4.1.md b/MIGRATION_v4.0_to_v4.1.md new file mode 100644 index 00000000..bfa04cc1 --- /dev/null +++ b/MIGRATION_v4.0_to_v4.1.md @@ -0,0 +1,98 @@ +# Migrating from v4.0 to v4.1 + +4.1 picks up [AdCP 3.0.1](https://adcontextprotocol.org/docs/reference/release-notes#version-301) +(a stable-surface no-op for handlers — no field renames, no new enum values +on stable schemas) and ships the [a2a-sdk 1.0 migration](https://a2a-protocol.org/changelog). +Most code keeps working unchanged. This guide lists the three places where +that's not true. + +## 1. `ADCPClient.pending_task_id` → `ADCPClient.active_task_id` + +The attribute name reflects what it actually holds — the *currently active* +A2A task id, including non-terminal states (working, input-required, auth-required). +"pending" suggested only one specific lifecycle phase. + +```python +# Before (4.0) +if client.pending_task_id is not None: + ... + +# After (4.1) +if client.active_task_id is not None: + ... +``` + +Same rename on `A2AAdapter.pending_task_id` → `active_task_id`. + +The constructor's `context_id=` kwarg and `reset_context()` now raise +`TypeError` (was `ValueError`) when called on non-A2A protocols. The string +value remains acceptable; only the operation is invalid for MCP. Catch +`TypeError` if you were catching `ValueError` here. + +## 2. `FormatId` class identity changed + +AdCP 3.0.1 polished `core/format-id.json`'s schema title from `"Format ID"` +to `"Format Reference (Structured Object)"`. datamodel-code-generator +follows the title, so the canonical class on disk is now +`FormatReferenceStructuredObject` — the public `FormatId` name is preserved +as an alias in `adcp.types.aliases`. + +For 99% of code, this is invisible: + +```python +# Both work identically on 4.0 and 4.1 +from adcp import FormatId, Format + +fid = FormatId(agent_url="https://creative.example.com", id="display_300x250") +fmt = Format(format_id=fid, ...) + +isinstance(fid, FormatId) # True on both versions +``` + +Two niche cases break: + +- **Pickled `FormatId` instances from 4.0** fail to unpickle on 4.1 because + the qualname `FormatId` no longer exists at + `adcp.types.generated_poc.core.format_id`. Re-create the instances under + 4.1 (or migrate to JSON serialization, which round-trips cleanly across + both versions). +- **Reflection on `FormatId.__name__`** sees `"FormatReferenceStructuredObject"` + rather than `"FormatId"`. If you were snapshotting type names in tests or + log scrapers, update the expected values. + +```python +# Before (4.0) +assert FormatId.__name__ == "FormatId" + +# After (4.1) +assert FormatId.__name__ == "FormatReferenceStructuredObject" +``` + +## 3. `MEDIA_BUY_STATE_MACHINE` keys match the spec enum + +The stale `pending_activation` key has been replaced with `pending_creatives` +and `pending_start` (the two distinct phases that 4.0 was conflating). On +4.0, `valid_actions_for_status("pending_activation")` returned a list; on +4.1 it returns `[]` and the spec-correct keys return the action lists. + +```python +# Before (4.0) — was already broken in production agents +actions = valid_actions_for_status("pending_activation") # returns a list + +# After (4.1) — match the spec +actions = valid_actions_for_status("pending_creatives") # creatives still pending +# or +actions = valid_actions_for_status("pending_start") # creatives approved, awaiting start +``` + +If you stored `"pending_activation"` as a status string anywhere, map it to +`"pending_start"` on read. + +## What to test after upgrading + +- Run your full test suite — the `pending_task_id` rename is a noisy compile + break that surfaces immediately; the other two are quieter. +- If you have any pickle-based fixtures or `__name__` assertions, search for + `FormatId` references and update the expected values. +- If you operate a media-buy state machine, search for `pending_activation` + in your codebase.