Skip to content

feat(webhooks): creative lifecycle webhooks + sync_accounts universal write (#2261, #4582 track 3)#4730

Merged
bokelley merged 4 commits into
mainfrom
bokelley/issue-2261
May 18, 2026
Merged

feat(webhooks): creative lifecycle webhooks + sync_accounts universal write (#2261, #4582 track 3)#4730
bokelley merged 4 commits into
mainfrom
bokelley/issue-2261

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Summary

Closes #2261. Lands #4582 track 3 (per-account subscription model) without adding a new tool — sync_accounts becomes the universal account-state write surface for both implicit (provisioning) and explicit (settings-update via AccountRef) accounts. Governance unchanged: no implicit subscription; governance teams that want webhooks register an explicit notification_configs[] entry.

Three expert review passes (ad-tech-protocol-expert-deep, adtech-product-expert-deep, code-reviewer-deep, docs-expert-deep) applied to the prior shape; this PR is the result of converging on option 3 after the experts flagged auto-subscription as a foot-gun and naming as misleading on the earlier sync_governance-overload design.

Events

  • creative.status_changed — seller/system-initiated transitions only. Buyer-initiated transitions (archive, unarchive, resubmit) ack via the sync_creatives response path; they do NOT fire. transition.from narrowed to {processing, pending_review, approved} — post-terminal states never appear.
  • creative.purged — seller destruction. purge_kind: soft retains a tombstone on list_creatives for 30 days; purge_kind: hard retains nothing (sanctioned Rule 4 carve-out for legal erasure). No coalescence.

Paired with #4588 media-buy.impairment (no ordering guarantee; reconcile via snapshot).

Subscription model — sync_accounts polymorphic key

Each per-account entry uses one of two key shapes (oneOf):

  • Provisioning mode — flat brand + operator + billing (today's shape). Implicit-account sellers provision/upsert.
  • Settings-update modeaccount (AccountRef) keyed by account_id (explicit) or natural key (implicit). No provisioning side effects. Explicit-account sellers (DV360-class) gain a single focused write surface; sellers that don't implement either mode reject with UNSUPPORTED_PROVISIONING.

Both modes carry notification_configs[] (replace semantics, max 16, subscriber_id always required, additionalProperties: false). Sellers MUST reject event_types[] containing any media-buy-anchored type (forward rule, not enumeration).

list_accounts echoes notification_configs[] per account with credentials redacted (write-only).

Read surface (#4701 track 4 adoption)

list_creatives adopts the 7-item webhook_activity[] checklist:

Reason codes

New creative-event-reason-code enum — 13 categorical values, distinct from impairment-reason-code. Includes review_passed (initial approval), advertiser_request (buyer-initiated takedown), account_closed / account_suspended (cascade), folded seller_archive (was inactivity_archive + storage_policy), and pinned retention_expired (soft only) / legal_erasure (hard only). Each value's enumDescription documents buyer remediation.

Expert-flagged fixes applied

  • Auto-subscription foot-gun → removed (option 3 architecture)
  • Self-serve adoption blocker → resolved (no governance dependency)
  • Naming misleading → resolved (sync_accounts is the right surface name)
  • Missing reason code for initial approval → review_passed added
  • subscriber_id always required (no conditional required-when-multiple)
  • Purge tombstone fields wrapped (schema-enforced co-presence)
  • additionalProperties: false on notification-config.json
  • transition.from narrowed to non-terminal states
  • Reason-code distinguishability rules (policy_revocation vs content_drift when-in-doubt)
  • retention_expired ↔ soft only, legal_erasure ↔ hard only
  • Rule 4 carve-out for hard purges documented in snapshot-and-log.mdx
  • Forbidden event_types phrased as forward rule
  • Ordering note between creative.status_changed and media-buy.impairment
  • maxItems: 16 rationale documented

Test plan

  • npm run build:schemas — clean
  • npm run test:schemas — 7/7
  • npm run test:examples — 36/36
  • npm run test:composed — 43/43
  • npm run test:json-schema — 260/260
  • npm run test:docs-nav — 15/15
  • npm run check:registry — 14 entries OK
  • npm run typecheck — clean
  • Precommit (test:unit + typecheck) — clean
  • Visual review of rendered MDX (snapshot/log carve-out, sync_accounts two-modes section, list_creatives buyer handler)
  • Optional: rebuild @adcp/sdk and verify NotificationConfig, CreativeStatusChangedWebhook, CreativePurgedWebhook types export as expected

🤖 Generated with Claude Code

bokelley and others added 4 commits May 18, 2026 05:59
…sal account-state write (closes #2261, lands #4582 track 3)

Account-anchored creative lifecycle webhooks (creative.status_changed, creative.purged) with sync_accounts as the universal write surface for both implicit (provisioning) and explicit (settings-update via AccountRef) accounts. Governance unchanged — no implicit subscription; governance teams that want webhooks register an explicit notification_configs[] entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y-record.subscriber_id required-vs-optional rule

Protocol reviewer flagged that UNSUPPORTED_PROVISIONING is referenced from sync_accounts-request but not in error-code.json (enum + enumDescriptions + enumMetadata). Also tightened webhook-activity-record.subscriber_id prose to state it's required on account-anchored channels (where every subscriber has a subscriber_id at registration) and optional on per-resource push channels (single-subscriber unambiguous, multi-subscriber lands with #3009).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New code added in 92199a2 needs a drift-lint disposition. Held for 3.1 with target_version="3.1" — consistent with the other sync_accounts wire additions (BILLING_NOT_SUPPORTED, BILLING_NOT_PERMITTED_FOR_AGENT, PAYMENT_TERMS_NOT_SUPPORTED).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The provisioning/settings-update oneOf is structurally disambiguated (presence/absence of `account` AccountRef vs flat `brand+operator+billing` trio, enforced by mutual `not: { required: [...] }` constraints). Adding a tagged `mode` discriminator would be redundant. Classified as `narrowable` per adcp#3917 — same pattern as the existing sync-accounts-response and sync-governance-response oneOfs already in the baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit c54c0d5 into main May 18, 2026
17 checks passed
@bokelley bokelley deleted the bokelley/issue-2261 branch May 18, 2026 10:27
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.

Creative lifecycle webhooks: formalize state-change notifications outside media buy lifecycle

1 participant