Summary
update_media_buy does not implement the revision optimistic-concurrency field. The pinned AdCP spec (3.1.0-beta.3) makes this a MUST: per media-buy/update-media-buy-request.json and the SDK type (adcp==5.7.0 UpdateMediaBuyRequest.revision):
"Expected current revision for optimistic concurrency. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match. Obtain from get_media_buys or the most recent update response."
This was the one part of the envelope-tolerance review on #1546 (Blocker 3) intentionally deferred, because — unlike the sibling fixes (context echo, idempotency-key preservation) that shipped in that PR — it is a migration-bearing concurrency feature, not a mechanical change.
Current state (as of #1546)
- There is no
revision column on the MediaBuy ORM model, and no compare logic.
revision is currently stripped by the envelope-tolerance layer (ADCP_ENVELOPE_FIELDS in src/core/request_compat.py) because no wrapper / request model declares it, so update_media_buy(revision=N, ...) reaches the impl with revision=None.
_update_media_buy_impl never consults revision.
Net effect: a buyer that pins revision for safe concurrent editing gets a silent success instead of the spec-mandated CONFLICT on a stale write.
Scope / acceptance criteria
- Migration — add a
revision integer column to MediaBuy (Alembic; non-empty upgrade()/downgrade(), single head). Initialize existing rows to 1.
- Plumb the field — declare
revision on the update_media_buy MCP wrapper, A2A raw/handler, and the internal UpdateMediaBuyRequest, so the envelope-tolerance middleware no longer strips it (it strips only ADCP_ENVELOPE_FIELDS a tool does not declare).
- Enforce transactionally — in the update path, compare the request's
revision against the media buy's current revision under the same transaction; raise a typed CONFLICT on mismatch; increment the stored revision on success.
- Echo revision — return the current revision on the
update_media_buy response, and on get_media_buys responses (the spec names both as the source of the next expected revision).
- Tests — mismatch →
CONFLICT wire envelope; matching revision → success + increment; concurrent-update race (two updates carrying the same stale revision → the second gets CONFLICT). Mutation-verify each pin. Cite the authoritative schema (dist/schemas/3.1.0-beta.3/media-buy/update-media-buy-request.json) and note whether the conformance storyboard grades it.
Related (lower priority)
Full idempotency replay-dedup for sync_accounts. #1546 restored end-to-end preservation of the client idempotency_key, but _sync_accounts_impl still does not consult it for dedup (the idempotency_attempt repository already exists). Wiring replay-dedup here is a natural companion to the revision work.
References
Summary
update_media_buydoes not implement therevisionoptimistic-concurrency field. The pinned AdCP spec (3.1.0-beta.3) makes this a MUST: permedia-buy/update-media-buy-request.jsonand the SDK type (adcp==5.7.0UpdateMediaBuyRequest.revision):This was the one part of the envelope-tolerance review on #1546 (Blocker 3) intentionally deferred, because — unlike the sibling fixes (context echo, idempotency-key preservation) that shipped in that PR — it is a migration-bearing concurrency feature, not a mechanical change.
Current state (as of #1546)
revisioncolumn on theMediaBuyORM model, and no compare logic.revisionis currently stripped by the envelope-tolerance layer (ADCP_ENVELOPE_FIELDSinsrc/core/request_compat.py) because no wrapper / request model declares it, soupdate_media_buy(revision=N, ...)reaches the impl withrevision=None._update_media_buy_implnever consultsrevision.Net effect: a buyer that pins
revisionfor safe concurrent editing gets a silent success instead of the spec-mandatedCONFLICTon a stale write.Scope / acceptance criteria
revisioninteger column toMediaBuy(Alembic; non-emptyupgrade()/downgrade(), single head). Initialize existing rows to1.revisionon theupdate_media_buyMCP wrapper, A2A raw/handler, and the internalUpdateMediaBuyRequest, so the envelope-tolerance middleware no longer strips it (it strips onlyADCP_ENVELOPE_FIELDSa tool does not declare).revisionagainst the media buy's current revision under the same transaction; raise a typedCONFLICTon mismatch; increment the stored revision on success.update_media_buyresponse, and onget_media_buysresponses (the spec names both as the source of the next expected revision).CONFLICTwire envelope; matching revision → success + increment; concurrent-update race (two updates carrying the same stale revision → the second getsCONFLICT). Mutation-verify each pin. Cite the authoritative schema (dist/schemas/3.1.0-beta.3/media-buy/update-media-buy-request.json) and note whether the conformance storyboard grades it.Related (lower priority)
Full idempotency replay-dedup for
sync_accounts. #1546 restored end-to-end preservation of the clientidempotency_key, but_sync_accounts_implstill does not consult it for dedup (theidempotency_attemptrepository already exists). Wiring replay-dedup here is a natural companion to the revision work.References
dist/schemas/3.1.0-beta.3/media-buy/update-media-buy-request.json(revision→CONFLICT);core/version-envelope.json@ v3.1.0-beta.3