Skip to content

feat(media-buy): external audience sources on sync_audiences (#6540) - #6857

Open
bokelley wants to merge 2 commits into
mainfrom
sync-audiences-external-sources
Open

feat(media-buy): external audience sources on sync_audiences (#6540)#6857
bokelley wants to merge 2 commits into
mainfrom
sync-audiences-external-sources

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Implements #6540 — the runtime leg of the audience-activation surface (#4324 / #6549), experimental under the same media_buy.audience_activation feature id. WG approved the audience work for 3.2 as experimental.

What this adds

An audience on sync_audiences carries either inline member deltas or a source reference — a pointer to buyer data the seller ingests over a declared activation rail. Data never transits AdCP.

  • core/audience-source.json (new, experimental): dataset (grantee-identified share the seller reads — Snowflake Secure Data Sharing, Databricks D2D, BigQuery authorized views; optional declarative access_expires_at) | platform_segment (binds a vendor-distributed segment to an audience_id; segment_ref is the vendor's ID as issued to the buyer, seller owns the ingest-side mapping).
  • Request: source on the audience object; schema-enforced mutual exclusion with add/remove.
  • Response: source echo (never credentials) with access_status (active/unavailable) and columns_read; last_synced_at schema-required exactly when counts populate (ready/too_small) so every count has an anchor — first-read-pending (processing) and failed-first-ingest stay legal.
  • SOURCE_ACCESS_FAILED on both error-code surfaces, error.field-keyed recovery: bad reference (fix request) vs. missing grant (grant + retry) vs. vendor outage (backoff).
  • Docs: full external-sources section on sync_audiences (flows, canonical column contract, refresh vs. replication lag, latency band); present-tense updates to every "until RFC: external audience sources — pass-through references on sync_audiences #6540 lands" placeholder; canonical experimental-list row extended.
  • Tests: tests/audience-external-sources.test.cjs (12) wired into the chain.

Normative rules from the RFC review threads

  1. Transport fixed at creation — cross-transport upserts rejected with CONFLICT (error.field: audience_id); convert by delete-and-recreate.
  2. Loss of source access MUST NOT change audience status — the seller cannot distinguish revocation from expiry from a transient outage (all three observables are a failed read), so membership freezes at last successful read and stays targetable, last_synced_at freezes, and failed re-reads MUST NOT emit suspended (reserved for consent/policy causes, which remain orthogonal). Health reports via source.access_status.
  3. Access expiry is not deletion — bounds the access window, not retention; DPA governs; teardown stays delete: true.
  4. Single pane — sourced audiences appear in discovery identically to pushed ones; no new list task.
  5. Capability gate — source kinds map 1:1 to declared activation patterns; undeclared kinds → UNSUPPORTED_FEATURE; credential material → CREDENTIAL_IN_ARGS; grantee-identified flows only.

Validation

Schema build, 12/12 new tests, example validation, error-code lint (both enum surfaces), oneOf audit, and compliance links all green on a tree rebased onto the merged #6549.

Closes #6540.

🤖 Generated with Claude Code

Runtime leg of the audience-activation surface, experimental under
media_buy.audience_activation. Audiences carry either inline member
deltas or a source reference (core/audience-source.json): dataset
(grantee-identified share the seller reads) or platform_segment
(binds a vendor-distributed segment). Response echoes the source with
access_status and columns_read; counts anchor to last_synced_at.
Transport fixed at creation; loss of source access never changes
audience status; access expiry is not deletion. New SOURCE_ACCESS_FAILED
error code with error.field-keyed recovery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp of when the most recent sync operation was accepted by the platform. Useful for agents reasoning about audience freshness. Omitted if the seller does not track this."
"description": "ISO 8601 timestamp of when the most recent sync operation was accepted by the platform. Useful for agents reasoning about audience freshness. Omitted if the seller does not track this. REQUIRED when source is present: for externally sourced audiences every count field is as-of this timestamp — it is the buyer's only anchor for what a count means, since two reads can differ because membership changed, match rate changed, or the seller re-read the source."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: Description says last_synced_at is "REQUIRED when source is present," but the if/then two hunks down only requires it when status is ready/too_small — and the docs lifecycle rule scopes it the same way ("Required on sourced audiences whenever counts are populated (ready/too_small)"). A sourced audience in processing has source present with nothing synced yet, and the test at tests/audience-external-sources.test.cjs:612 correctly asserts it validates without last_synced_at. The normative field text is stricter than both the enforcement and the docs. Tighten it: "REQUIRED once counts populate (status ready/too_small)."

@aao-secretariat aao-secretariat Bot added the ladon/needs-human-review Ladon has escalated this PR for human review. label Aug 25, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated schema paths require human/CODEOWNERS approval.

This PR adds an experimental runtime leg of audience activation (#6540): a new discriminated core/audience-source.json, a source request field with schema-enforced mutual exclusion vs add/remove, a response source echo, and a new SOURCE_ACCESS_FAILED error code. Schema, changeset scope (minor, protocol-scoped), oneOf discrimination, and schema↔docs coherence largely hold. No critical or high findings were reported, so the diff itself is not blocking.

However, this PR modifies/adds files under static/schemas/source/**, which are under a hard, deterministic approval gate (gated_paths: true), and review_decision is REVIEW_REQUIRED — not APPROVED. Per decision rule row 2, the outcome must be escalate until a real human/CODEOWNERS approval satisfies the gate.

Medium findings (non-blocking, worth a look during human review):

  • static/schemas/source/media-buy/sync-audiences-response.json:116 — last_synced_at description overstates its requirement relative to its own if/then and the docs (normative-text mismatch).

Escalation reasons:

  • Gated schema paths touched (e.g. static/schemas/source/core/audience-source.json, static/schemas/source/media-buy/sync-audiences-request.json, sync-audiences-response.json, enums/error-code.json) and review_decision is REVIEW_REQUIRED. Human/CODEOWNERS approval required before merge.

Medium findings

  • static/schemas/source/media-buy/sync-audiences-response.json:116 — last_synced_at description overstates requirement vs its own if/then and the docs

Why human review

  • Gated paths touched under static/schemas/source/** (audience-source.json added; sync-audiences-request/response.json, error-code.json, audience-activation-method.json modified) and review_decision is REVIEW_REQUIRED — human/CODEOWNERS approval required before this can merge.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/audience-activation-method.json (modified) matches static/schemas/source/**; static/schemas/source/core/audience-source.json (added) matches static/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/sync-audiences-request.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/sync-audiences-response.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

… schema-graph growth

New 3.2 wire code needs a held-for-next-minor entry in the error-code
drift registry, and core/audience-source.json adds one definition to the
media-buy tool graph (583→584 instances, 152→153 unique) — pins updated
to acknowledge the deliberate growth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated schema paths require human/CODEOWNERS approval.

This PR modifies/adds files under static/schemas/source/** (audience-source.json added; sync-audiences-request/response.json, error-code.json, audience-activation-method.json modified), which sit behind a hard, non-overridable approval gate. review_decision is not APPROVED (unknown/none), so per decision rule row 2 the gate is not satisfied and the outcome must be escalate.

No blocking (critical/high) findings surfaced in this run. The prior medium finding on sync-audiences-response.json:116 is outside the current delta and not re-raised. The delta since the prior pass — a new SOURCE_ACCESS_FAILED disposition (held-for-next-minor, target 3.2) plus definition-count bumps (583→584, 152→153) for the single new audience-source definition — is internally consistent and lint-valid. This is a net-clean iteration, but the gated-paths gate still stands until a human/CODEOWNERS approval is recorded.

Escalation reasons:

  • Gated paths touched under static/schemas/source/** and review_decision is not APPROVED — human/CODEOWNERS review required before merge.

Why human review

  • Touches gated paths under static/schemas/source/** (audience-source.json added; sync-audiences-request.json, sync-audiences-response.json, error-code.json, audience-activation-method.json modified) and review_decision is not APPROVED — human/CODEOWNERS approval required before this can merge (decision rule row 2, hard gate).
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/audience-activation-method.json (modified) matches static/schemas/source/**; static/schemas/source/core/audience-source.json (added) matches static/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/sync-audiences-request.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/sync-audiences-response.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'unknown', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ladon/needs-human-review Ladon has escalated this PR for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC: external audience sources — pass-through references on sync_audiences

1 participant