@adcp/sdk 7.3.0 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  2. package/dist/lib/core/SingleAgentClient.d.ts +58 -14
  3. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  4. package/dist/lib/core/SingleAgentClient.js +68 -26
  5. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  6. package/dist/lib/errors/index.d.ts +8 -3
  7. package/dist/lib/errors/index.d.ts.map +1 -1
  8. package/dist/lib/errors/index.js +1 -1
  9. package/dist/lib/errors/index.js.map +1 -1
  10. package/dist/lib/index.d.ts +2 -2
  11. package/dist/lib/index.d.ts.map +1 -1
  12. package/dist/lib/index.js.map +1 -1
  13. package/dist/lib/protocols/index.d.ts +16 -6
  14. package/dist/lib/protocols/index.d.ts.map +1 -1
  15. package/dist/lib/protocols/index.js.map +1 -1
  16. package/dist/lib/protocols/responseSizeLimit.js +7 -0
  17. package/dist/lib/protocols/responseSizeLimit.js.map +1 -1
  18. package/dist/lib/registry/index.d.ts +36 -3
  19. package/dist/lib/registry/index.d.ts.map +1 -1
  20. package/dist/lib/registry/index.js +41 -5
  21. package/dist/lib/registry/index.js.map +1 -1
  22. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  23. package/dist/lib/server/create-adcp-server.d.ts +95 -0
  24. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  25. package/dist/lib/server/create-adcp-server.js +543 -35
  26. package/dist/lib/server/create-adcp-server.js.map +1 -1
  27. package/dist/lib/server/example-tld-guard.d.ts +9 -0
  28. package/dist/lib/server/example-tld-guard.d.ts.map +1 -0
  29. package/dist/lib/server/example-tld-guard.js +25 -0
  30. package/dist/lib/server/example-tld-guard.js.map +1 -0
  31. package/dist/lib/server/index.d.ts +5 -3
  32. package/dist/lib/server/index.d.ts.map +1 -1
  33. package/dist/lib/server/index.js +17 -4
  34. package/dist/lib/server/index.js.map +1 -1
  35. package/dist/lib/server/test-controller-bridge.d.ts +885 -1
  36. package/dist/lib/server/test-controller-bridge.d.ts.map +1 -1
  37. package/dist/lib/server/test-controller-bridge.js +1502 -2
  38. package/dist/lib/server/test-controller-bridge.js.map +1 -1
  39. package/dist/lib/testing/index.d.ts +2 -2
  40. package/dist/lib/testing/index.d.ts.map +1 -1
  41. package/dist/lib/testing/index.js +17 -3
  42. package/dist/lib/testing/index.js.map +1 -1
  43. package/dist/lib/types/core.generated.d.ts +1212 -20
  44. package/dist/lib/types/core.generated.d.ts.map +1 -1
  45. package/dist/lib/types/core.generated.js +1 -1
  46. package/dist/lib/types/inline-enums.generated.d.ts +6 -2
  47. package/dist/lib/types/inline-enums.generated.d.ts.map +1 -1
  48. package/dist/lib/types/inline-enums.generated.js +11 -5
  49. package/dist/lib/types/inline-enums.generated.js.map +1 -1
  50. package/dist/lib/types/schemas.generated.d.ts +26202 -26253
  51. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  52. package/dist/lib/types/schemas.generated.js +1353 -1300
  53. package/dist/lib/types/schemas.generated.js.map +1 -1
  54. package/dist/lib/types/tools.generated.d.ts +1082 -21
  55. package/dist/lib/types/tools.generated.d.ts.map +1 -1
  56. package/dist/lib/types/wellknown-schemas.generated.d.ts +2 -2
  57. package/dist/lib/validation/sync-creatives.d.ts +6 -6
  58. package/dist/lib/version.d.ts +3 -3
  59. package/dist/lib/version.js +3 -3
  60. package/examples/hello_creative_adapter_ad_server.ts +5 -0
  61. package/examples/hello_creative_adapter_template.ts +5 -0
  62. package/examples/hello_seller_adapter_guaranteed.ts +5 -0
  63. package/examples/hello_seller_adapter_non_guaranteed.ts +5 -0
  64. package/examples/hello_seller_adapter_social.ts +5 -0
  65. package/examples/hello_si_adapter_brand.ts +5 -0
  66. package/package.json +2 -2
  67. package/skills/call-adcp-agent/SKILL.md +1 -0
@@ -17,8 +17,124 @@
17
17
  * Sellers provide a `getSeededProducts` callback that returns the list the
18
18
  * SDK should merge — which lets the same wiring work whether the backing
19
19
  * store is in-memory, Postgres, Redis, or a mock.
20
+ *
21
+ * ## Scope of verification (storyboard pass through this bridge)
22
+ *
23
+ * A storyboard run that succeeds because seeded fixtures were merged into
24
+ * the response verifies **protocol conformance against fixture data**:
25
+ * wire shape, error envelopes, idempotency, signed-request handling,
26
+ * sandbox stamping. It does **not** verify that the seller's adapter
27
+ * against the real upstream (e.g., social / search / programmatic
28
+ * inventory APIs) is working — the upstream response is shadowed by the
29
+ * post-handler merge.
30
+ *
31
+ * Treat this bridge as the conformance equivalent of a recorded-fixtures
32
+ * unit test, not an end-to-end integration test. Sellers should still
33
+ * exercise their adapters against a real (or sandbox) upstream OAuth tier
34
+ * separately; the typical pattern is a CLI runner pointed at a deployed
35
+ * sandbox URL with live credentials. The two together — storyboard-via-
36
+ * bridge plus live-OAuth runner — give wire conformance and adapter
37
+ * health respectively. See adcp-client#1775 for the cross-repo
38
+ * coordination on making bridge participation visible in storyboard
39
+ * run records.
40
+ *
41
+ * ## Adopter responsibilities
42
+ *
43
+ * **`resolveAccount` is the trust boundary.** The dispatcher's sandbox
44
+ * gate is "request carries a sandbox marker AND (resolved account is
45
+ * sandbox OR no account was resolved)." If you deploy a server with this
46
+ * bridge registered but no `resolveAccount` configured, a buyer can stamp
47
+ * `context.sandbox: true` on a request and trigger the merge. That's the
48
+ * intended behavior for storyboard runners with no account scoping, but
49
+ * means **production bindings must always configure `resolveAccount`** —
50
+ * otherwise the buyer-supplied sandbox marker is the only gate.
51
+ *
52
+ * **Multi-tenant isolation is the adopter's job.** Callbacks receive
53
+ * `ctx.account` and must key their fixture store on it. The SDK does no
54
+ * defensive cross-check between the account on the response entries and
55
+ * the `ctx.account` that asked for them. A sloppy session-store keying
56
+ * can return tenant A's fixtures to tenant B; nothing in this module
57
+ * will notice. Treat fixture stores like any other multi-tenant data
58
+ * layer.
59
+ */
60
+ import type { Product, GetProductsResponse, Account, ListAccountsResponse, ListCreativesResponse, GetMediaBuysResponse, GetMediaBuyDeliveryResponse, ListCreativeFormatsResponse, Format, GetAccountFinancialsResponse, GetAccountFinancialsSuccess, GetAccountFinancialsRequest, PropertyList, ListPropertyListsResponse, GetPropertyListResponse, GetPropertyListRequest, CollectionList, ListCollectionListsResponse, GetCollectionListResponse, GetCollectionListRequest, ContentStandards, ListContentStandardsResponse, GetContentStandardsResponse, GetContentStandardsRequest, GetSignalsResponse, GetCreativeDeliveryResponse, GetCreativeFeaturesResponse, CreativeFeatureResult, SIGetOfferingRequest, SIGetOfferingResponse } from '../types/tools.generated';
61
+ import type { GetBrandIdentityRequest, GetBrandIdentityResponse, GetBrandIdentitySuccess, GetRightsResponse, GetRightsSuccess } from '../types/core.generated';
62
+ /**
63
+ * Seeded signal entry — the inline element type of `GetSignalsResponse.signals`.
64
+ * Derived via lookup so it stays in lockstep with the generated wire schema.
65
+ * Dedup key: `signal_id`.
20
66
  */
