Skip to content

feat(server): createMediaBuyStore — opt-in targeting_overlay echo (#1415)#1424

Merged
bokelley merged 3 commits into
mainfrom
bokelley/sdk-issues-1415-1417
May 3, 2026
Merged

feat(server): createMediaBuyStore — opt-in targeting_overlay echo (#1415)#1424
bokelley merged 3 commits into
mainfrom
bokelley/sdk-issues-1415-1417

Conversation

@bokelley

@bokelley bokelley commented May 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1415.

createAdcpServerFromPlatform gains a mediaBuyStore opt-in. When wired, the framework satisfies the seller spec contract for targeting_overlay echo on get_media_buys without each adapter persisting + merging by hand.

import { createAdcpServerFromPlatform, createMediaBuyStore, InMemoryStateStore } from '@adcp/sdk/server';

createAdcpServerFromPlatform(myPlatform, {
  mediaBuyStore: createMediaBuyStore({ store: new InMemoryStateStore() }),
});

The framework then:

  • Persists packages[].targeting_overlay from create_media_buy requests, joined with seller-assigned package_id (or buyer_ref when supplied). Runs in the createMediaBuy projection so it covers both the sync arm AND the HITL completion arm.
  • On get_media_buys, backfills missing packages[].targeting_overlay from the store. Packages the seller already echoed are left untouched.
  • On update_media_buy, deep-merges the patched overlay against prior persisted: omitted keys keep prior, non-null values replace, explicit null clears. new_packages[] are persisted as fresh entries.

Backed by any AdcpStateStore (InMemoryStateStore for dev, PostgresStateStore for production). Account-scoped per-tenant via scopedStore. Failures logged + swallowed — auto-echo never breaks a successful seller response.

hello_seller_adapter_guaranteed.ts now wires mediaBuyStore by default — every fork inherits the contract for free.

Triage resolution: drops four stale suppressors

Triage flagged that test/lib/storyboard-drift.test.js:291–299 suppressed the four media_buy_seller/inventory_list_targeting/get_after_* assertions as UPSTREAM_SCHEMA_DRIFT per adcontextprotocol/adcp#2488. Verified directly: schemas/cache/3.0.5/media-buy/get-media-buys-response.json includes targeting_overlay on PackageStatus with prose mandating echo for sellers claiming property-lists/collection-lists. adcp#2488 is resolved by the 3.0 GA schema update; the suppressors are stale and dropped in this PR.

Test plan

  • npx vitest run src/lib/server/media-buy-store.test.ts — 9/9 pass
  • node --test test/lib/storyboard-drift.test.js — 498/499 pass (1 unrelated skip), 0 fail
  • npx tsc --noEmit -p tsconfig.lib.json clean
  • npm run format:check clean
  • Storyboard media_buy_seller/inventory_list_targeting/get_after_create passes against the worked example (manual repro)

Companion follow-ups

🤖 Generated with Claude Code

bokelley added a commit that referenced this pull request May 3, 2026
Address PR #1424 code-reviewer feedback:
- Document the optimistic-on-success persistence in updateMediaBuy.
  Persistence runs after the platform method returns without throwing;
  publishers returning error envelopes on the success path will see
  the persisted overlay diverge from their internal view.
- Document `backfill` mutation semantics on the public MediaBuyStore
  interface (was previously only on the implementation).
- Document why `new_packages[]` entries without `targeting_overlay`
  are intentionally not tracked (overlay-only store).

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

bokelley commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Fold candidate — issue #1430 (filed from the protocol-expert review on this PR).

persistFromCreate in media-buy-store.ts always sources targeting_overlay from the request package. The spec requires echoing what the seller actually persisted — for normalizing sellers (those that resolve agent_url, strip unknown fields, or canonicalize list_id before persisting), the echoed value diverges from what was stored.

The fix is small: in persistFromCreate, replace:

targeting_overlay: reqPkg.targeting_overlay,

with:

targeting_overlay: respPkg?.targeting_overlay ?? reqPkg.targeting_overlay,

media-buy-store.ts is new in this PR, so the fix fits naturally before merge — no separate PR or changeset needed beyond what's already here. The CreateMediaBuyResultForStore interface would also need targeting_overlay?: TargetingOverlay added to its package shape to type-check.

Blocked-on: #1424 posted on #1430.


Generated by Claude Code

bokelley and others added 2 commits May 3, 2026 00:02
… sellers (#1415)

`createAdcpServerFromPlatform` gains a `mediaBuyStore` option. When wired,
the framework persists `packages[].targeting_overlay` from
`create_media_buy`, echoes it on `get_media_buys`, and deep-merges
`update_media_buy` patches against the prior persisted overlay (omitted
keys keep prior, non-null values replace, explicit `null` clears).
Backed by any `AdcpStateStore`, account-scoped via `scopedStore`.
Failures are logged + swallowed — auto-echo never breaks a successful
seller response.

Closes the seller spec gap on
`schemas/cache/3.0.5/media-buy/get-media-buys-response.json`'s
mandated echo of persisted `property_list` / `collection_list` refs
for sellers claiming property-lists / collection-lists specialisms.

Worked example `hello_seller_adapter_guaranteed.ts` now wires
`mediaBuyStore` by default — every fork inherits the contract for free.

Drops four stale `UPSTREAM_SCHEMA_DRIFT` suppressors in
`test/lib/storyboard-drift.test.js`. The cited adcp#2488 was resolved
by the AdCP 3.0 GA schema update — `PackageStatus.targeting_overlay`
is on the wire response shape, so the assertions are now valid and
the SDK is positioned to satisfy them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address PR #1424 code-reviewer feedback:
- Document the optimistic-on-success persistence in updateMediaBuy.
  Persistence runs after the platform method returns without throwing;
  publishers returning error envelopes on the success path will see
  the persisted overlay diverge from their internal view.
- Document `backfill` mutation semantics on the public MediaBuyStore
  interface (was previously only on the implementation).
- Document why `new_packages[]` entries without `targeting_overlay`
  are intentionally not tracked (overlay-only store).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley force-pushed the bokelley/sdk-issues-1415-1417 branch from 478056a to f038199 Compare May 3, 2026 04:02
… persistFromCreate

For normalizing sellers that resolve agent_url, strip unknown fields, or
canonicalize list_id before persisting, the echoed targeting_overlay must
reflect what was actually stored, not a verbatim copy of the request value.
Also adds targeting_overlay to CreateMediaBuyResultForStore packages shape
to type-check the respPkg access.

Refs #1430.

https://claude.ai/code/session_01LnaXzvFxKDS9b99ZUw79bN
@bokelley

bokelley commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Applied the fold from #1430 in commit 3b7e6ab:

  • CreateMediaBuyResultForStore.packages now includes targeting_overlay?: TargetingOverlay so the response shape is typeable.
  • persistFromCreate now stores respPkg?.targeting_overlay ?? reqPkg.targeting_overlay — normalizing sellers that mutate the overlay before echoing will have their canonical form persisted, not the raw request value.

All 9 media-buy-store.test.ts tests pass on the updated diff.


Generated by Claude Code

@bokelley

bokelley commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Acknowledged — the fold from #1430 looks clean. The respPkg?.targeting_overlay ?? reqPkg.targeting_overlay normalization is the right call: sellers that canonicalize the overlay before echoing now have their version persisted rather than the raw request value, which is what the spec intends. All 9 tests green is a good signal. No further action needed from triage.


Triaged by Claude Code. Session: https://claude.ai/code/session_011fctMz3kLEKYpnQz1unL8L


Generated by Claude Code

@bokelley bokelley merged commit dda2a77 into main May 3, 2026
10 checks passed
bokelley added a commit that referenced this pull request May 3, 2026
…1430) (#1433)

* test(server): lock in seller-response preference over request in MediaBuyStore (#1430)

Adds a unit test to mediaBuyStore.persistFromCreate that exercises the
spec contract surfaced in PR #1424 protocol-expert review: when both
request and response carry packages[i].targeting_overlay, the persisted
echo reflects the seller's response (not the buyer's request copy).

The behavior shipped in #1424 already prefers response over request,
but no test pinned it — adding one prevents regression.

Closes #1430.

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

* chore: add empty changeset for test-only #1430 PR

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 3, 2026
Final review of what's queued for 6.7 ship surfaced four
adopter-relevant changes that weren't in the migration recipe yet:

New recipes (12 → 15):
- #13 createTenantStore — opinionated multi-tenant AccountStore
  builder with built-in fail-closed tenant-isolation gate. Resolves
  the screenshot's "RosterAccountStore (Postgres)" item — Postgres
  was always blocked on adopter persistence layer; the helper is
  the SDK side of that contract.
- #14 state-machine helpers (MEDIA_BUY_TRANSITIONS,
  assertMediaBuyTransition, CREATIVE_ASSET_TRANSITIONS,
  assertCreativeTransition). Drop-in replacements for the local
  status-graph copy three example files were carrying.
- #15 createMediaBuyStore — opt-in targeting_overlay echo on
  get_media_buys for sellers claiming property-lists /
  collection-lists.

Recipe updates:
- #5 — note AccountStore.syncGovernance is now typed (drops the v5
  escape-hatch + `as any` pattern); credentials stripped on emit.
- #10 — add Shape C (createRosterAccountStore) for publisher-curated
  rosters alongside Shape A (InMemoryImplicitAccountStore) and
  Shape B (createOAuthPassthroughResolver). Three-shape table.
- #12 — `createOAuthPassthroughResolver` now also re-exported from
  `@adcp/sdk/server` (the patch landed on main); all three import
  paths work.

What-else-changed additions:
- Account<TCtxMeta> v3 wire alignment — billing_entity (with bank
  stripped on emit), rate_card, payment_terms, credit_limit, setup
  (drives pending_approval → active lifecycle), account_scope,
  governance_agents, reporting_bucket. Promoted from a footnote per
  adtech-product-expert's earlier review.
- multi_id truncation dev warn paired with the existing
  media_buy_ids[] fan-out item.
- Cross-specialism dispatch docs — no ctx.platform.<X> accessor;
  class instance + this OR closure capture.
- composeMethod testing recipes link (docs/recipes/composeMethod-testing.md
  is now merged on main from #1371).
- Sales-social + sales-guaranteed planning surface mocks
  (delivery_estimate / forecast / availability) — adopters can now
  wire Product.forecast against the mocks.
- narrowAccountRef export.
- Storyboard-runner task_completion.<key> capture + webhook fallback.

CLAUDE.md and AGENTS.md blurbs updated from "twelve" to "fifteen"
with the three new helper names called out.

Picked up via merge from main (20 commits since the last sync):
createTenantStore (#1420), createMediaBuyStore (#1424),
state-machine helpers (#1427), createRosterAccountStore + Shape C
docs, syncGovernance typed, Account v3 wire alignment, Account.bank
strip, sales-* planning surface (#1414), task_completion capture
(#1426), runner webhook fallback (#1434), narrowAccountRef (#1428),
composeMethod testing recipes (#1371 merged), oauth-passthrough
re-export, multi-id truncation warn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 3, 2026
…ne (#1416) (#1480)

The Hello adapter was the only example carrying an EXPECTED_FAILURES allowlist
(3 entries). Investigation revealed:

- #1416 (NOT_CANCELLABLE) was a real adapter-side gap. Wired now: localBuyStatus
  Map tracks per-buy MediaBuyStatus, createMediaBuy stamps pending_creatives,
  updateMediaBuy calls assertMediaBuyTransition (newly imported from
  @adcp/sdk/server) on canceled/paused patches and updates the tracker.
  Re-cancel throws NOT_CANCELLABLE per core/state-machine.yaml.

- #1415 (targeting_overlay echo) and #1417 (HITL media_buy_id capture) are
  fixture-side gaps — both SDK fixes already shipped (createMediaBuyStore in
  PR #1424, runner `task_completion.<path>` support in PR #1426). The
  storyboards in adcontextprotocol/adcp need migration to consume them:
  - #1415: every step's account ref needs `sandbox: true` so create + get
    resolve to the same namespace in the synthesis-branch resolver.
  - #1417: sales_guaranteed/create_media_buy needs `task_completion.media_buy_id`
    instead of bare `media_buy_id` for HITL completion-artifact capture.

Allowlist shrinks 3 → 2; remaining entries' `reason` strings updated to spell
out which side of the boundary closes each. Header comment refreshed.

The other four hello adapters (creative-template, seller-social,
signals-marketplace, si-brand) have no allowlist and already pass clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request May 3, 2026
@github-actions github-actions Bot mentioned this pull request May 3, 2026
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.

SDK: opinionated MediaBuyStore + auto-echo for targeting_overlay

2 participants