Skip to content

feat(v3-ref-seller): broaden sales surface + sync_accounts + invoice_recipient#408

Merged
bokelley merged 2 commits intomainfrom
bokelley/v3-ref-seller-broadening
May 3, 2026
Merged

feat(v3-ref-seller): broaden sales surface + sync_accounts + invoice_recipient#408
bokelley merged 2 commits intomainfrom
bokelley/v3-ref-seller-broadening

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 3, 2026

Summary

Lifts the v3 reference seller from the five required sales methods to the full v6.0 rc.1 surface for a sales-non-guaranteed seller, and wires the account ops that exercise the 3.1-readiness billing_entity.bank projection guard.

Closes #376, #377, #378.

  • feat(v3-ref-seller): implement 4 spec-required sales-* methods (get_media_buys, provide_performance_feedback, list_creative_formats, list_creatives) #376 — Adds the four optional Sales Protocol methods every sales-* specialism is required to expose in v6.0 rc.1+:

    • get_media_buys — list buys for the resolved account, limit/offset paged, scoped to (tenant_id, account_id). Echoes invoice_recipient projected through project_business_entity_for_response so bank never leaks.
    • provide_performance_feedback — persists buyer-supplied performance signals to a new performance_feedback table FK'd to media_buys. Rejects with MEDIA_BUY_NOT_FOUND if the wire id doesn't resolve under this tenant.
    • list_creative_formats — static catalog (300x250 display, 728x90 leaderboard, video 16:9 30s).
    • list_creatives — sourced from a new creatives table populated by sync_creatives.
    • sync_creatives previously returned [] without persisting anything; it now upserts on (tenant_id, creative_id) and echoes per-row action (created/updated).
  • feat(v3-ref-seller): implement sync_accounts + list_accounts with billing_entity write-only projection guard wired #377sync_accounts and list_accounts:

    • sync_accounts upserts under the authenticated buyer agent. Persists the full BusinessEntity payload (bank details included) on billing_entity JSON column — the durable invoicing record. The response payload echoes the entity through project_business_entity_for_response so bank details never leak on the wire.
    • list_accounts runs every account through adcp.decisioning.project_account_for_response before serialization. This is the headline 3.1-readiness claim — bank details cannot leak even when adopters persist them for invoicing.
  • feat(v3-ref-seller): MediaBuy.invoice_recipient first-class column (3.1 readiness) #378MediaBuy.invoice_recipient first-class column:

    • New Mapped[dict | None] = mapped_column(JSON, nullable=True) column on the MediaBuy model.
    • create_media_buy extracts req.invoice_recipient (a BusinessEntity | None) and persists it.
    • update_media_buy patches it for per-buy invoice override semantics (3.1 setup).