21
- import type { Product, GetProductsResponse } from '../types/tools.generated';
67
+ export type SeededSignal = GetSignalsResponse['signals'][number];
68
+ /**
69
+ * Seeded creative-delivery entry — the inline element type of
70
+ * `GetCreativeDeliveryResponse.creatives`. Derived via lookup so it stays in
71
+ * lockstep with the generated wire schema. Dedup key: `creative_id`.
72
+ */
73
+ export type SeededCreativeDelivery = GetCreativeDeliveryResponse['creatives'][number];
74
+ /**
75
+ * Seeded creative-feature result — alias for the per-feature evaluation entry.
76
+ * `get_creative_features` returns a `results: CreativeFeatureResult[]` array
77
+ * on the success arm; the bridge seeds at the feature granularity (not the
78
+ * whole envelope) so adopters can override specific feature scores while the
79
+ * handler computes everything else. Dedup key: `feature_id`.
80
+ */
81
+ export type SeededCreativeFeature = CreativeFeatureResult;
82
+ /**
83
+ * Seeded creative entry — the inline element type of `ListCreativesResponse.creatives`.
84
+ * Derived via lookup so it stays in lockstep with the generated wire schema.
85
+ */
86
+ export type SeededCreative = ListCreativesResponse['creatives'][number];
87
+ /**
88
+ * Seeded media-buy entry — the inline element type of `GetMediaBuysResponse.media_buys`.
89
+ * Derived via lookup so it stays in lockstep with the generated wire schema.
90
+ */
91
+ export type SeededMediaBuy = GetMediaBuysResponse['media_buys'][number];
92
+ /**
93
+ * Seeded account-financials entry. The `get_account_financials` response is a
94
+ * singleton (one account, one envelope), so the bridge callback returns an
95
+ * array keyed by `account.account_id` and the framework picks the entry
96
+ * matching the request's `account` reference — replacing the handler response
97
+ * for that account when matched. Storyboards seeding financials for an account
98
+ * under test see their fixture; un-seeded accounts pass through to the handler.
99
+ */
100
+ export type SeededAccountFinancials = GetAccountFinancialsSuccess;
101
+ /**
102
+ * Seeded media-buy-delivery entry — the inline element type of
103
+ * `GetMediaBuyDeliveryResponse.media_buy_deliveries`. Derived via lookup so it
104
+ * stays in lockstep with the generated wire schema.
105
+ */
106
+ export type SeededMediaBuyDelivery = GetMediaBuyDeliveryResponse['media_buy_deliveries'][number];
107
+ /**
108
+ * Seeded brand-identity record. The `get_brand_identity` response is a
109
+ * singleton keyed by `brand_id` (request requires `brand_id`); the bridge
110
+ * picks the seeded fixture matching `request.brand_id` and replaces the
111
+ * handler response body, preserving framework-managed `context` / `ext`.
112
+ */
113
+ export type SeededBrandIdentity = GetBrandIdentitySuccess;
114
+ /**
115
+ * Seeded rights entry — the inline element type of `GetRightsSuccess.rights`.
116
+ * `get_rights` is a discovery / search tool (natural-language `query`), so the
117
+ * response carries an array; the bridge appends seeded entries with dedup by
118
+ * `rights_id`.
119
+ */
120
+ export type SeededRight = GetRightsSuccess['rights'][number];
121
+ /**
122
+ * Seeded SI offering record. The `si_get_offering` response is a singleton
123
+ * keyed by `offering_id` (request requires `offering_id`). Although the
124
+ * sponsored-intelligence flow has session-keyed state in subsequent tools
125
+ * (`si_initiate_session` issues a session, `si_send_message` advances it),
126
+ * `si_get_offering` itself is a stateless catalog lookup — the response's
127
+ * `offering_token` SEEDS a future session but the lookup does not consume
128
+ * one. That's why the singleton-replace pattern fits cleanly here while
129
+ * the session-stateful SI tools are intentionally out of scope.
130
+ */
131
+ export type SeededSiOffering = SIGetOfferingResponse;
132
+ /**
133
+ * The shape of `GetMediaBuyDeliveryResponse.aggregated_totals` (optional on the
134
+ * wire — recomputed by the bridge from the merged delivery array per the
135
+ * documented policy).
136
+ */
137
+ type AggregatedTotals = NonNullable<GetMediaBuyDeliveryResponse['aggregated_totals']>;
22
138
  /**
23
139
  * Context passed to {@link TestControllerBridge.getSeededProducts}.
24
140
  *
@@ -38,6 +154,16 @@ export interface TestControllerBridgeContext<TAccount = unknown> {
38
154
  * Set on `AdcpServerConfig.testController`; when absent, behavior is
39
155
  * unchanged. The bridge is opt-in via the presence of `getSeededProducts`
40
156
  * — omit it to hold seeded state without changing response shape.
157
+ *
158
+ * **Construction-time misconfiguration warn.** `createAdcpServer` emits a
159
+ * one-shot `logger.warn` at construction when this bridge is registered
160
+ * without either `resolveAccount` or `resolveAccountFromAuth` configured —
161
+ * the dispatch-time sandbox gate's account-side check has no teeth in that
162
+ * setup, so caller-supplied `account.sandbox` becomes the only line of
163
+ * defense against fixture leakage. Storyboard runners without account
164
+ * scoping can ignore the warning; production bindings should wire a
165
+ * resolver. See `AdcpServerConfig.testController` JSDoc § "Security —
166
+ * trust boundary" and `adcp-client#1784`.
41
167
  */
