Skip to content

feat(examples): sync_accounts + list_accounts with billing_entity write-only guard wired#403

Draft
bokelley wants to merge 1 commit intomainfrom
claude/issue-377-sync-list-accounts-billing-guard
Draft

feat(examples): sync_accounts + list_accounts with billing_entity write-only guard wired#403
bokelley wants to merge 1 commit intomainfrom
claude/issue-377-sync-list-accounts-billing-guard

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 3, 2026

Closes #377

Implements the two missing account-management methods on V3ReferenceSeller in the v3 reference seller example. The Account.billing_entity column already stores full bank details for invoicing — this PR wires the existing project_business_entity_for_response / project_account_for_response helpers so the write-only guard is enforced on every response path, not just architectural.

Changes

examples/v3_reference_seller/src/platform.py

  • sync_accounts — upserts AccountRow (persists full billing_entity including bank); echoes billing_entity back with bank stripped via project_business_entity_for_response; returns SyncAccountsSuccessResponse. Natural upsert key is {brand.domain}:{operator} — comments explain the separator safety and tell adopters to replace this with their own stable key. dry_run is intentionally ignored (reference impl always commits); dry_run=False is returned to avoid lying to the caller about what was persisted.

  • list_accounts — queries AccountRow scoped to the authenticated buyer agent with optional status/sandbox filters; reconstructs adcp.types.Account via model_validate (coerces billing_entity JSON dict → BusinessEntity Pydantic object, so project_account_for_response can call model_copy() safely); returns ListAccountsResponse.

Both methods re-query BuyerAgentRow by agent_url to resolve the FK — ctx.buyer_agent.ext doesn't carry the DB surrogate id because _row_to_agent in buyer_registry.py discards row.id. Comments at both call sites name the root cause.

examples/v3_reference_seller/tests/test_smoke.py

Two new async smoke tests using inline _StubSession fakes (consistent with the existing no-PG test philosophy). Both assert model_dump(mode="json") output so they test the serialised form, not just the Pydantic object attribute:

  • test_sync_accounts_strips_bank_details — passes a billing_entity with IBAN/BIC; asserts "bank" absent and "legal_name" present in the response
  • test_list_accounts_strips_bank_details — stubs an AccountRow with bank in billing_entity; asserts same

What was tested

  • ruff check — clean (examples/ excluded from CI ruff run; verified explicitly)
  • mypy examples/v3_reference_seller/src/platform.py --ignore-missing-imports — no new errors (3 pre-existing class-subclass errors in the file)
  • pytest examples/v3_reference_seller/tests/ -v — 9/9 pass (2 new, 7 pre-existing)
  • pytest tests/ -q — same baseline as main (1 pre-existing TLS test failing; unrelated to this PR)

Pre-PR review

  • code-reviewer: approved after 3 blockers fixed — added "name" to sync response dict; fixed sandbox normalisation (None → False) to keep DB and wire consistent; changed dry_run echo from req.dry_run to False (was a lie on the wire when session committed)
  • dx-expert: approved after 1 blocker fixed — expanded the natural-key comment to state adopters should replace it; added entity-level vs account-level helper guidance at sync_accounts call site; improved dry_run docstring with SQLAlchemy rollback sketch

Nits surfaced (not fixed — not blocking):

  • get_products docstring doesn't end with "Production adopters…" (pre-existing inconsistency; out of scope)
  • Update path preserves billing when acct.billing is None (intentional no-op; acceptable for reference impl)

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See adcp#3121
for context.

Session: https://claude.ai/code/session_01N51oUL2dxvvaVqe2xcjTuf


Generated by Claude Code

…te-only guard wired

Implements the two missing account-management methods on V3ReferenceSeller
in the v3 reference seller example, satisfying issue #377.

* sync_accounts — upserts AccountRow (stores full billing_entity including
  bank details for invoicing); echoes billing_entity back with bank stripped
  via project_business_entity_for_response before returning
  SyncAccountsSuccessResponse. Natural upsert key is brand.domain:operator;
  adopters with an existing ID scheme should replace this derivation. dry_run
  is intentionally ignored — response always carries dry_run=False to avoid
  lying to the caller about what was committed.

* list_accounts — queries AccountRow scoped to the authenticated buyer agent
  with optional status/sandbox filters; reconstructs adcp.types.Account via
  model_validate (coerces billing_entity dict → BusinessEntity) then projects
  through project_account_for_response so bank is never serialised on the wire.

Both methods use the module-level current_tenant import and guard ctx.buyer_agent
before querying. They re-query BuyerAgentRow by agent_url to resolve the FK
(ctx.buyer_agent.ext doesn't carry the surrogate id — see buyer_registry).

Adds two smoke tests that drive the full method path through inline _StubSession
fakes and assert bank absent / legal_name present in model_dump(mode="json")
output, matching the existing no-PG test philosophy.

Closes #377

https://claude.ai/code/session_01N51oUL2dxvvaVqe2xcjTuf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(v3-ref-seller): implement sync_accounts + list_accounts with billing_entity write-only projection guard wired

2 participants