feat(compliance): audience_buy_flow + event_dedup_flow capability-gated scenarios (#4637)#4664
Merged
Merged
Conversation
… audience validation (#4637) Two new capability-gated scenarios in the contract pattern (#4637), added to sales-non-guaranteed.requires_scenarios: - media_buy_seller/audience_buy_flow — gated on media_buy.audience_targeting presence. Certifies sync_audiences → bound audience_id in targeting → unbound id rejected → delivery against an audience-targeted buy. Sibling to performance_buy_flow on the audience side; the unbound-id rejection is the discriminating assertion. The literal error.field path on packages[0].targeting_overlay.audience_include[0] mirrors the event_source_id contract from #4642. - media_buy_seller/event_dedup_flow — gated on media_buy.conversion_tracking.multi_source_event_dedup equals true. Certifies that the same event_id from two registered event sources attributes to one conversion, not two. Sellers without multi_source_event_dedup grade not_applicable — the bit gates the scenario; the cumulative-count assertion (1, not 2) is the contract. The training agent does not declare this bit, so it grades not_applicable here; no training-agent fix is needed for this scenario. Training-agent fix: create_media_buy now rejects targeting_overlay.audience_include / audience_exclude entries whose audience_id was never registered via sync_audiences, with INVALID_REQUEST and error.field set to the literal JSONPath-lite path. Mirrors the event_source_id validation pattern from #4654. sync_audiences itself is now wired through the training agent (legacy /mcp HANDLER_MAP and v6 /sales/mcp via AudiencePlatform) so adopters can run the audience scenario against the reference implementation. Four unit tests cover the four contract paths (accept/reject × include/exclude). Three sibling product-level scenarios (reach, clicks, completed_views) remain blocked on #4651 product-level capability gating RFC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (PR 4664 CI fix) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
bokelley
added a commit
that referenced
this pull request
May 18, 2026
… contains: matcher (#4637) (#4722) Four new storyboard scenarios in the capability-claim contract pattern, all gated via the `contains:` matcher (shipped in @adcp/client 7.70 — adcp-client#1817), all added to `sales-non-guaranteed.requires_scenarios`: - `performance_buy_flow_roas` — gated on `media_buy.conversion_tracking.supported_targets` containing `per_ad_spend` (#4639). Certifies ROAS goal acceptance, rejection of per_ad_spend without value_field, and delivery surfaces `conversion_value` + `roas` alongside `conversions` + CPA. - `reach_buy_flow` — gated on `media_buy.supported_optimization_metrics` containing `reach` (#4669). Certifies reach-unit binding, rejection of unsupported reach_unit, and delivery surfaces `reach` + `frequency`. - `clicks_buy_flow` — gated on `media_buy.supported_optimization_metrics` containing `clicks` (#4669). Certifies click goal acceptance and delivery surfaces `clicks` + `cost_per_click`. No rejection arm — clicks is universal in semantics. - `completed_views_buy_flow` — gated on `media_buy.supported_optimization_metrics` containing `completed_views` (#4669). Certifies view_duration_seconds binding, rejection of unsupported duration (per optimization-goal.json), and delivery surfaces `completed_views` + `completion_rate`. All four grade `not_applicable` against the embedded training agent — the training agent doesn't declare `supported_targets` or `supported_optimization_metrics`. Same anti-façade hygiene as `event_dedup_flow` (#4664): an agent that doesn't claim a capability is not held to its scenario. Refs: #4637, #4639, #4669, #4642, #4664, #4651, adcp-client#1817 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
bokelley
added a commit
that referenced
this pull request
May 18, 2026
…loses #4725) (#4731) Lands the deferred per-creative conversion attribution work from #4642 as a single PR with two coupled changes: - New boolean `media_buy.conversion_tracking.per_creative_attribution` capability bit in get-adcp-capabilities-response.json. Optional; defaults to false. Declares that the seller can attribute conversions per creative within a package and surface them via media_buy_deliveries[].by_package[].by_creative[].conversions. - New scenario media_buy_seller/per_creative_conversion_attribution, gated on the new bit, added to sales-non-guaranteed.requires_scenarios. Registers two creatives via sync_creatives, creates a media buy assigning both to one package with a CPA goal, logs two conversion events, simulates delivery, and asserts the by_creative[] breakdown is populated for both creatives (creative_id + conversions on rows 0 and 1) — the second-row assertion is the asymmetry check that separates honest per-creative attribution from a single-row façade. Closes the gap deliberately left by performance_buy_flow (#4642), whose narrative defers per-creative attribution because honest adopters report at differing granularities: social per-ad, retail-media per-line, MMP-mediated per-campaign, broadcast/CTV per-placement. Requiring per-creative in the base CPA scenario would have failed those honest implementations. The bit gates the scenario; sellers that don't advertise it grade not_applicable. log_event's payload (core/event.json) does not carry creative_id — attributing each event to a specific creative is the seller's internal click / view-through correlation responsibility, not the buyer's. The scenario logs two events with distinct event_ids and relies on the seller's correlation to spread simulate_delivery's conversions across the two assigned creatives in the by_creative[] breakdown. No training-agent changes — the reference implementation does not declare per_creative_attribution, so the scenario grades not_applicable and CI passes. Same anti-façade pattern as event_dedup_flow (#4664) and frequency_cap_enforcement (#4640). Refs: #4725 (capability bit + scenario), #4637 (capability-claim meta), #4642 (performance_buy_flow that deferred this), #4639 (supported_targets bit for the sibling ROAS gate). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
5 tasks
bokelley
added a commit
that referenced
this pull request
May 19, 2026
…ompleted_views scenarios (#4766) * feat(training-agent): metric-mode capability + validation + derived delivery for clicks/reach/completed_views Declares seller-level `media_buy.supported_optimization_metrics` (honest union across catalog products), validates `reach_unit` / `view_duration_seconds` against product capabilities on create_media_buy, and emits `cost_per_click` plus goal-gated `reach + frequency` / `completed_views + completion_rate` on get_media_buy_delivery. Flips three capability-gated storyboards from `not_applicable` to applicable: clicks_buy_flow, reach_buy_flow, completed_views_buy_flow. Same forcing-function shape as #4654 and #4664. Manual rollup pending adcp-client#1818 (SDK seller-level field exposure). Refs: #4637, #4642, #4654, #4664, #4722. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(training-agent): add completed_views delivery emission + cost_per_click negative tests (PR 4766 review) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two new capability-gated scenarios in the contract pattern (#4637), both added to
sales-non-guaranteed.requires_scenarios, plus a training-agent fix that unblocks one of them.media_buy_seller/audience_buy_flowGated on
media_buy.audience_targetingpresence (not on the audience-sync specialism — option B from the RFC chat: a DSP can accept buyer-uploaded CRM lists intargeting_overlaywithout claiming the full audience-sync discovery/deletion lifecycle). Certifies:sync_audiencesreturns anaudience_idvalid as a binding target intargeting_overlay.audience_include[].create_media_buywithtargeting_overlay.audience_includereferencing the registered audience is accepted.create_media_buywithaudience_includereferencing an unregistered id is rejected (INVALID_REQUESTwitherror.fieldset to the literal JSONPath-lite pathpackages[0].targeting_overlay.audience_include[0]) — silent acceptance would mean the seller cannot actually restrict delivery to the buyer's audience.totals.impressionsfor the audience-targeted buy. Per-audience attribution breakdown is intentionally deferred (not first-class indelivery-metrics.jsontoday).Sibling to
media_buy_seller/performance_buy_flowon the audience side — the unbound-id rejection is the discriminating assertion, modeled on the event_source_id contract from #4642.media_buy_seller/event_dedup_flowGated on
media_buy.conversion_tracking.multi_source_event_dedupequals true. Certifies that the sameevent_idfrom two registered event sources attributes to one conversion, not two:sync_event_sourcesaccepts two sources (pixel + CAPI) sharing event_types.create_media_buyaccepts an event-kind goal whoseevent_sourcesarray contains BOTH ids.log_eventcalls with the SAMEevent_idfrom each source both succeed at the API level (dedup is attribution-time, not ingestion-time).get_media_buy_deliveryreportstotals.conversionsequal to 1, not 2.The training agent does not declare
multi_source_event_dedup: true(verified inv6-sales-platform.ts), so this scenario gradesnot_applicableagainst the training agent in CI — no training-agent fix is needed. The scenario only runs against sellers that explicitly opt in.Training-agent fix (audience_buy_flow only)
create_media_buynow rejectstargeting_overlay.audience_include/audience_excludeentries whoseaudience_idwas never registered viasync_audiences, withINVALID_REQUESTanderror.fieldset to the literal JSONPath-lite path. Mirrors the event_source_id validation pattern from #4654. Implementation:server/src/training-agent/audience-handlers.tswith the in-memory store +handleSyncAudiences.sync_audienceswired into the legacy/mcpHANDLER_MAP and the v6/sales/mcpAudiencePlatform.handleCreateMediaBuywalking each package'stargeting_overlay.audience_include[]andaudience_exclude[]against the audience store.Four unit tests in
server/tests/unit/training-agent.test.tscover the four contract paths: rejects/accepts × include/exclude.Refs
performance_buy_flowprecedentevent_source_idvalidation precedentTest plan
npm run build:schemascleannpm run build:compliance— all 12 lints pass; new scenarios indist/compliance/latest/protocols/media-buy/scenarios/npx tsc --project server/tsconfig.json --noEmitcleannpx vitest run server/tests/unit/training-agent.test.ts— 376/376 pass (includes 4 new audience contract tests + updated tool-count assertion)audience_buy_flowshould gradepassed,event_dedup_flowshould gradenot_applicableScenario design notes
error.fielduses literalvalue:only, notmatches:regex —field_valuedoesn't support regex and the field is JSONPath-lite percore/error.json.delivery-metrics.jsontoday. Both will land as follow-up scenarios gated on sub-capability bits.audience_excludefollows the same binding contract but is exercised only via unit tests, not a separate scenario phase — one positive + one negative onaudience_includeis the discriminating signal in YAML.audience-syncspecialism storyboard or itsindex.yaml.performance_buy_flow.yaml— reference only.multi_source_event_dedup— keeping it honest as a "doesn't claim what it can't do" reference is the right state.🤖 Generated with Claude Code