42
168
  export interface TestControllerBridge<TAccount = unknown> {
43
169
  /**
@@ -54,6 +180,220 @@ export interface TestControllerBridge<TAccount = unknown> {
54
180
  * bridge when it has a resolved non-sandbox account, belt-and-suspenders).
55
181
  */
56
182
  getSeededProducts?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<Product[]> | Product[];
183
+ /**
184
+ * Retrieve seeded creatives for the current request. Returned entries are
185
+ * appended to the handler's `list_creatives` response (`creatives` array);
186
+ * on `creative_id` collision the seeded entry wins. Empty array (or
187
+ * `undefined`) when nothing is seeded. Same sandbox gating contract as
188
+ * {@link TestControllerBridge.getSeededProducts}.
189
+ */
190
+ getSeededCreatives?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededCreative[]> | SeededCreative[];
191
+ /**
192
+ * Retrieve seeded media buys for the current request. Returned entries are
193
+ * appended to the handler's `get_media_buys` response (`media_buys` array);
194
+ * on `media_buy_id` collision the seeded entry wins. Same sandbox gating
195
+ * contract as {@link TestControllerBridge.getSeededProducts}.
196
+ */
197
+ getSeededMediaBuys?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededMediaBuy[]> | SeededMediaBuy[];
198
+ /**
199
+ * Retrieve seeded media-buy delivery snapshots. Returned entries are merged
200
+ * into the handler's `get_media_buy_delivery` response (`media_buy_deliveries`
201
+ * array); on `media_buy_id` collision the seeded entry wins, matching the
202
+ * precedent set by `mergeSeededMediaBuys` / `mergeSeededCreatives` /
203
+ * `mergeSeededAccounts` — storyboards seed deliberately, so a seeded fixture
204
+ * for an existing `media_buy_id` is an explicit author override.
205
+ *
206
+ * After the merge, the response's `aggregated_totals` block is recomputed
207
+ * from the merged per-delivery `totals` so `media_buy_count` /
208
+ * `impressions` / `spend` stay wire-correct. See
209
+ * {@link recomputeAggregatedTotals} for the recomputation policy. Same
210
+ * sandbox gating contract as {@link TestControllerBridge.getSeededProducts}.
211
+ */
212
+ getSeededMediaBuyDelivery?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededMediaBuyDelivery[]> | SeededMediaBuyDelivery[];
213
+ /**
214
+ * Retrieve seeded accounts for the current request. Returned entries are
215
+ * appended to the handler's `list_accounts` response (`accounts` array);
216
+ * on `account_id` collision the seeded entry wins. Same sandbox gating
217
+ * contract as {@link TestControllerBridge.getSeededProducts}.
218
+ */
219
+ getSeededAccounts?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<Account[]> | Account[];
220
+ /**
221
+ * Retrieve seeded account-financials records. Unlike the other seeded
222
+ * collections, `get_account_financials` returns a singleton response —
223
+ * one account, one envelope. The bridge callback returns an array of
224
+ * seeded records keyed by `account.account_id`; the framework picks the
225
+ * entry whose `account_id` matches the resolved request account and
226
+ * replaces the handler response's financials payload with that fixture.
227
+ * Framework-managed `context` and `ext` from the handler response are
228
+ * PRESERVED across the replace — the seeded fixture is authoritative on
229
+ * the financials body (spend / period / account / currency / ...) but not
230
+ * on the response envelope (the fixture can't know the current request's
231
+ * `request_id` / `adcp_version` echo).
232
+ *
233
+ * When no seeded entry matches, the handler response passes through
234
+ * unchanged. Duplicate seeded entries with the same `account.account_id`
235
+ * are warn-and-dropped during validation (first occurrence wins).
236
+ *
237
+ * Same sandbox gating contract as {@link TestControllerBridge.getSeededProducts}.
238
+ */
239
+ getSeededAccountFinancials?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededAccountFinancials[]> | SeededAccountFinancials[];
240
+ /**
241
+ * Retrieve seeded creative formats. Returned entries are appended to the
242
+ * handler's `list_creative_formats` response (`formats` array); on
243
+ * collision (matched by canonical `format_id.agent_url` + `format_id.id`)
244
+ * the seeded entry wins. Same sandbox gating contract as
245
+ * {@link TestControllerBridge.getSeededProducts}.
246
+ *
247
+ * Composes with `SalesPlatform.listCreativeFormats` /
248
+ * `CreativeBuilderPlatform.listCreativeFormats` — the adapter handler
249
+ * runs first, then this bridge supplements. Not a replacement for those
250
+ * handler-side hooks; storyboards use this seam to inject test-only
251
+ * formats without rewriting the adapter.
252
+ */
253
+ getSeededCreativeFormats?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<Format[]> | Format[];
254
+ /**
255
+ * Retrieve seeded property lists. The same callback feeds both
256
+ * `list_property_lists` (append-merge into `lists: PropertyList[]`, dedup
257
+ * by `list_id` with seeded winning on collision) and `get_property_list`
258
+ * (singleton — pick the entry whose `list_id` matches the request's
259
+ * `list_id` and REPLACE the handler response's `list` field; the handler's
260
+ * auxiliary fields — `identifiers`, `pagination`, `resolved_at`,
261
+ * `cache_valid_until`, `coverage_gaps`, `context`, `ext` — pass through
262
+ * verbatim because they depend on request-time pagination / resolve
263
+ * params that a static fixture can't know).
264
+ *
265
+ * Unblocks the `property-lists` and `governance-aware-seller` storyboards
266
+ * (property catalog seeding) on platform-proxy sellers whose state of
267
+ * record is upstream. Same sandbox gating contract as
268
+ * {@link TestControllerBridge.getSeededProducts}.
269
+ */
270
+ getSeededPropertyLists?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<PropertyList[]> | PropertyList[];
271
+ /**
272
+ * Retrieve seeded content-standards configurations. The same callback feeds
273
+ * both `list_content_standards` (success arm `standards: ContentStandards[]`,
274
+ * append-merge with seeded winning on `standards_id` collision) and
275
+ * `get_content_standards` (singleton — pick by `standards_id` and replace
276
+ * the `ContentStandards` body). Seeded fixture is authoritative on the
277
+ * `ContentStandards` body. Framework-managed envelope fields (`context`,
278
+ * `ext`) round-trip from the handler — matches the precedent set by
279
+ * {@link replaceAccountFinancialsIfSeeded}.
280
+ *
281
+ * Unblocks the `content-standards` storyboard. Same sandbox gating contract
282
+ * as {@link TestControllerBridge.getSeededProducts}.
283
+ */
284
+ getSeededContentStandards?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<ContentStandards[]> | ContentStandards[];
285
+ /**
286
+ * Retrieve seeded collection lists. The same callback feeds both
287
+ * `list_collection_lists` (append-merge into `lists: CollectionList[]`,
288
+ * dedup by `list_id` with seeded winning on collision) and
289
+ * `get_collection_list` (singleton — pick by `list_id` and replace the
290
+ * `list` field; the handler's `collections`, `pagination`, `resolved_at`,
291
+ * `cache_valid_until`, `coverage_gaps`, `context`, `ext` pass through
292
+ * verbatim).
293
+ *
294
+ * Unblocks the `collection-lists` storyboard (program-level brand safety
295
+ * via IMDb/Gracenote/EIDR IDs). Same sandbox gating contract as
296
+ * {@link TestControllerBridge.getSeededProducts}.
297
+ */
298
+ getSeededCollectionLists?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<CollectionList[]> | CollectionList[];
299
+ /**
300
+ * Retrieve seeded signals for the current request. Returned entries are
301
+ * appended to the handler's `get_signals` response (`signals` array); on
302
+ * `signal_id` collision the seeded entry wins. The signal_id-keyed dedup
303
+ * works uniformly across `signal-marketplace` and `signal-owned`
304
+ * specialisms — both use the same response envelope; the discriminator
305
+ * lives on each entry's `signal_type` field.
306
+ *
307
+ * `get_signals` does not carry a `query_summary` block (per AdCP 3.0.11).
308
+ * Pagination is not recomputed on the merge. `PaginationResponse.total_count`
309
+ * is optional; recomputing it on partial pages would mis-represent the
310
+ * cross-page total (the handler may have served page 1 of N, and the seeded
311
+ * fixture sits outside that pagination context). Storyboards asserting on
312
+ * totals should seed the handler's response, not the post-merge envelope.
313
+ * Same sandbox gating contract as
314
+ * {@link TestControllerBridge.getSeededProducts}.
315
+ */
316
+ getSeededSignals?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededSignal[]> | SeededSignal[];
317
+ /**
318
+ * Retrieve seeded creative-delivery entries for `get_creative_delivery`.
319
+ * Returned entries are appended to the handler response's `creatives` array;
320
+ * on `creative_id` collision the seeded entry wins (storyboards seed
321
+ * deliberately — a seeded fixture for an existing creative_id is an explicit
322
+ * author override, matching the precedent set by other list-shaped bridges).
323
+ *
324
+ * After the merge, `pagination.total` (when set by the handler) is updated
325
+ * by the count of new non-colliding seeded entries. `get_creative_delivery`
326
+ * does not carry a `query_summary` block, and there is no top-level
327
+ * aggregated-totals envelope (unlike `get_media_buy_delivery`), so no other
328
+ * recomputation is performed.
329
+ *
330
+ * Unblocks the `creative-template` / `creative-generative` /
331
+ * `creative-ad-server` delivery-readback storyboards. Same sandbox gating
332
+ * contract as {@link TestControllerBridge.getSeededProducts}.
333
+ */
334
+ getSeededCreativeDelivery?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededCreativeDelivery[]> | SeededCreativeDelivery[];
335
+ /**
336
+ * Retrieve seeded creative-feature results for `get_creative_features`.
337
+ * `get_creative_features` returns a `oneOf` envelope — the success arm
338
+ * carries `results: CreativeFeatureResult[]` (one entry per evaluated
339
+ * feature). The bridge seeds at the per-feature granularity: returned
340
+ * entries are merged into the success arm's `results` array, dedup by
341
+ * `feature_id`, seeded wins on collision. Adopters can override specific
342
+ * feature scores (e.g., brand-safety policy outcomes) without rewriting
343
+ * the entire evaluation handler.
344
+ *
345
+ * When the handler returned the error arm (`errors[]`), the bridge is a
346
+ * no-op — error envelopes pass through unchanged. When the handler
347
+ * returned the success arm, framework-managed `context` / `ext` round-trip
348
+ * from the handler verbatim. Same sandbox gating contract as
349
+ * {@link TestControllerBridge.getSeededProducts}.
350
+ */
351
+ getSeededCreativeFeatures?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededCreativeFeature[]> | SeededCreativeFeature[];
352
+ /**
353
+ * Retrieve seeded brand-identity records. `get_brand_identity` returns a
354
+ * singleton response keyed by `brand_id` (request requires `brand_id`).
355
+ * The callback returns an array of seeded fixtures keyed by their own
356
+ * `brand_id`; the framework picks the entry matching the request and
357
+ * REPLACES the handler response body with that fixture. Framework-managed
358
+ * envelope fields (`context`, `ext`) round-trip from the handler — matches
359
+ * the precedent set by {@link replaceContentStandardsIfSeeded} and
360
+ * {@link replaceAccountFinancialsIfSeeded}.
361
+ *
362
+ * When no seeded entry matches, the handler response passes through. Same
363
+ * sandbox gating contract as {@link TestControllerBridge.getSeededProducts}.
364
+ * Unblocks the `brand-rights` storyboard identity-discovery phase.
365
+ */
366
+ getSeededBrandIdentity?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededBrandIdentity[]> | SeededBrandIdentity[];
367
+ /**
368
+ * Retrieve seeded rights entries. `get_rights` is a discovery / search tool
369
+ * (natural-language `query`); the response carries an array of matching
370
+ * rights, so the bridge follows the array-collection pattern. Returned
371
+ * entries are appended to the handler's `rights` array (success arm) with
372
+ * dedup by `rights_id` — on collision the seeded entry wins, matching the
373
+ * precedent set by `getSeededProducts` (storyboards seed deliberately).
374
+ *
375
+ * Same sandbox gating contract as {@link TestControllerBridge.getSeededProducts}.
376
+ * Unblocks the `brand-rights` storyboard rights-discovery phase.
377
+ */
378
+ getSeededRights?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededRight[]> | SeededRight[];
379
+ /**
380
+ * Retrieve seeded SI offering records. `si_get_offering` returns a singleton
381
+ * response keyed by `offering_id` (request requires `offering_id`). The
382
+ * callback returns an array of seeded fixtures, each carrying an
383
+ * `offering.offering_id`; the framework picks the entry matching the
384
+ * request and REPLACES the handler response with that fixture. Handler's
385
+ * `context` and `ext` round-trip.
386
+ *
387
+ * Stateless lookup: although the broader sponsored-intelligence flow has
388
+ * session-keyed state in `si_initiate_session` / `si_send_message`, the
389
+ * `si_get_offering` response only PRODUCES an `offering_token` for a
390
+ * future session — it does not consume one. The singleton-replace pattern
391
+ * fits here; the session-stateful tools are out of scope by design.
392
+ *
393
+ * Same sandbox gating contract as {@link TestControllerBridge.getSeededProducts}.
394
+ * Unblocks the `sponsored-intelligence` storyboard offering-lookup phase.
395
+ */
396
+ getSeededSiOffering?: (ctx: TestControllerBridgeContext<TAccount>) => Promise<SeededSiOffering[]> | SeededSiOffering[];
57
397
  }
