Skip to content

Commit 1849c18

Browse files
bokelleyclaude
andauthored
feat: promote audience_sync storyboard to canonical definitions (#2003)
* feat: promote audience_sync storyboard to canonical definitions Full audience lifecycle storyboard: account discovery, audience creation with hashed identifiers, and audience deletion via sync_audiences. Fixes schema_ref paths (media-buy/ not audience/) and doc_ref paths from the @adcp/client version. Adds track, required_tools, and platform_types fields for client consumption. 26 canonical protocol storyboards total. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: correct narrative — account must already exist, no sync_accounts fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add audio_platform to audience_sync platform_types Streaming audio platforms (Spotify, Pandora) have user-level audience data and support audience sync. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0f78057 commit 1849c18

4 files changed

Lines changed: 212 additions & 2 deletions

File tree

.changeset/large-lemons-bet.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
id: audience_sync
2+
version: "1.0.0"
3+
title: "Audience sync"
4+
category: audience_sync
5+
summary: "Full audience lifecycle: account discovery, audience creation with hashed identifiers, and audience deletion."
6+
track: audiences
7+
required_tools:
8+
- sync_audiences
9+
platform_types:
10+
- display_ad_server
11+
- social_platform
12+
- retail_media
13+
- audio_platform
14+
- dsp
15+
- pmax_platform
16+
17+
narrative: |
18+
You support audience syncing via the sync_audiences task. Buyers push first-party
19+
audience segments to your platform using hashed identifiers (emails, phones). Your
20+
platform matches those identifiers against your user graph and returns match rates.
21+
22+
This storyboard verifies the full audience lifecycle: discover an account to sync
23+
against, create a test audience with hashed identifiers, and clean up by deleting it.
24+
25+
agent:
26+
interaction_model: media_buy_seller
27+
capabilities:
28+
- sells_media
29+
- accepts_audiences
30+
examples:
31+
- "Retail media networks"
32+
- "Social platforms"
33+
- "DSPs with audience onboarding"
34+
35+
caller:
36+
role: buyer_agent
37+
example: "Scope3 (DSP)"
38+
39+
prerequisites:
40+
description: |
41+
The caller needs an active account on the seller platform. The account_setup phase
42+
discovers or creates the account relationship before syncing audiences.
43+
test_kit: "test-kits/acme-outdoor.yaml"
44+
45+
phases:
46+
- id: account_setup
47+
title: "Account setup"
48+
narrative: |
49+
Before syncing audiences, the buyer needs an account on the seller platform.
50+
This phase discovers an existing account via list_accounts or establishes one
51+
via sync_accounts.
52+
53+
steps:
54+
- id: discover_account
55+
title: "Discover or create account"
56+
narrative: |
57+
List existing accounts to find one suitable for audience sync. The account
58+
must already exist and be active. The account_id is captured for use in
59+
subsequent audience operations.
60+
task: list_accounts
61+
schema_ref: "account/list-accounts-request.json"
62+
response_schema_ref: "account/list-accounts-response.json"
63+
doc_ref: "/accounts/tasks/list_accounts"
64+
stateful: false
65+
expected: |
66+
Return at least one account with:
67+
- account_id: platform-assigned identifier
68+
- status: active (required for audience sync)
69+
70+
sample_request: {}
71+
72+
validations:
73+
- check: response_schema
74+
description: "Response matches list-accounts-response.json schema"
75+
- check: field_present
76+
path: "accounts[0].account_id"
77+
description: "At least one account exists with an account_id"
78+
79+
- id: audience_sync
80+
title: "Audience sync"
81+
narrative: |
82+
The buyer syncs a test audience containing hashed email and phone identifiers.
83+
The seller platform matches those identifiers and returns per-audience results
84+
including action (created/updated), status, and match rates.
85+
86+
After verifying creation, the test audience is deleted to clean up.
87+
88+
steps:
89+
- id: discover_audiences
90+
title: "Discover existing audiences"
91+
narrative: |
92+
Call sync_audiences with no audiences array to discover what audiences
93+
already exist on the platform for this account. This is a read-only
94+
discovery call.
95+
task: sync_audiences
96+
schema_ref: "media-buy/sync-audiences-request.json"
97+
response_schema_ref: "media-buy/sync-audiences-response.json"
98+
doc_ref: "/media-buy/task-reference/sync_audiences"
99+
comply_scenario: sync_audiences
100+
stateful: false
101+
expected: |
102+
Return existing audiences for the account (may be empty).
103+
Each audience should have an audience_id and status.
104+
105+
sample_request:
106+
account:
107+
brand:
108+
domain: "acmeoutdoor.com"
109+
operator: "pinnacle-agency.com"
110+
111+
validations:
112+
- check: response_schema
113+
description: "Response matches sync-audiences-response.json schema"
114+
115+
- id: create_audience
116+
title: "Create test audience"
117+
narrative: |
118+
Push a test audience with hashed email and phone identifiers. The seller
119+
matches identifiers against their user graph and returns the audience with
120+
action: created, match counts, and match rate.
121+
task: sync_audiences
122+
schema_ref: "media-buy/sync-audiences-request.json"
123+
response_schema_ref: "media-buy/sync-audiences-response.json"
124+
doc_ref: "/media-buy/task-reference/sync_audiences"
125+
comply_scenario: sync_audiences
126+
stateful: true
127+
expected: |
128+
Accept the audience and return:
129+
- audience_id: matches the submitted ID
130+
- action: created
131+
- status: active or processing
132+
- uploaded_count: number of identifiers received
133+
- matched_count: number of identifiers matched
134+
- effective_match_rate: ratio of matched to uploaded
135+
136+
sample_request:
137+
account:
138+
brand:
139+
domain: "acmeoutdoor.com"
140+
operator: "pinnacle-agency.com"
141+
audiences:
142+
- audience_id: "adcp-test-audience-001"
143+
name: "AdCP test audience"
144+
add:
145+
- hashed_email: "a000000000000000000000000000000000000000000000000000000000000000"
146+
- hashed_phone: "b000000000000000000000000000000000000000000000000000000000000000"
147+
148+
validations:
149+
- check: response_schema
150+
description: "Response matches sync-audiences-response.json schema"
151+
- check: field_present
152+
path: "audiences[0].audience_id"
153+
description: "Audience has an audience_id"
154+
- check: field_present
155+
path: "audiences[0].action"
156+
description: "Audience has an action (created or updated)"
157+
158+
- id: delete_audience
159+
title: "Delete test audience"
160+
narrative: |
161+
Clean up by deleting the test audience. The seller should acknowledge the
162+
deletion with action: deleted.
163+
task: sync_audiences
164+
schema_ref: "media-buy/sync-audiences-request.json"
165+
response_schema_ref: "media-buy/sync-audiences-response.json"
166+
doc_ref: "/media-buy/task-reference/sync_audiences"
167+
comply_scenario: sync_audiences
168+
stateful: true
169+
expected: |
170+
Delete the test audience and return:
171+
- audience_id: matches the test audience
172+
- action: deleted
173+
174+
sample_request:
175+
account:
176+
brand:
177+
domain: "acmeoutdoor.com"
178+
operator: "pinnacle-agency.com"
179+
audiences:
180+
- audience_id: "adcp-test-audience-001"
181+
delete: true
182+
183+
validations:
184+
- check: response_schema
185+
description: "Response matches sync-audiences-response.json schema"
186+
- check: field_present
187+
path: "audiences[0].action"
188+
description: "Audience deletion acknowledged with action field"

docs/storyboards/schema.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# id: string (unique identifier, e.g., "creative_template")
1414
# version: string (semver, e.g., "1.0.0")
1515
# title: string (human-readable title)
16-
# category: enum (capability_discovery | schema_validation | behavioral_analysis | error_compliance | creative_template | creative_ad_server | creative_sales_agent | creative_generative | creative_lifecycle | media_buy_seller | media_buy_guaranteed_approval | media_buy_non_guaranteed | media_buy_proposal_mode | media_buy_governance_escalation | media_buy_catalog_creative | media_buy_state_machine | campaign_governance_denied | campaign_governance_conditions | campaign_governance_delivery | signal_marketplace | signal_owned | social_platform | si_session | brand_rights | property_governance | content_standards)
16+
# category: enum (capability_discovery | schema_validation | behavioral_analysis | error_compliance | creative_template | creative_ad_server | creative_sales_agent | creative_generative | creative_lifecycle | media_buy_seller | media_buy_guaranteed_approval | media_buy_non_guaranteed | media_buy_proposal_mode | media_buy_governance_escalation | media_buy_catalog_creative | media_buy_state_machine | campaign_governance_denied | campaign_governance_conditions | campaign_governance_delivery | signal_marketplace | signal_owned | social_platform | si_session | brand_rights | property_governance | content_standards | audience_sync)
1717
# summary: string (one-line description for listings)
1818
# narrative: string (paragraph explaining the overall flow)
1919
#

server/tests/unit/storyboards.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
describe('listStoryboards', () => {
1313
it('returns all storyboards when no category filter', () => {
1414
const results = listStoryboards();
15-
expect(results.length).toBeGreaterThanOrEqual(25);
15+
expect(results.length).toBeGreaterThanOrEqual(26);
1616

1717
const ids = results.map((s) => s.id);
1818
expect(ids).toContain('capability_discovery');
@@ -40,6 +40,7 @@ describe('listStoryboards', () => {
4040
expect(ids).toContain('brand_rights');
4141
expect(ids).toContain('property_governance');
4242
expect(ids).toContain('content_standards');
43+
expect(ids).toContain('audience_sync');
4344
});
4445

4546
it('each summary has required fields', () => {
@@ -719,3 +720,22 @@ describe('media_buy_state_machine storyboard', () => {
719720
expect(kit!.id).toBe('acme_outdoor');
720721
});
721722
});
723+
724+
describe('audience_sync storyboard', () => {
725+
it('covers account discovery, audience creation, and deletion', () => {
726+
const sb = getStoryboard('audience_sync')!;
727+
expect(sb).toBeDefined();
728+
const phaseIds = sb.phases.map((p) => p.id);
729+
expect(phaseIds).toContain('account_setup');
730+
expect(phaseIds).toContain('audience_sync');
731+
const tasks = sb.phases.flatMap((p) => p.steps.map((s) => s.task));
732+
expect(tasks).toContain('list_accounts');
733+
expect(tasks).toContain('sync_audiences');
734+
});
735+
736+
it('resolves acme_outdoor test kit', () => {
737+
const kit = getTestKitForStoryboard('audience_sync');
738+
expect(kit).toBeDefined();
739+
expect(kit!.id).toBe('acme_outdoor');
740+
});
741+
});

0 commit comments

Comments
 (0)