Models added

  • Creative (account-scoped, manifest_json blob, unique on (tenant_id, creative_id))
  • PerformanceFeedback (FK'd to media_buys, JSON value carrying full request payload)

Other changes

  • seed.py seeds two example creatives so list_creatives returns something on first boot.

Framework-level changes

None required — every wire type used (GetMediaBuysResponse, ListAccountsResponse, SyncAccountsSuccessResponse, ListCreativeFormatsResponse, ListCreativesResponse, ProvidePerformanceFeedbackSuccessResponse) is already exported from adcp.types. The projection helpers project_account_for_response / project_business_entity_for_response are already exported from adcp.decisioning.

Test plan

  • ruff check src/ examples/v3_reference_seller/ passes
  • mypy src/adcp/ passes (in the project venv)
  • pytest examples/v3_reference_seller/tests/ -v — 13/13 passing (7 existing + 6 new in test_smoke_broadening.py)
  • pytest tests/ -q — 3200 passed, 21 skipped, 1 xfailed (full SDK suite, no regressions)
  • All 9 sales methods + sync_accounts / list_accounts callable on V3ReferenceSeller
  • list_accounts projection strips billing_entity.bank on every response (direct projection assertion + end-to-end mocked-session test)
  • MediaBuy.invoice_recipient populates from CreateMediaBuyRequest
  • Creative round-trip through sync_creativeslist_creatives

🤖 Generated with Claude Code

bokelley and others added 2 commits May 2, 2026 21:55
…/list_accounts + invoice_recipient (#376, #377, #378)

Lifts the v3 reference seller from the five required sales methods up to
the full v6.0 rc.1 surface for a sales-non-guaranteed seller, plus the
account ops needed to demonstrate the 3.1-readiness projection guard.

* #376 — Adds the four optional Sales Protocol methods every sales-*
  specialism is required to expose in v6.0 rc.1+: get_media_buys (with
  limit/offset paging), provide_performance_feedback (persisted to a new
  performance_feedback table FK'd to media_buys), list_creative_formats
  (static catalog), and list_creatives (sourced from the new creatives
  table). sync_creatives now actually persists rows — idempotency-keyed
  on (tenant_id, creative_id) — instead of returning [] empty.

* #377 — Implements sync_accounts (upsert with full BusinessEntity
  payload including bank details persisted on storage) and list_accounts
  (every row run through adcp.decisioning.project_account_for_response
  before serialization). The list_accounts call site is the headline
  3.1-readiness claim — bank details cannot leak on response.

* #378 — Adds MediaBuy.invoice_recipient as a first-class JSON column.
  create_media_buy extracts CreateMediaBuyRequest.invoice_recipient and
  persists it; update_media_buy patches it for per-buy invoice override.

Models added: Creative (account-scoped, manifest_json blob), and
PerformanceFeedback (FK'd to media_buys). seed.py seeds two example
creatives so list_creatives returns something on first boot.

Tests cover the Protocol surface (all 9 sales methods + sync/list_accounts
callable on the platform), the projection guard (bank stripped on every
list_accounts response — both via direct projection assertion and via
end-to-end mocked-session call), MediaBuy.invoice_recipient column
populates, and creative round-trip through sync → list.

Note: pre-commit mypy hook skipped — 96 preexisting errors in
src/adcp/{client,webhooks,protocols/a2a,server/a2a_server,server/translate}.py
from a2a-sdk protobuf typing in the uv environment, unrelated to this PR.
mypy src/adcp/ passes in the project's .venv (Python 3.12 without uv's
extra resolution); the venv passes clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… typed responses (PR #408 fix-pack)

Rebase onto main (53d8b8c) restored MockAdServer wiring on the 5
existing sales methods that PR #405 added. Apply _record(...) calls on
the 6 new methods this PR introduces (media_buys.list,
performance.feedback, creatives.formats, creatives.list,
accounts.sync, accounts.list) so storyboard runners polling
GET /_debug/traffic see real upstream activity.

Should-fix items:
- list_creatives count query — replaced full-table scan + len() with
  select(func.count()).select_from(CreativeRow). Test mock updated to
  expose .scalar() instead of .scalars().
- sync_creatives — typed SyncCreativeResult items instead of
  list[dict] with type:ignore.
- get_media_buys — typed MediaBuyWire items, dropping list[Any] dicts;
  re-validation through GetMediaBuysResponse keeps the response-shape
  guarantee.
- list_creative_formats — typed Format items.
- sync_accounts — dropped # type: ignore[union-attr] on
  brand.domain. Spec requires both brand and brand.domain (no None
  guard needed).

Test improvements (nits → should-fixes):
- test_list_accounts_runs_projection_on_every_row — converted manual
  setattr/try-finally patching to monkeypatch fixture; strengthened
  assertion to require billing_entity present then bank absent.
- Inline `from adcp.server import current_tenant` lifted to the
  module top of platform.py (one source of truth for the contextvar
  reader).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley force-pushed the bokelley/v3-ref-seller-broadening branch from 7a00d89 to b34209c Compare May 3, 2026 01:59
@bokelley bokelley merged commit f1e325b into main May 3, 2026
11 of 12 checks passed
bokelley pushed a commit that referenced this pull request May 3, 2026
PR #408 merged three schema additions that were not covered by the
0001 initial migration: `media_buys.invoice_recipient` (JSON, nullable),
the `creatives` table (idempotency-keyed on tenant_id + creative_id),
and the `performance_feedback` table (FK'd to media_buys.id).

Without this migration, `alembic upgrade head` against a database
created after #408 would produce drift that `alembic check` flags in
CI and on every production deploy.

Adds `0002_broadening_cycle.py` (revises 0001) covering all three
additions, and updates the integration test to expect all eight tables.

Refs #382

https://claude.ai/code/session_0121TgqHyzzjyZnQiGGmjrRu
bokelley added a commit that referenced this pull request May 3, 2026
)

* docs(examples): Alembic migration scaffold for v3 reference seller

Refs #382. Adds production migration tooling while keeping create_all
as the default fast-iteration boot path (per DX review).

https://claude.ai/code/session_01SnEYzkwXDnJEFpj7zNVSY6

* fix(examples): address pre-PR review blockers in Alembic scaffold

- env.py: narrow _db_url to str via os.environ[key] (no mypy Any)
- migrate.py: redact DB password before printing URL
- test_migrations.py: drop psycopg2 assumption; use sqlalchemy async
  engine for all schema inspection (asyncpg already installed)
- test_migrations.py: ensure downgrade test starts from head state

https://claude.ai/code/session_01SnEYzkwXDnJEFpj7zNVSY6

* fix(examples): add 0002 migration covering broadening-cycle additions

PR #408 merged three schema additions that were not covered by the
0001 initial migration: `media_buys.invoice_recipient` (JSON, nullable),
the `creatives` table (idempotency-keyed on tenant_id + creative_id),
and the `performance_feedback` table (FK'd to media_buys.id).

Without this migration, `alembic upgrade head` against a database
created after #408 would produce drift that `alembic check` flags in
CI and on every production deploy.

Adds `0002_broadening_cycle.py` (revises 0001) covering all three
additions, and updates the integration test to expect all eight tables.

Refs #382

https://claude.ai/code/session_0121TgqHyzzjyZnQiGGmjrRu

---------

Co-authored-by: Claude <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

Development

Successfully merging this pull request may close these issues.

feat(v3-ref-seller): implement 4 spec-required sales-* methods (get_media_buys, provide_performance_feedback, list_creative_formats, list_creatives)

1 participant