58
398
  /**
59
399
  * Sandbox-request predicate. Reads the spec's two canonical sandbox markers:
@@ -81,6 +421,23 @@ export declare function isSandboxRequest(input: Record<string, unknown>): boolea
81
421
  * handler explicitly declared `sandbox: false` (which stays authoritative
82
422
  * — a handler that has already decided the request is non-sandbox
83
423
  * shouldn't be overridden by the bridge).
424
+ *
425
+ * **Submitted-arm short-circuit.** `get_products` formally permits an
426
+ * async Submitted arm per `schemas/cache/3.0.11/media-buy/get-products-async-response-submitted.json`
427
+ * (queued custom/bespoke curation). The dispatcher routes
428
+ * `{ status: 'submitted', task_id }` handler returns through
429
+ * `wrapSubmittedEnvelope`, but the bridge merge then receives the
430
+ * unwrapped Submitted body from `formatted.structuredContent`. Without
431
+ * this guard, the merge would spread `products: [...]` into that body
432
+ * and produce a `{ status: 'submitted', task_id, products: [...], sandbox: true }`
433
+ * hybrid that violates the wire schema. Detect the Submitted shape and
434
+ * return the handler response reference-equal so the dispatcher's
435
+ * skip-on-reference-equality wrap-avoidance kicks in.
436
+ *
437
+ * None of the other 12 bridged read tools have a formal Submitted arm
438
+ * in 3.0.11 per `schemas/cache/3.0.11/core/async-response-data.json`;
439
+ * this guard is `get_products`-specific defense rather than a uniform
440
+ * pattern across helpers.
84
441
  */
85
442
  export declare function mergeSeededProductsIntoResponse(response: GetProductsResponse, seeded: readonly Product[]): GetProductsResponse;
