Skip to content

ci(release): cut 10.0.0-beta.N pre-releases from next - #1672

Merged
bokelley merged 9 commits into
nextfrom
claude/sdk10-beta-channel
Oct 27, 2026
Merged

bokelley merged 9 commits into
nextfrom
claude/sdk10-beta-channel

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

SDK 10 targets AdCP 3.3; with the signed 3.3.0-beta.0 bundle adopted (#1662) and main synced through 9.3.0 (#1671), next leaves the alpha channel.

  • release-please-config.next.json: prerelease-type: beta; the channel contract test (test_release_please_keeps_next_on_the_beta_channel) and RELEASING.md follow. The 10.0.0-alpha.0 proposal (chore(next): release 9.0.0 #1533) is closed as superseded.
  • next also releases its SDK 10 companion: adcp-reporting-storage is a second Release Please component on next (0.2.0-beta.N, tags adcp-reporting-storage-v0.2.0-beta.N, changelog packages/adcp-reporting-storage/CHANGELOG.next.md, manifest entry, its own release PR and publish dispatch in release-please-next.yml); the normalizer accepts next's storage release PR; release-publish.yml publishes the storage component from next; and the ci(release): refuse an adcp release its companion packages can't install with #1605 companion gate now runs for every adcp publish on both lines instead of being skipped on next. chore(reporting-storage): cut 0.2.0-beta.N pre-releases from next #1663 (storage-only, merged with Release-As: 0.2.0-beta.0) seeds that line, so the first adcp 10.0.0-beta.0 publish passes the gate once next's storage release is on PyPI.

Merge order after this lands: Release Please opens two release PRs on next; merge and publish adcp-reporting-storage 0.2.0-beta.0 first, then adcp 10.0.0-beta.0 (its companion gate requires the storage release on PyPI). One outside step: add next to the release-publish environment's deployment branches.

Squash-merge with the body ending in the footer below: Release Please reads Release-As from the squash commit.

Release-As: 10.0.0-beta.0

🤖 Generated with Claude Code

bokelley and others added 9 commits October 26, 2026 21:14
…iver the ones already stuck in the queue (#1651)

PgWebhookDeliverySupervisor never sent a task webhook (#1606). send_mcp
stored the task status (completed, failed, ...) in status_str, while the
worker only claimed rows whose status_str was 'pending' or 'retry', so every
job enqueued through send_mcp stayed unsent. A row that was claimed and then
retried after a transport error would also have gone out with
status "retry".

Queue state now lives in a new delivery_state column; status_str keeps the
task status the payload carries. create_schema() migrates an existing table
with ADD COLUMN IF NOT EXISTS delivery_state TEXT NOT NULL DEFAULT 'pending',
which marks every row already in the queue deliverable without touching its
task status. No worker ever claimed those rows, so each is sent once, oldest
first. During a rolling deploy, rows an older process enqueues take the same
default, and an older worker (still claiming on status_str) never picks up a
row this version writes. The old status_str work index is dropped.

The worker also fixes the idempotency_key before a fresh send and persists
it when it reschedules, so a retry after a transport error reuses the key
instead of minting a new one. A replay that raised keeps the bytes it was
replaying. send_mcp persists an enum status as its wire value.

Release note: adopters running PgWebhookDeliverySupervisor will see task
webhooks that were stuck in adcp_webhook_delivery_queue delivered after
upgrading; inspect or delete them first if they should not go out
(docs/webhooks/pg-delivery-supervisor.md).

Closes #1606.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n pin (#1650)

#1634 repeats the deferred #1538. It bumps the exact
datamodel-code-generator pin (0.64 -> 0.81) without regenerating
src/adcp/types/, and edits the frozen reporting interop requirement files
under scripts/ci/reporting_interop/python/, which breaks those controls.

Add a pip entry for the interop directory that ignores every dependency,
matching the npm entry from #1590, and pip and uv entries for the root that
ignore datamodel-code-generator. Version updates stay off
(open-pull-requests-limit: 0); ignore rules also apply to security updates.
Generator upgrades are an SDK 10 decision (docs/v10-codegen-plan.md, #1649).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pin the published, cosign-verified AdCP 3.2.7 release (schemas/releases/3.2.7.json:
source 8d1d3244, certificate identity release.yml@refs/heads/3.2.x, measured byte
counts and SHA-256 of all four artifacts) and refresh the 3.2 cache, the packaged
compliance bundle and the generated sources through the stock
--require-signed-release synchronization and regeneration chain.

3.2.5 through 3.2.7 are compliance-storyboard releases. The one schema change is
3.2.6's `x-removed-in: "4.0.0"` marker on the 43 deprecated fields scheduled for
removal in AdCP 4.0 (annotation only: no field, type or requirement changes). The
generator's version-envelope normalization compared an inline `adcp_major_version`
copy against the envelope's declaration modulo documentation keys, so the new marker
on the envelope made `SearchBrandsRequest` fall out of `AdcpVersionEnvelope`; treat
`x-removed-in` as documentation so the hierarchy is unchanged. The generated types
otherwise differ only in the example `$schema` URLs, and the field-shape delta is
empty.

Ship the 3.2.7 compliance bundle and release pin in the wheel and sdist alongside the
retained 3.2.1 and 3.2.4 trees. Replace the 3.2.4 adoption test with a 3.2.7 one and
keep a focused immutable 3.2.4 historical test; point the live-pin assertions, the
request-signing vector manifest and the task-message lattice refs at 3.2.7.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…ed array (#1658)

Validating a schema-valid McpWebhookPayload or AsyncResponseData raised
``TypeError: Unable to apply constraint 'min_length' ... object of type 'Losses'
has no len()`` instead of returning a model or a ValidationError (#1616).

An array schema that carries an ``allOf`` of ``contains``/``if``-``then``
constraints (``losses`` on the legacy purchase continuation, ``refinements`` on
refine_proposals_request, ``results`` on refine_proposals_response) has no Python
shape codegen can give those keywords, so datamodel-code-generator renders the
composition as an empty object model and widens the field to ``list[Item] |
Empty``. Pydantic applies the array's min_length/max_length to the whole union,
and a dict reaching the model arm trips the constraint with a raw TypeError.

Add a generic post-generation rule, drop_empty_model_arms_from_array_fields,
that strips every empty AdCPBaseModel arm from a union that also carries a
``list[...]`` arm. The class itself stays, since it is a public adcp.types name
and removing it would be a 9.x surface change for nothing. The existing
fix_legacy_purchase_accepted_losses stays as the model-specific validator
injection it also performs.

Tests: the four bundle examples of core/mcp-webhook-payload.json validate both
as written and in the completed form the sweep grades; a dict on each affected
field is a ValidationError; the generated tree carries no empty-model arm beside
a list. Remove the two schemas from the round-trip sweep's REFUSING allowlist,
and add a guard that no schema-valid document may make a generated model raise
anything but ValidationError.

Closes #1616

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…d transition graph (#1664)

A seller that mirrors status from its ad server needs two things the SDK did
not publish (#1604): the terminal statuses, which every adopter rederived from
MEDIA_BUY_TRANSITIONS, and the moves it may record from its platform, which
are not all moves a buyer may request.

adcp.decisioning.state_machines now exports:

- MEDIA_BUY_TERMINAL_STATUSES, derived from the graph (completed, canceled,
  rejected), and is_terminal_media_buy_status(), which returns False for a
  status the spec does not define (draft, pending_approval).
- OBSERVED_MEDIA_BUY_TRANSITIONS, a strict superset of MEDIA_BUY_TRANSITIONS,
  and is_observable_media_buy_transition(). The extra edges are the three
  AdCP 3.2 published edges that follow from the seller's lifecycle rather
  than from a status a buyer asks for: pending_creatives -> active,
  pending_creatives -> paused and pending_start -> paused (3.2.7
  lifecycle.mdx "Creative sync timing" and transition diagram;
  specification.mdx on the latent create-time hold). The result equals the
  graph the @adcp/client storyboard runner's status.monotonic invariant
  enforces for 3.2.

The issue's other moves (active -> pending_start, completed -> active or
paused) have no AdCP 3.2 edge, and the specification says terminal states
allow no further transitions, so the observed graph leaves them out and the
docstrings say why. There is no CREATIVE_TERMINAL_STATUSES: every creative
status can move on. MEDIA_BUY_TRANSITIONS and assert_media_buy_transition are
unchanged; media_buy_actions reuses the new terminal constant.

Docs: docs/media-buy-state-machine.md. Tests cover the derivation, the spec
enum, the published graph, unknown and unchanged statuses, and the generated
MediaBuyStatus enum; an adopter type-check fixture covers the new surface.

Closes #1604

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nchronous business transactions (#1665)

PgNotificationOutbox.enqueue_prepared only accepted a psycopg 3
AsyncConnection, so a seller whose business writes run on synchronous
SQLAlchemy, psycopg2 or psycopg 3 could not queue a notification in the same
transaction as the change it announces (#1607).

enqueue_prepared_sync takes the same arguments and accepts a psycopg 3
Connection, a psycopg2 connection, or a SQLAlchemy Connection on either driver
(session.connection() for an ORM unit of work). The pure work moves into
_prepare_insert (validation, _protected, _scope, _aad, AES-GCM, deadline) and
_existing_binding_id (the duplicate-binding check), which both methods call;
only the two statements differ.

Transaction detection per driver, refused with ValueError before any SQL:
- psycopg 3 and psycopg2: the session must be in a transaction block
  (INTRANS), the async method's rule.
- SQLAlchemy: in_transaction() must be true and the driver connection must not
  be in autocommit (AUTOCOMMIT isolation). SQLAlchemy begins the driver
  transaction on the first statement, so the notification may be the first
  write.
Any other object, including a psycopg AsyncConnection, raises TypeError.
Driver dispatch reads sys.modules, so no optional driver is imported.

Both paths now ask the driver for tuple rows. An adopter connection with
row_factory=dict_row made enqueue_prepared fail with KeyError: 0 on main.

Tests against real PostgreSQL, for psycopg 3, psycopg2, and SQLAlchemy on
each: the row commits with the business write and the existing async worker
(a fresh outbox instance) delivers the same PreparedWebhook; a rollback
leaves neither row and delivers nothing; sync and async share one binding
(identical re-enqueue returns the id, different headers raise, the
transaction stays usable); per-driver refusal outside a transaction;
SQLAlchemy enqueue-first, AUTOCOMMIT refusal and Session use; dict-row
connections on all three paths. Docs section and an adopter type-check
fixture.

Closes #1607

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aao-ipr-bot[bot] <279263549+aao-ipr-bot[bot]@users.noreply.github.com>
Forward-ports main through v9.3.0 (2bbe334) into next with a true merge:
the signed AdCP 3.2.7 adoption (#1657), the codegen empty-arm fix (#1658),
#1650, #1651, #1664 and #1665.

Conflicts: next keeps its 3.3.0-beta.0 wire pin; main's 3.2.7 becomes the
3.2 preview bundle (PREVIEW_VERSIONS), its compliance package and release
pin ship beside 3.3.0-beta.0 (MANIFEST.in, package-data), and main's 3.2.7
adoption test becomes the installed-3.2-contract test
(tests/test_adcp_3_2_7_historical.py) beside main's 3.2.4 historical test.
Generated types are regenerated from the 3.3.0-beta.0 bundle with main's
codegen fix (4 files).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SDK 10 targets AdCP 3.3; with the signed 3.3.0-beta.0 bundle adopted, next
leaves the alpha channel. release-please-config.next.json now sets
prerelease-type: beta, the release-channel contract test and RELEASING.md
follow, and the 10.0.0-alpha.0 proposal (#1533) is superseded.

next also releases its SDK 10 companion: adcp-reporting-storage is a second
component (0.2.0-beta.N, CHANGELOG.next.md, its own release PR and publish
run), the normalizer accepts its release PR, release-publish.yml publishes
it from next, and the #1605 companion gate runs for every adcp publish on
both lines instead of being skipped on next.

Release-As: 10.0.0-beta.0

Co-Authored-By: Claude Fable 5.1 <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 — release-engineering PR with no blocking findings.

This PR cuts 10.0.0-beta.N from next and forward-ports main through 9.3.0. The reviewer found no Critical/High/Medium findings.

What was checked:

  • Genuinely new work (release-please-config.next.json beta channel + second adcp-reporting-storage component, release-please-next.yml, release-publish.yml companion-gate widening, dependabot ignore entries, pyproject normalizer) faithfully mirrors main's proven two-component setup, including the correct bare-vs-path-prefixed output asymmetry.
  • Tests (test_release_configuration.py, test_normalize_pyproject_prerelease.py) are updated to assert every new fact — CI gates stay armed.
  • Forward-ported source (webhook supervisor delivery_state split, idempotency-key fix, enqueue_prepared_sync, state-machine exports) carries additive feat/fix commits with no mislabeled breaking change on the public surface.
  • delivery_state migration uses a constant DEFAULT (metadata-only, no table rewrite) — no data-loss concern.

Decision table: No critical/high/medium findings. gated_paths is false, so row 2 does not fire. high_risk is true but all high_risk_reasons are (modified) schema-cache files with no medium-or-higher finding attached — rows 3 and 5 do not fire. No author team gate, no prior decision. None of rows 1–8 match → row 9 approve.

@bokelley
bokelley merged commit 322e6f0 into next Oct 27, 2026
42 checks passed
@bokelley
bokelley deleted the claude/sdk10-beta-channel branch October 27, 2026 08:56
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