86
443
  /**
@@ -96,6 +453,427 @@ export declare function mergeSeededProductsIntoResponse(response: GetProductsRes
96
453
  export declare function filterValidSeededProducts(raw: unknown, logger?: {
97
454
  warn: (message: string, meta?: Record<string, unknown>) => void;
98
455
  }): Product[];
456
+ /**
457
+ * Validate seeded creatives. Drops entries that are not plain objects or are
458
+ * missing a non-empty string `creative_id` — matches the products contract
459
+ * (a missing identifier collides on `undefined === undefined` when deduping).
460
+ */
461
+ export declare function filterValidSeededCreatives(raw: unknown, logger?: {
462
+ warn: (message: string, meta?: Record<string, unknown>) => void;
463
+ }): SeededCreative[];
464
+ /**
465
+ * Validate seeded media buys. Drops entries missing a non-empty string
466
+ * `media_buy_id`.
467
+ */
468
+ export declare function filterValidSeededMediaBuys(raw: unknown, logger?: {
469
+ warn: (message: string, meta?: Record<string, unknown>) => void;
470
+ }): SeededMediaBuy[];
471
+ /**
472
+ * Validate seeded media-buy-delivery snapshots. Drops entries missing a
473
+ * non-empty string `media_buy_id` — the dedup key against the handler set.
474
+ */
475
+ export declare function filterValidSeededMediaBuyDeliveries(raw: unknown, logger?: {
476
+ warn: (message: string, meta?: Record<string, unknown>) => void;
477
+ }): SeededMediaBuyDelivery[];
478
+ /**
479
+ * Validate seeded accounts. Drops entries missing a non-empty string `account_id`.
480
+ */
481
+ export declare function filterValidSeededAccounts(raw: unknown, logger?: {
482
+ warn: (message: string, meta?: Record<string, unknown>) => void;
483
+ }): Account[];
484
+ /**
485
+ * Validate seeded account-financials records. Drops entries whose `account`
486
+ * field is not a `{ account_id: string }`-shaped object (`AccountReference`
487
+ * carries `account_id` on the operator-resolved variant; the bridge keys on
488
+ * that field to match against the request's account).
489
+ *
490
+ * Also drops duplicate entries by `account.account_id` (first occurrence
491
+ * wins, matching the array-collection helpers' on-collision-seeded-wins
492
+ * contract — the "first" seeded entry in iteration order is authoritative).
493
+ * A fixture array with two entries for the same `account_id` is almost
494
+ * always a seed-store bug; warn-and-drop surfaces it instead of silently
495
+ * picking whichever happened to come first in iteration order.
496
+ */
497
+ export declare function filterValidSeededAccountFinancials(raw: unknown, logger?: {
498
+ warn: (message: string, meta?: Record<string, unknown>) => void;
499
+ }): SeededAccountFinancials[];
500
+ /**
501
+ * Validate seeded creative formats. Drops entries whose `format_id` is not a
502
+ * `{ agent_url: string, id: string }`-shaped object — both fields are
503
+ * required to dedupe (and to canonicalize per the URL canonicalization rules
504
+ * in the AdCP spec).
505
+ */
506
+ export declare function filterValidSeededCreativeFormats(raw: unknown, logger?: {
507
+ warn: (message: string, meta?: Record<string, unknown>) => void;
508
+ }): Format[];
509
+ /**
510
+ * Merge seeded creatives into a `list_creatives` response. Existing creatives
511
+ * come first; seeded entries append after deduping by `creative_id`. On
512
+ * collision the seeded entry wins. Stamps `sandbox: true` unless the handler
513
+ * explicitly declared `sandbox: false`. Returns a NEW response object.
514
+ *
515
+ * Also updates `query_summary.returned` to match the final array length and
516
+ * `query_summary.total_matching` to `handler.total_matching + (seeded entries
517
+ * that did NOT collide with the handler set)`. Storyboards that assert on
518
+ * counts see the merged totals, not the handler's pre-merge counts. Mirror
519
+ * field on `pagination.total_count` is updated by the same delta when the
520
+ * handler set it.
521
+ */
522
+ export declare function mergeSeededCreativesIntoResponse(response: ListCreativesResponse, seeded: readonly SeededCreative[]): ListCreativesResponse;
523
+ /**
524
+ * Merge seeded media buys into a `get_media_buys` response. Existing media
525
+ * buys come first; seeded entries append after deduping by `media_buy_id`.
526
+ * On collision the seeded entry wins. Returns a NEW response object.
527
+ *
528
+ * `get_media_buys` does NOT carry a `query_summary` block (per AdCP 3.0.11);
529
+ * it exposes its count via `pagination.total_count` (optional). When the
530
+ * handler set `pagination.total_count`, it's incremented by the count of
531
+ * new (non-colliding) seeded entries so the merged response stays
532
+ * internally consistent. Handlers that left `total_count` off pass through
533
+ * unchanged.
534
+ */
535
+ export declare function mergeSeededMediaBuysIntoResponse(response: GetMediaBuysResponse, seeded: readonly SeededMediaBuy[]): GetMediaBuysResponse;
536
+ /**
537
+ * Recompute `aggregated_totals` from a merged `media_buy_deliveries` array.
538
+ *
539
+ * The wire schema makes `aggregated_totals.impressions` / `spend` /
540
+ * `media_buy_count` REQUIRED, so once the bridge changes the delivery list it
541
+ * MUST rewrite the totals — otherwise `media_buy_count` is stale and the
542
+ * impressions/spend sums no longer reflect the merged set.
543
+ *
544
+ * Policy (see VALIDATE-YOUR-AGENT.md "Platform-proxy sellers"):
545
+ * - Sum-derived (required): `impressions`, `spend`, `media_buy_count`.
546
+ * - Sum-derived (optional): `clicks`, `completed_views`, `views`,
547
+ * `conversions`, `conversion_value` — recomputed ONLY when EVERY merged
548
+ * delivery populates the field on its `totals`. Otherwise fall back to
549
+ * the handler's value (or omit if the handler omitted).
550
+ * - Derived ratios: `roas` (`conversion_value / spend`),
551
+ * `completion_rate` (`completed_views / impressions`),
552
+ * `cost_per_acquisition` (`spend / conversions`). Recomputed ONLY when
553
+ * both input fields recomputed AND the divisor is non-zero. Otherwise
554
+ * fall back to the handler's value (or omit).
555
+ * - Pass-through (not derivable from per-delivery `totals`): `reach`,
556
+ * `reach_unit`, `frequency`, `new_to_brand_rate`. The handler's values
557
+ * survive verbatim.
558
+ *
559
+ * Empty merged array → `{ impressions: 0, spend: 0, media_buy_count: 0 }` +
560
+ * any pass-through the handler set. Divide-by-zero guards keep ratios omitted
561
+ * rather than producing `Infinity` / `NaN` values that would fail validation.
562
+ *
563
+ * Pure helper — testable in isolation, no I/O.
564
+ */
565
+ export declare function recomputeAggregatedTotals(deliveries: readonly SeededMediaBuyDelivery[], handlerAggregated: AggregatedTotals | undefined): AggregatedTotals;
566
+ /**
567
+ * Merge seeded media-buy-delivery entries into a `get_media_buy_delivery`
568
+ * response. Existing handler entries come first; seeded entries append after
569
+ * deduping by `media_buy_id`. On collision the SEEDED entry wins, matching the
570
+ * precedent set by `mergeSeededMediaBuys` / `mergeSeededCreatives` /
571
+ * `mergeSeededAccounts` — storyboards seed deliberately, so a seeded fixture
572
+ * for an existing `media_buy_id` is an explicit author override.
573
+ *
574
+ * After merging, `aggregated_totals` is recomputed via
575
+ * {@link recomputeAggregatedTotals} so `media_buy_count` / `impressions` /
576
+ * `spend` reflect the merged set instead of the handler's pre-merge values.
577
+ *
578
+ * Returns a NEW response object — the handler's singleton envelope fields
579
+ * (`reporting_period`, `currency`, `attribution_window`, `errors`, `sandbox`,
580
+ * `context`, `ext`, plus webhook-only `notification_type` / `partial_data` /
581
+ * `sequence_number` / etc.) pass through verbatim. Stamps `sandbox: true`
582
+ * unless the handler explicitly declared `sandbox: false`.
583
+ */
584
+ export declare function mergeSeededMediaBuyDeliveryIntoResponse(response: GetMediaBuyDeliveryResponse, seeded: readonly SeededMediaBuyDelivery[]): GetMediaBuyDeliveryResponse;
585
+ /**
586
+ * Merge seeded accounts into a `list_accounts` response. Existing accounts
587
+ * come first; seeded entries append after deduping by `account_id`. On
588
+ * collision the seeded entry wins. Returns a NEW response object.
589
+ *
590
+ * `list_accounts` exposes its count via `pagination.total_count` (optional,
591
+ * same as `get_media_buys`). When the handler set it, it's incremented by
592
+ * the count of new (non-colliding) seeded entries.
593
+ */
594
+ export declare function mergeSeededAccountsIntoResponse(response: ListAccountsResponse, seeded: readonly Account[]): ListAccountsResponse;
595
+ /**
596
+ * Pick a seeded `get_account_financials` fixture matching the request's
597
+ * account. Unlike the array-collection helpers, this returns the SINGLE
598
+ * matched envelope or `undefined` — `get_account_financials` is a singleton
599
+ * response and "merge" reduces to "replace if the request's account matches
600
+ * a seeded fixture".
601
+ *
602
+ * Matching honors both the raw request and the resolved account from
603
+ * `resolveAccount`. `AccountReference` is a discriminated union — the
604
+ * operator-resolved variant carries `account_id` on the request, but the
605
+ * brand+operator variants do not. When the framework has already resolved
606
+ * the request to an account, prefer that resolved id so seeded fixtures
607
+ * find their match regardless of which `AccountReference` variant the
608
+ * buyer sent.
609
+ *
610
+ * @param resolvedAccountId - The `account_id` from `ctx.account` after
611
+ * `resolveAccount` ran. Pass `undefined` when no account was resolved
612
+ * (singleton-tenant adopters) — the function falls back to the request's
613
+ * `account.account_id` field, which preserves the original semantics for
614
+ * adopters who don't wire `resolveAccount`.
615
+ */
616
+ export declare function pickSeededAccountFinancialsForRequest(request: GetAccountFinancialsRequest | Record<string, unknown>, seeded: readonly SeededAccountFinancials[], resolvedAccountId?: string): SeededAccountFinancials | undefined;
617
+ /**
618
+ * Replace a `get_account_financials` response when a seeded fixture matches
619
+ * the request's account. The seeded fixture is authoritative on the
620
+ * financials payload (spend, period, account, currency, ...). The handler's
621
+ * `context` and `ext` are framework-managed (`context` echoes
622
+ * `adcp_version` / `request_id`; `ext` carries adopter passthrough) and
623
+ * MUST be preserved across the replace — wire-correct context echo is the
624
+ * framework's responsibility, not the seed fixture's.
625
+ *
626
+ * When no fixture matches, returns the handler response unchanged.
627
+ *
628
+ * @param resolvedAccountId - Optional resolved `account_id` from
629
+ * `ctx.account`; passed through to {@link pickSeededAccountFinancialsForRequest}.
630
+ */
631
+ export declare function replaceAccountFinancialsIfSeeded(request: GetAccountFinancialsRequest | Record<string, unknown>, response: GetAccountFinancialsResponse, seeded: readonly SeededAccountFinancials[], resolvedAccountId?: string): GetAccountFinancialsResponse;
632
+ /**
633
+ * Merge seeded creative formats into a `list_creative_formats` response.
634
+ * Existing formats come first; seeded entries append after deduping by
635
+ * canonical `${agent_url}|${id}`. On collision the seeded entry wins.
636
+ * Returns a NEW response object.
637
+ */
638
+ export declare function mergeSeededCreativeFormatsIntoResponse(response: ListCreativeFormatsResponse, seeded: readonly Format[]): ListCreativeFormatsResponse;
639
+ /**
640
+ * Validate seeded property-list entries. Drops entries missing a non-empty
641
+ * string `list_id`.
642
+ */
643
+ export declare function filterValidSeededPropertyLists(raw: unknown, logger?: {
644
+ warn: (message: string, meta?: Record<string, unknown>) => void;
645
+ }): PropertyList[];
646
+ /**
647
+ * Validate seeded collection-list entries. Drops entries missing a non-empty
648
+ * string `list_id`.
649
+ */
650
+ export declare function filterValidSeededCollectionLists(raw: unknown, logger?: {
651
+ warn: (message: string, meta?: Record<string, unknown>) => void;
652
+ }): CollectionList[];
653
+ /**
654
+ * Validate seeded content-standards entries. Drops entries missing a
655
+ * non-empty string `standards_id`.
656
+ */
657
+ export declare function filterValidSeededContentStandards(raw: unknown, logger?: {
658
+ warn: (message: string, meta?: Record<string, unknown>) => void;
659
+ }): ContentStandards[];
660
+ /**
661
+ * Merge seeded property lists into a `list_property_lists` response.
662
+ * Existing entries come first; seeded entries append after deduping by
663
+ * `list_id`. On collision the seeded entry wins. Returns a NEW response
664
+ * object. When the handler set `pagination.total_count`, it's incremented
665
+ * by the count of new (non-colliding) seeded entries.
666
+ *
667
+ * `list_property_lists` does not carry a `query_summary` block (per AdCP
668
+ * 3.0.11) — pagination.total_count is the only count field to update.
669
+ */
670
+ export declare function mergeSeededPropertyListsIntoResponse(response: ListPropertyListsResponse, seeded: readonly PropertyList[]): ListPropertyListsResponse;
671
+ /**
672
+ * Merge seeded collection lists into a `list_collection_lists` response.
673
+ * Symmetric with {@link mergeSeededPropertyListsIntoResponse} — same dedup
674
+ * key (`list_id`), same pagination update policy.
675
+ */
676
+ export declare function mergeSeededCollectionListsIntoResponse(response: ListCollectionListsResponse, seeded: readonly CollectionList[]): ListCollectionListsResponse;
677
+ /**
678
+ * Merge seeded content-standards into a `list_content_standards` response
679
+ * (success arm). Drops to a no-op if the response is the error arm (no
680
+ * `standards` array). On `standards_id` collision the seeded entry wins.
681
+ * Updates `pagination.total_count` when the handler set it. Returns a NEW
682
+ * response object.
683
+ */
684
+ export declare function mergeSeededContentStandardsIntoResponse(response: ListContentStandardsResponse, seeded: readonly ContentStandards[]): ListContentStandardsResponse;
685
+ /**
686
+ * Pick the seeded property list whose `list_id` matches the request.
687
+ * Returns `undefined` when nothing matches.
688
+ */
689
+ export declare function pickSeededPropertyListForRequest(request: GetPropertyListRequest | Record<string, unknown>, seeded: readonly PropertyList[]): PropertyList | undefined;
690
+ /**
691
+ * Replace a `get_property_list` response's `list` field with a seeded fixture
692
+ * when one matches. The handler's auxiliary fields (`identifiers`,
693
+ * `pagination`, `resolved_at`, `cache_valid_until`, `coverage_gaps`,
694
+ * `context`, `ext`) pass through verbatim — those depend on request-time
695
+ * pagination / resolve params that a static fixture can't know. Only `list`
696
+ * is replaced. When no fixture matches, returns the handler response
697
+ * unchanged.
698
+ */
699
+ export declare function replacePropertyListIfSeeded(request: GetPropertyListRequest | Record<string, unknown>, response: GetPropertyListResponse, seeded: readonly PropertyList[]): GetPropertyListResponse;
700
+ /**
701
+ * Pick the seeded collection list whose `list_id` matches the request.
702
+ * Returns `undefined` when nothing matches.
703
+ */
704
+ export declare function pickSeededCollectionListForRequest(request: GetCollectionListRequest | Record<string, unknown>, seeded: readonly CollectionList[]): CollectionList | undefined;
705
+ /**
706
+ * Replace a `get_collection_list` response's `list` field with a seeded
707
+ * fixture when one matches. Same envelope-preservation policy as
708
+ * {@link replacePropertyListIfSeeded}.
709
+ */
710
+ export declare function replaceCollectionListIfSeeded(request: GetCollectionListRequest | Record<string, unknown>, response: GetCollectionListResponse, seeded: readonly CollectionList[]): GetCollectionListResponse;
711
+ /**
712
+ * Pick the seeded content-standards entry whose `standards_id` matches the
713
+ * request. Returns `undefined` when nothing matches.
714
+ */
715
+ export declare function pickSeededContentStandardsForRequest(request: GetContentStandardsRequest | Record<string, unknown>, seeded: readonly ContentStandards[]): ContentStandards | undefined;
716
+ /**
717
+ * Replace a `get_content_standards` response with a seeded fixture when one
718
+ * matches. Seeded fixture is authoritative on the `ContentStandards` body.
719
+ * Framework-managed envelope fields (`context`, `ext`) round-trip from the
720
+ * handler — matches the precedent set by {@link replaceAccountFinancialsIfSeeded}.
721
+ *
722
+ * When no fixture matches, returns the handler response unchanged. The
723
+ * caller is responsible for skipping the error arm (the dispatcher gates on
724
+ * `!isErrorResponse`).
725
+ */
726
+ export declare function replaceContentStandardsIfSeeded(request: GetContentStandardsRequest | Record<string, unknown>, response: GetContentStandardsResponse, seeded: readonly ContentStandards[]): GetContentStandardsResponse;
727
+ /**
728
+ * Validate seeded signals. Drops entries whose `signal_id` is not a valid
729
+ * `SignalID` discriminated-union shape (`{source:'catalog',
730
+ * data_provider_domain, id}` or `{source:'agent', agent_url, id}`). A missing
731
+ * or malformed `signal_id` collides on `undefined === undefined` when
732
+ * deduping, so we drop early.
733
+ */
734
+ export declare function filterValidSeededSignals(raw: unknown, logger?: {
735
+ warn: (message: string, meta?: Record<string, unknown>) => void;
736
+ }): SeededSignal[];
737
+ /**
738
+ * Validate seeded creative-delivery entries. Drops entries missing a non-empty
739
+ * string `creative_id`.
740
+ */
741
+ export declare function filterValidSeededCreativeDelivery(raw: unknown, logger?: {
742
+ warn: (message: string, meta?: Record<string, unknown>) => void;
743
+ }): SeededCreativeDelivery[];
744
+ /**
745
+ * Validate seeded creative-feature results. Drops entries missing a non-empty
746
+ * string `feature_id` (the dedup key against the handler's `results` array).
747
+ */
748
+ export declare function filterValidSeededCreativeFeatures(raw: unknown, logger?: {
749
+ warn: (message: string, meta?: Record<string, unknown>) => void;
750
+ }): SeededCreativeFeature[];
751
+ /**
752
+ * Merge seeded signals into a `get_signals` response. Existing handler signals
753
+ * come first; seeded entries append after deduping by `signal_id`. On
754
+ * collision the seeded entry wins. Stamps `sandbox: true` unless the handler
755
+ * explicitly declared `sandbox: false`.
756
+ *
757
+ * `get_signals` carries `pagination: PaginationResponse` and no `query_summary`.
758
+ * Pagination is not recomputed on the merge. `PaginationResponse.total_count`
759
+ * is optional; recomputing it on partial pages would mis-represent the
760
+ * cross-page total (the handler may have served page 1 of N, and the seeded
761
+ * fixture sits outside that pagination context). Storyboards asserting on
762
+ * totals should seed the handler's response, not the post-merge envelope.
763
+ */
764
+ export declare function mergeSeededSignalsIntoResponse(response: GetSignalsResponse, seeded: readonly SeededSignal[]): GetSignalsResponse;
765
+ /**
766
+ * Merge seeded creative-delivery entries into a `get_creative_delivery`
767
+ * response. Existing handler creatives come first; seeded entries append after
768
+ * deduping by `creative_id`. On collision the seeded entry wins (matches the
769
+ * precedent set by `mergeSeededMediaBuyDelivery` — storyboards seed
770
+ * deliberately, so a seeded fixture for an existing id is an explicit author
771
+ * override).
772
+ *
773
+ * `pagination.total` (optional per the AdCP 3.0.11 schema) is incremented by
774
+ * the count of new non-colliding seeded entries. There is no top-level
775
+ * aggregated-totals envelope on this response (unlike `get_media_buy_delivery`),
776
+ * so no further recomputation is performed. Stamps `sandbox: true` unless the
777
+ * handler explicitly declared `sandbox: false`. Returns a NEW response object.
778
+ */
779
+ export declare function mergeSeededCreativeDeliveryIntoResponse(response: GetCreativeDeliveryResponse, seeded: readonly SeededCreativeDelivery[]): GetCreativeDeliveryResponse;
780
+ /**
781
+ * Merge seeded creative-feature results into a `get_creative_features`
782
+ * response. The response is a `oneOf` envelope — success arm carries
783
+ * `results: CreativeFeatureResult[]`, error arm carries `errors: Error[]`.
784
+ * When the handler returned the error arm, this helper is a no-op; the error
785
+ * envelope passes through unchanged. When the handler returned the success
786
+ * arm, seeded results merge into the `results` array (dedup by `feature_id`,
787
+ * seeded wins on collision).
788
+ *
789
+ * Framework-managed envelope fields (`context`, `ext`, `detail_url`,
790
+ * `pricing_option_id`, `vendor_cost`, `currency`, `consumption`) round-trip
791
+ * from the handler verbatim — the bridge only augments the per-feature
792
+ * results array.
793
+ *
794
+ * Returns a NEW response object.
795
+ */
796
+ export declare function mergeSeededCreativeFeaturesIntoResponse(response: GetCreativeFeaturesResponse, seeded: readonly SeededCreativeFeature[]): GetCreativeFeaturesResponse;
797
+ /**
798
+ * Validate seeded brand-identity entries. Drops entries missing a non-empty
799
+ * string `brand_id`, and warn-drops duplicates (first occurrence wins) since
800
+ * a fixture array with two entries for the same `brand_id` is almost always
801
+ * a seed-store bug.
802
+ */
803
+ export declare function filterValidSeededBrandIdentity(raw: unknown, logger?: {
804
+ warn: (message: string, meta?: Record<string, unknown>) => void;
805
+ }): SeededBrandIdentity[];
806
+ /**
807
+ * Validate seeded rights entries. Drops entries missing a non-empty string
808
+ * `rights_id` (the dedup key against the handler set).
809
+ */
810
+ export declare function filterValidSeededRights(raw: unknown, logger?: {
811
+ warn: (message: string, meta?: Record<string, unknown>) => void;
812
+ }): SeededRight[];
813
+ /**
814
+ * Validate seeded SI offering entries. Each entry must carry a non-empty
815
+ * string at `offering.offering_id` — that's the field the bridge matches
816
+ * against `request.offering_id`. Warn-drops duplicates by that key.
817
+ *
818
+ * An entry without `offering` (e.g. an `available: false` fixture from a
819
+ * different storyboard) can't be matched by the bridge and is dropped — a
820
+ * seeded fixture that can't address a request would be a dead write.
821
+ */
822
+ export declare function filterValidSeededSiOffering(raw: unknown, logger?: {
823
+ warn: (message: string, meta?: Record<string, unknown>) => void;
824
+ }): SeededSiOffering[];
825
+ /**
826
+ * Merge seeded rights entries into a `get_rights` response (success arm).
827
+ * Existing entries come first; seeded entries append after deduping by
828
+ * `rights_id`. On collision the seeded entry wins. Returns a NEW response
829
+ * object. `get_rights` does not carry `pagination` / `query_summary` blocks
830
+ * per AdCP 3.0.11 — no count fields to update.
831
+ *
832
+ * Drops to a no-op if the response is the error arm (no `rights` array). The
833
+ * dispatcher gates on `!isErrorResponse` upstream; we re-narrow defensively.
834
+ */
835
+ export declare function mergeSeededRightsIntoResponse(response: GetRightsResponse, seeded: readonly SeededRight[]): GetRightsResponse;
836
+ /**
837
+ * Pick the seeded brand-identity entry whose `brand_id` matches the request.
838
+ * Returns `undefined` when nothing matches.
839
+ */
840
+ export declare function pickSeededBrandIdentityForRequest(request: GetBrandIdentityRequest | Record<string, unknown>, seeded: readonly SeededBrandIdentity[]): SeededBrandIdentity | undefined;
841
+ /**
842
+ * Replace a `get_brand_identity` response with a seeded fixture when one
843
+ * matches the request's `brand_id`. Seeded fixture is authoritative on the
844
+ * `GetBrandIdentitySuccess` body. Framework-managed envelope fields
845
+ * (`context`, `ext`) round-trip from the handler — matches the precedent set
846
+ * by {@link replaceContentStandardsIfSeeded}.
847
+ *
848
+ * When no fixture matches, returns the handler response unchanged. Caller
849
+ * is responsible for skipping the error arm (the dispatcher gates on
850
+ * `!isErrorResponse`).
851
+ */
852
+ export declare function replaceBrandIdentityIfSeeded(request: GetBrandIdentityRequest | Record<string, unknown>, response: GetBrandIdentityResponse, seeded: readonly SeededBrandIdentity[]): GetBrandIdentityResponse;
853
+ /**
854
+ * Pick the seeded SI offering entry whose `offering.offering_id` matches the
855
+ * request's `offering_id`. Returns `undefined` when nothing matches.
856
+ */
857
+ export declare function pickSeededSiOfferingForRequest(request: SIGetOfferingRequest | Record<string, unknown>, seeded: readonly SeededSiOffering[]): SeededSiOffering | undefined;
858
+ /**
859
+ * Replace an `si_get_offering` response with a seeded fixture when one
860
+ * matches the request's `offering_id`. Seeded fixture is authoritative on
861
+ * the offering body (`available`, `offering_token`, `offering`, ...);
862
+ * handler's `context` and `ext` round-trip — same envelope-preservation
863
+ * policy as {@link replaceBrandIdentityIfSeeded}.
864
+ *
865
+ * When no fixture matches, returns the handler response unchanged.
866
+ *
867
+ * Note: seeded fixture is authoritative on the entire offering body,
868
+ * including `offering_token`. The token is intentionally NOT preserved
869
+ * from the handler — storyboards seed `offering_token` to drive
870
+ * deterministic session continuation in downstream steps. When/if a
871
+ * stateful SI session bridge ships (phase 5+; see
872
+ * adcontextprotocol/adcp-client#1755), the storyboard contract becomes
873
+ * coupled: seeded `offering_token` here must match the seeded session's
874
+ * `offering_token`, or `si_initiate_session` will reject as stale-token.
875
+ */
876
+ export declare function replaceSiOfferingIfSeeded(request: SIGetOfferingRequest | Record<string, unknown>, response: SIGetOfferingResponse, seeded: readonly SeededSiOffering[]): SIGetOfferingResponse;
99
877
  /**
100
878
  * Bridge the default test-controller store (a `Map<string, unknown>` that
101
879
  * holds seeded fixtures by `product_id`, populated by `seed_product` scenarios)
@@ -160,6 +938,111 @@ export interface BridgeFromSessionStoreOptions<TSession> {
160
938
  * / pricing / property fields the response schema requires.
161
939
  */
162
940
  productDefaults?: Partial<Product>;
941
+ /**
942
+ * Extract seeded creatives from a resolved session. The returned entries
943
+ * are passed through `filterValidSeededCreatives` and merged into
944
+ * `list_creatives` responses on sandbox requests. Each entry MUST carry a
945
+ * non-empty string `creative_id`. Return `null` / `undefined` when the
946
+ * session has no seeded creatives.
947
+ */
948
+ selectSeededCreatives?: (session: TSession) => Iterable<SeededCreative> | Promise<Iterable<SeededCreative> | null | undefined> | null | undefined;
949
+ /**
950
+ * Extract seeded media buys from a resolved session. Each entry MUST carry
951
+ * a non-empty string `media_buy_id`.
952
+ */
953
+ selectSeededMediaBuys?: (session: TSession) => Iterable<SeededMediaBuy> | Promise<Iterable<SeededMediaBuy> | null | undefined> | null | undefined;
954
+ /**
955
+ * Extract seeded media-buy-delivery snapshots from a resolved session. Each
956
+ * entry MUST carry a non-empty string `media_buy_id`. The framework appends
957
+ * non-colliding seeded entries to the handler's response and recomputes
958
+ * `aggregated_totals` from the merged set — see
959
+ * {@link TestControllerBridge.getSeededMediaBuyDelivery}.
960
+ */
961
+ selectSeededMediaBuyDelivery?: (session: TSession) => Iterable<SeededMediaBuyDelivery> | Promise<Iterable<SeededMediaBuyDelivery> | null | undefined> | null | undefined;
962
+ /**
963
+ * Extract seeded accounts from a resolved session. Each entry MUST carry
964
+ * a non-empty string `account_id`.
965
+ */
966
+ selectSeededAccounts?: (session: TSession) => Iterable<Account> | Promise<Iterable<Account> | null | undefined> | null | undefined;
967
+ /**
968
+ * Extract seeded account-financials envelopes from a resolved session.
969
+ * Each entry MUST carry `account.account_id`. The framework picks the
970
+ * fixture matching the request's `account` reference (singleton replace,
971
+ * not append — see {@link TestControllerBridge.getSeededAccountFinancials}).
972
+ */
973
+ selectSeededAccountFinancials?: (session: TSession) => Iterable<SeededAccountFinancials> | Promise<Iterable<SeededAccountFinancials> | null | undefined> | null | undefined;
974
+ /**
975
+ * Extract seeded creative formats from a resolved session. Each entry MUST
976
+ * carry a `format_id.agent_url` + `format_id.id` (both non-empty strings).
977
+ */
978
+ selectSeededCreativeFormats?: (session: TSession) => Iterable<Format> | Promise<Iterable<Format> | null | undefined> | null | undefined;
979
+ /**
980
+ * Extract seeded property lists from a resolved session. Each entry MUST
981
+ * carry a non-empty string `list_id`. Feeds both `list_property_lists`
982
+ * (append-merge into `lists`) and `get_property_list` (singleton replace
983
+ * of the `list` field; handler's `identifiers` / `pagination` / etc. pass
984
+ * through).
985
+ */
986
+ selectSeededPropertyLists?: (session: TSession) => Iterable<PropertyList> | Promise<Iterable<PropertyList> | null | undefined> | null | undefined;
987
+ /**
988
+ * Extract seeded content-standards from a resolved session. Each entry MUST
989
+ * carry a non-empty string `standards_id`. Feeds `list_content_standards`
990
+ * (append-merge into `standards`) and `get_content_standards` (singleton
991
+ * replace of the whole response envelope, preserving handler `ext`).
992
+ */
993
+ selectSeededContentStandards?: (session: TSession) => Iterable<ContentStandards> | Promise<Iterable<ContentStandards> | null | undefined> | null | undefined;
994
+ /**
995
+ * Extract seeded collection lists from a resolved session. Each entry MUST
996
+ * carry a non-empty string `list_id`. Feeds both `list_collection_lists`
997
+ * (append-merge into `lists`) and `get_collection_list` (singleton replace
998
+ * of the `list` field).
999
+ */
1000
+ selectSeededCollectionLists?: (session: TSession) => Iterable<CollectionList> | Promise<Iterable<CollectionList> | null | undefined> | null | undefined;
1001
+ /**
1002
+ * Extract seeded signals from a resolved session. Each entry MUST carry a
1003
+ * non-empty string `signal_id`. Feeds `get_signals` (append-merge into
1004
+ * `signals`, dedup by `signal_id`, seeded wins). Works uniformly across
1005
+ * `signal-marketplace` and `signal-owned` specialisms.
1006
+ */
1007
+ selectSeededSignals?: (session: TSession) => Iterable<SeededSignal> | Promise<Iterable<SeededSignal> | null | undefined> | null | undefined;
1008
+ /**
1009
+ * Extract seeded creative-delivery entries from a resolved session. Each
1010
+ * entry MUST carry a non-empty string `creative_id`. Feeds
1011
+ * `get_creative_delivery` (append-merge into `creatives`, dedup by
1012
+ * `creative_id`, seeded wins; `pagination.total` updated when set).
1013
+ */
1014
+ selectSeededCreativeDelivery?: (session: TSession) => Iterable<SeededCreativeDelivery> | Promise<Iterable<SeededCreativeDelivery> | null | undefined> | null | undefined;
1015
+ /**
1016
+ * Extract seeded creative-feature results from a resolved session. Each
1017
+ * entry MUST carry a non-empty string `feature_id`. Feeds
1018
+ * `get_creative_features` (merge into success-arm `results` by `feature_id`,
1019
+ * seeded wins; no-op when the handler returned the error arm).
1020
+ */
1021
+ selectSeededCreativeFeatures?: (session: TSession) => Iterable<SeededCreativeFeature> | Promise<Iterable<SeededCreativeFeature> | null | undefined> | null | undefined;
1022
+ /**
1023
+ * Extract seeded brand-identity records from a resolved session. Each entry
1024
+ * MUST carry a non-empty string `brand_id`. Feeds `get_brand_identity` via
1025
+ * singleton replace (pick by `request.brand_id` and replace the response
1026
+ * body, preserving handler `context` / `ext`).
1027
+ */
1028
+ selectSeededBrandIdentity?: (session: TSession) => Iterable<SeededBrandIdentity> | Promise<Iterable<SeededBrandIdentity> | null | undefined> | null | undefined;
1029
+ /**
1030
+ * Extract seeded rights entries from a resolved session. Each entry MUST
1031
+ * carry a non-empty string `rights_id`. Feeds `get_rights` (append-merge
1032
+ * into the `rights` array, seeded wins on `rights_id` collision).
1033
+ */
1034
+ selectSeededRights?: (session: TSession) => Iterable<SeededRight> | Promise<Iterable<SeededRight> | null | undefined> | null | undefined;
1035
+ /**
1036
+ * Extract seeded SI offering records from a resolved session. Each entry
1037
+ * MUST carry a non-empty string at `offering.offering_id`. Feeds
1038
+ * `si_get_offering` via singleton replace (pick by `request.offering_id`
1039
+ * and replace the response body, preserving handler `context` / `ext`).
1040
+ *
1041
+ * Stateless lookup only — the session-keyed SI tools
1042
+ * (`si_initiate_session`, `si_send_message`, `si_terminate_session`) are
1043
+ * intentionally out of scope for the bridge.
1044
+ */
1045
+ selectSeededSiOffering?: (session: TSession) => Iterable<SeededSiOffering> | Promise<Iterable<SeededSiOffering> | null | undefined> | null | undefined;
163
1046
  }
164
1047
  /**
165
1048
  * Session-scoped variant of {@link bridgeFromTestControllerStore}.
@@ -189,4 +1072,5 @@ export interface BridgeFromSessionStoreOptions<TSession> {
189
1072
  * ```
190
1073
  */
191
1074
  export declare function bridgeFromSessionStore<TSession, TAccount = unknown>(opts: BridgeFromSessionStoreOptions<TSession>): TestControllerBridge<TAccount>;
1075
+ export {};
192
1076
  //# sourceMappingURL=test-controller-bridge.d.ts.map