@adcp/sdk 6.11.0 → 6.13.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.
- package/bin/adcp-config.js +7 -1
- package/bin/adcp.js +191 -4
- package/dist/lib/adapters/index.d.ts +0 -2
- package/dist/lib/adapters/index.d.ts.map +1 -1
- package/dist/lib/adapters/index.js +1 -8
- package/dist/lib/adapters/index.js.map +1 -1
- package/dist/lib/auth/oauth/index.d.ts +1 -0
- package/dist/lib/auth/oauth/index.d.ts.map +1 -1
- package/dist/lib/auth/oauth/index.js +17 -1
- package/dist/lib/auth/oauth/index.js.map +1 -1
- package/dist/lib/auth/oauth/web-flow.d.ts +270 -0
- package/dist/lib/auth/oauth/web-flow.d.ts.map +1 -0
- package/dist/lib/auth/oauth/web-flow.js +413 -0
- package/dist/lib/auth/oauth/web-flow.js.map +1 -0
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +2 -7
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/mock-server/index.d.ts +2 -0
- package/dist/lib/mock-server/index.d.ts.map +1 -1
- package/dist/lib/mock-server/index.js +17 -0
- package/dist/lib/mock-server/index.js.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/recipe.d.ts +155 -0
- package/dist/lib/mock-server/sales-guaranteed/recipe.d.ts.map +1 -0
- package/dist/lib/mock-server/sales-guaranteed/recipe.js +107 -0
- package/dist/lib/mock-server/sales-guaranteed/recipe.js.map +1 -0
- package/dist/lib/mock-server/sales-guaranteed/server.d.ts.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/server.js +212 -0
- package/dist/lib/mock-server/sales-guaranteed/server.js.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.d.ts +123 -0
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.d.ts.map +1 -0
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.js +81 -0
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.js.map +1 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/ctx-metadata/index.d.ts +1 -1
- package/dist/lib/server/ctx-metadata/index.d.ts.map +1 -1
- package/dist/lib/server/ctx-metadata/index.js +3 -1
- package/dist/lib/server/ctx-metadata/index.js.map +1 -1
- package/dist/lib/server/ctx-metadata/wire-shape.d.ts +21 -0
- package/dist/lib/server/ctx-metadata/wire-shape.d.ts.map +1 -1
- package/dist/lib/server/ctx-metadata/wire-shape.js +111 -0
- package/dist/lib/server/ctx-metadata/wire-shape.js.map +1 -1
- package/dist/lib/server/decisioning/context.d.ts +19 -0
- package/dist/lib/server/decisioning/context.d.ts.map +1 -1
- package/dist/lib/server/decisioning/index.d.ts +3 -0
- package/dist/lib/server/decisioning/index.d.ts.map +1 -1
- package/dist/lib/server/decisioning/index.js +16 -1
- package/dist/lib/server/decisioning/index.js.map +1 -1
- package/dist/lib/server/decisioning/platform.d.ts +17 -0
- package/dist/lib/server/decisioning/platform.d.ts.map +1 -1
- package/dist/lib/server/decisioning/platform.js.map +1 -1
- package/dist/lib/server/decisioning/proposal/dispatch.d.ts +203 -0
- package/dist/lib/server/decisioning/proposal/dispatch.d.ts.map +1 -0
- package/dist/lib/server/decisioning/proposal/dispatch.js +395 -0
- package/dist/lib/server/decisioning/proposal/dispatch.js.map +1 -0
- package/dist/lib/server/decisioning/proposal/index.d.ts +21 -0
- package/dist/lib/server/decisioning/proposal/index.d.ts.map +1 -0
- package/dist/lib/server/decisioning/proposal/index.js +37 -0
- package/dist/lib/server/decisioning/proposal/index.js.map +1 -0
- package/dist/lib/server/decisioning/proposal/lifecycle.d.ts +195 -0
- package/dist/lib/server/decisioning/proposal/lifecycle.d.ts.map +1 -0
- package/dist/lib/server/decisioning/proposal/lifecycle.js +366 -0
- package/dist/lib/server/decisioning/proposal/lifecycle.js.map +1 -0
- package/dist/lib/server/decisioning/proposal/mock-manager.d.ts +93 -0
- package/dist/lib/server/decisioning/proposal/mock-manager.d.ts.map +1 -0
- package/dist/lib/server/decisioning/proposal/mock-manager.js +109 -0
- package/dist/lib/server/decisioning/proposal/mock-manager.js.map +1 -0
- package/dist/lib/server/decisioning/proposal/store.d.ts +279 -0
- package/dist/lib/server/decisioning/proposal/store.d.ts.map +1 -0
- package/dist/lib/server/decisioning/proposal/store.js +291 -0
- package/dist/lib/server/decisioning/proposal/store.js.map +1 -0
- package/dist/lib/server/decisioning/proposal/types.d.ts +394 -0
- package/dist/lib/server/decisioning/proposal/types.d.ts.map +1 -0
- package/dist/lib/server/decisioning/proposal/types.js +58 -0
- package/dist/lib/server/decisioning/proposal/types.js.map +1 -0
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts +25 -0
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.js +198 -15
- package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +3 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +7 -1
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/task-map.js +1 -0
- package/dist/lib/testing/storyboard/task-map.js.map +1 -1
- package/dist/lib/testing/storyboard/test-kit.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/test-kit.js +4 -0
- package/dist/lib/testing/storyboard/test-kit.js.map +1 -1
- package/dist/lib/testing/types.d.ts +10 -0
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/examples/hello_seller_adapter_guaranteed.ts +29 -2
- package/examples/hello_seller_adapter_proposal_mode.ts +575 -0
- package/package.json +1 -1
- package/dist/lib/adapters/proposal-manager.d.ts +0 -142
- package/dist/lib/adapters/proposal-manager.d.ts.map +0 -1
- package/dist/lib/adapters/proposal-manager.js +0 -184
- package/dist/lib/adapters/proposal-manager.js.map +0 -1
|
@@ -58,6 +58,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
58
58
|
const create_adcp_server_1 = require("../../create-adcp-server");
|
|
59
59
|
const account_1 = require("../account");
|
|
60
60
|
const async_outcome_1 = require("../async-outcome");
|
|
61
|
+
const proposal_1 = require("../proposal");
|
|
61
62
|
const errors_1 = require("../../errors");
|
|
62
63
|
const validate_platform_1 = require("./validate-platform");
|
|
63
64
|
const validate_specialisms_1 = require("../validate-specialisms");
|
|
@@ -617,7 +618,7 @@ function createAdcpServerFromPlatform(platform, opts) {
|
|
|
617
618
|
mediaBuy: mergeHandlers(opts.mediaBuy, buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observability, fwLogger, {
|
|
618
619
|
allowPrivateWebhookUrls: opts.allowPrivateWebhookUrls === true,
|
|
619
620
|
autoEmitCompletionWebhooks: opts.autoEmitCompletionWebhooks !== false,
|
|
620
|
-
}, ctxFor, effectiveCtxMetadata, opts.mediaBuyStore), 'mediaBuy', mergeOpts),
|
|
621
|
+
}, ctxFor, effectiveCtxMetadata, opts.mediaBuyStore, opts.proposalStore), 'mediaBuy', mergeOpts),
|
|
621
622
|
creative: mergeHandlers(opts.creative, buildCreativeHandlers(platform, taskRegistry, taskWebhookEmit, observability, fwLogger, {
|
|
622
623
|
allowPrivateWebhookUrls: opts.allowPrivateWebhookUrls === true,
|
|
623
624
|
autoEmitCompletionWebhooks: opts.autoEmitCompletionWebhooks !== false,
|
|
@@ -1367,8 +1368,18 @@ async function projectSync(fn, mapResult, refresh) {
|
|
|
1367
1368
|
// Mutates `wire` in place — every handler builds a fresh response per
|
|
1368
1369
|
// call so mutation is safe. Runs BEFORE the framework wraps in envelope
|
|
1369
1370
|
// / caches in idempotency, so cached replays stay clean too.
|
|
1371
|
+
//
|
|
1372
|
+
// implementation_config strip: same pattern. Recipes ride on
|
|
1373
|
+
// Product.implementation_config server-side (typed contract between
|
|
1374
|
+
// ProposalManager and DecisioningPlatform via ctx.recipes) but are
|
|
1375
|
+
// opaque-to-buyer. The wire schema is `additionalProperties: true`
|
|
1376
|
+
// so the field is technically legal — but recipes carry upstream
|
|
1377
|
+
// identifiers (network codes, line-item template ids, ad-unit ids,
|
|
1378
|
+
// GAM line-item priority) that leak topology to buyers. Strip
|
|
1379
|
+
// before the response leaves the dispatcher.
|
|
1370
1380
|
if (wire != null && typeof wire === 'object') {
|
|
1371
1381
|
(0, ctx_metadata_1.stripCtxMetadata)(wire);
|
|
1382
|
+
(0, ctx_metadata_1.stripImplementationConfig)(wire);
|
|
1372
1383
|
}
|
|
1373
1384
|
return wire;
|
|
1374
1385
|
}
|
|
@@ -1572,7 +1583,18 @@ async function dispatchHitl(taskRegistry, opts, taskFn, overrideTaskId) {
|
|
|
1572
1583
|
taskFnError = err;
|
|
1573
1584
|
}
|
|
1574
1585
|
if (taskFnError === undefined) {
|
|
1575
|
-
// Success path
|
|
1586
|
+
// Success path. Strip wire-only-server fields BEFORE the registry
|
|
1587
|
+
// write so every downstream consumer (tasks/get polling at
|
|
1588
|
+
// line 1826, emitTaskWebhook below at line 2493, idempotency
|
|
1589
|
+
// replays of the Submitted envelope) inherits a clean payload.
|
|
1590
|
+
// Mirrors the same chokepoint pattern projectSync uses for the
|
|
1591
|
+
// sync arm. Without this strip, ctx_metadata + implementation_config
|
|
1592
|
+
// ride to the buyer via the HITL completion path even though the
|
|
1593
|
+
// sync path is clean — surfaced by security review on PR #1562.
|
|
1594
|
+
if (result != null && typeof result === 'object') {
|
|
1595
|
+
(0, ctx_metadata_1.stripCtxMetadata)(result);
|
|
1596
|
+
(0, ctx_metadata_1.stripImplementationConfig)(result);
|
|
1597
|
+
}
|
|
1576
1598
|
try {
|
|
1577
1599
|
await taskRegistry.complete(taskId, result);
|
|
1578
1600
|
}
|
|
@@ -2393,9 +2415,11 @@ function validatePushNotificationToken(token) {
|
|
|
2393
2415
|
}
|
|
2394
2416
|
return { ok: true };
|
|
2395
2417
|
}
|
|
2396
|
-
function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observability, logger, pushOpts, ctxFor, ctxMetadataStore, mediaBuyStore) {
|
|
2418
|
+
function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observability, logger, pushOpts, ctxFor, ctxMetadataStore, mediaBuyStore, proposalStore) {
|
|
2397
2419
|
const sales = platform.sales;
|
|
2398
|
-
|
|
2420
|
+
const proposalManager = platform.proposalManager;
|
|
2421
|
+
// Without sales AND without a proposal manager, there's nothing to dispatch.
|
|
2422
|
+
if (!sales && !proposalManager)
|
|
2399
2423
|
return undefined;
|
|
2400
2424
|
// Core lifecycle methods are optional on the SalesPlatform interface
|
|
2401
2425
|
// (#1341) — the per-specialism mapping in `RequiredPlatformsFor<S>`
|
|
@@ -2407,21 +2431,97 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2407
2431
|
// (`opts.mediaBuy.X`) can supply it OR the framework returns
|
|
2408
2432
|
// `METHOD_NOT_FOUND` from `tools/list` for the unsupported tool.
|
|
2409
2433
|
return {
|
|
2410
|
-
...(sales
|
|
2434
|
+
...((sales?.getProducts || proposalManager) && {
|
|
2411
2435
|
getProducts: async (params, ctx) => {
|
|
2412
2436
|
const reqCtx = ctxFor(ctx);
|
|
2437
|
+
// v1.5 seam: intercept refine[i].action='finalize' before
|
|
2438
|
+
// dispatching to the manager / sales. When the framework
|
|
2439
|
+
// commits the proposal inline, project the response directly.
|
|
2440
|
+
if (proposalManager && proposalStore) {
|
|
2441
|
+
const intercept = await (0, proposal_1.maybeInterceptFinalize)({
|
|
2442
|
+
request: params,
|
|
2443
|
+
manager: proposalManager,
|
|
2444
|
+
store: proposalStore,
|
|
2445
|
+
ctx: reqCtx,
|
|
2446
|
+
});
|
|
2447
|
+
if (intercept.kind === 'intercepted') {
|
|
2448
|
+
// Unified sync-or-HITL dispatch. routeIfHandoff runs
|
|
2449
|
+
// `intercept.project` inline for sync `FinalizeProposalSuccess`
|
|
2450
|
+
// returns and inside the background task for HITL handoffs.
|
|
2451
|
+
// Same machinery createMediaBuy / syncCreatives use — finalize
|
|
2452
|
+
// inherits the framework's task-lifecycle guarantees.
|
|
2453
|
+
//
|
|
2454
|
+
// **Known gap (HITL only)**: if the buyer calls tasks/cancel
|
|
2455
|
+
// while the adopter's handoff fn is still mid-run, the
|
|
2456
|
+
// framework marks the task cancelled but `intercept.project`
|
|
2457
|
+
// (which fires when the handoff fn resolves) still runs
|
|
2458
|
+
// store.commit. The committed proposal then sits in the
|
|
2459
|
+
// store with no buyer-facing task to retrieve it. Same gap
|
|
2460
|
+
// exists for createMediaBuy HITL today (see comment in
|
|
2461
|
+
// `sales.createMediaBuy` body re: CONSUMING reservation
|
|
2462
|
+
// cleanup). Mitigations: the proposal expires via the
|
|
2463
|
+
// 7-day eviction window in `InMemoryProposalStore`, and
|
|
2464
|
+
// production durable stores can implement a sweep against
|
|
2465
|
+
// the task registry to release stale committed-but-
|
|
2466
|
+
// unretrievable proposals. Closing the gap end-to-end
|
|
2467
|
+
// requires propagating an AbortSignal into the projection
|
|
2468
|
+
// — framework-level work tracked separately, not finalize-
|
|
2469
|
+
// specific.
|
|
2470
|
+
const push = extractPushConfig(params, logger, {
|
|
2471
|
+
allowPrivateWebhookUrls: pushOpts.allowPrivateWebhookUrls,
|
|
2472
|
+
});
|
|
2473
|
+
const out = await routeIfHandoff(taskRegistry, {
|
|
2474
|
+
tool: 'get_products',
|
|
2475
|
+
accountId: reqCtx.account.id,
|
|
2476
|
+
pushNotificationUrl: push.url,
|
|
2477
|
+
pushNotificationToken: push.token,
|
|
2478
|
+
emitWebhook: taskWebhookEmit ?? ctx.emitWebhook,
|
|
2479
|
+
autoEmitCompletion: pushOpts.autoEmitCompletionWebhooks,
|
|
2480
|
+
observability,
|
|
2481
|
+
logger,
|
|
2482
|
+
}, intercept.result, intercept.project);
|
|
2483
|
+
return out;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2413
2486
|
return projectSync(async () => {
|
|
2414
|
-
|
|
2487
|
+
// Pick dispatch target: ProposalManager (when wired) takes
|
|
2488
|
+
// ownership of get_products; sales is the v1 fallback.
|
|
2489
|
+
// Refine routing per Python's _select_proposal_method:
|
|
2490
|
+
// refine_products iff buying_mode='refine' AND
|
|
2491
|
+
// capabilities.refine AND the manager implements it.
|
|
2492
|
+
let result;
|
|
2493
|
+
if (proposalManager) {
|
|
2494
|
+
const buyingMode = params.buying_mode;
|
|
2495
|
+
const useRefine = buyingMode === 'refine' &&
|
|
2496
|
+
proposalManager.capabilities.refine === true &&
|
|
2497
|
+
typeof proposalManager.refineProducts === 'function';
|
|
2498
|
+
result = useRefine
|
|
2499
|
+
? await proposalManager.refineProducts(params, reqCtx)
|
|
2500
|
+
: await proposalManager.getProducts(params, reqCtx);
|
|
2501
|
+
}
|
|
2502
|
+
else {
|
|
2503
|
+
result = await sales.getProducts(params, reqCtx);
|
|
2504
|
+
}
|
|
2415
2505
|
// Auto-store products: persist each Product's wire shape +
|
|
2416
2506
|
// ctx_metadata so subsequent createMediaBuy / updateMediaBuy
|
|
2417
2507
|
// calls referencing product_id can hydrate the full Product
|
|
2418
2508
|
// automatically (publisher sees `req.packages[i].product`).
|
|
2419
2509
|
await autoStoreResources(ctxMetadataStore, reqCtx.account?.id, 'product', result?.products, 'product_id', logger);
|
|
2510
|
+
// v1.5 seam: persist proposals[] as DRAFT records (with
|
|
2511
|
+
// typed recipes pulled from Product.implementation_config)
|
|
2512
|
+
// so subsequent finalize / create_media_buy can hydrate.
|
|
2513
|
+
if (proposalStore) {
|
|
2514
|
+
await (0, proposal_1.maybePersistDraftAfterGetProducts)({
|
|
2515
|
+
response: result,
|
|
2516
|
+
store: proposalStore,
|
|
2517
|
+
ctx: reqCtx,
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2420
2520
|
return result;
|
|
2421
2521
|
}, r => r);
|
|
2422
2522
|
},
|
|
2423
2523
|
}),
|
|
2424
|
-
...(sales
|
|
2524
|
+
...(sales?.createMediaBuy && {
|
|
2425
2525
|
createMediaBuy: async (params, ctx) => {
|
|
2426
2526
|
const reqCtx = ctxFor(ctx);
|
|
2427
2527
|
// Auto-hydrate: walk `params.packages`, attach the full Product object
|
|
@@ -2429,11 +2529,59 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2429
2529
|
// `pkg.product.format_ids`, `pkg.product.ctx_metadata?.gam?.ad_unit_ids`
|
|
2430
2530
|
// directly — no separate lookup, no boilerplate.
|
|
2431
2531
|
await hydratePackagesWithProducts(ctxMetadataStore, reqCtx.account?.id, params?.packages, logger);
|
|
2532
|
+
// v1.5 seam: when the request carries a proposal_id, reserve
|
|
2533
|
+
// the proposal (atomic CAS COMMITTED → CONSUMING), validate
|
|
2534
|
+
// expiry + capability overlap, hydrate ctx.recipes. The
|
|
2535
|
+
// adapter runs against the reservation; finalize_consumption
|
|
2536
|
+
// (success) or release_consumption (failure) closes it out.
|
|
2537
|
+
let reservation = null;
|
|
2538
|
+
if (proposalStore) {
|
|
2539
|
+
reservation = await (0, proposal_1.maybeReserveProposalForCreateMediaBuy)({
|
|
2540
|
+
request: params,
|
|
2541
|
+
manager: proposalManager,
|
|
2542
|
+
store: proposalStore,
|
|
2543
|
+
ctx: reqCtx,
|
|
2544
|
+
});
|
|
2545
|
+
if (reservation) {
|
|
2546
|
+
reqCtx.recipes = reservation.recipes;
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2432
2549
|
return projectSync(async () => {
|
|
2433
2550
|
const push = extractPushConfig(params, logger, {
|
|
2434
2551
|
allowPrivateWebhookUrls: pushOpts.allowPrivateWebhookUrls,
|
|
2435
2552
|
});
|
|
2436
|
-
|
|
2553
|
+
let result;
|
|
2554
|
+
try {
|
|
2555
|
+
result = await sales.createMediaBuy(params, reqCtx);
|
|
2556
|
+
}
|
|
2557
|
+
catch (err) {
|
|
2558
|
+
// Adapter rejected — roll back the reservation so the buyer
|
|
2559
|
+
// can retry without PROPOSAL_NOT_COMMITTED blocking them.
|
|
2560
|
+
if (reservation && proposalStore) {
|
|
2561
|
+
await (0, proposal_1.releaseProposalReservation)({
|
|
2562
|
+
store: proposalStore,
|
|
2563
|
+
record: reservation,
|
|
2564
|
+
logger,
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
throw err;
|
|
2568
|
+
}
|
|
2569
|
+
// Inline-success path: promote CONSUMING → CONSUMED with the
|
|
2570
|
+
// adapter's media_buy_id. HITL handoff: the proposal stays
|
|
2571
|
+
// CONSUMING until the handoff completes — wiring the
|
|
2572
|
+
// post-completion commit hook is a v1.6 follow-up; for now
|
|
2573
|
+
// adopters using HITL accept the reservation lingers until
|
|
2574
|
+
// eviction. Most adopters use inline create_media_buy.
|
|
2575
|
+
if (reservation && proposalStore && !(0, async_outcome_2.isTaskHandoff)(result)) {
|
|
2576
|
+
const mediaBuyId = result.media_buy_id;
|
|
2577
|
+
if (mediaBuyId) {
|
|
2578
|
+
await (0, proposal_1.finalizeProposalConsumption)({
|
|
2579
|
+
store: proposalStore,
|
|
2580
|
+
record: reservation,
|
|
2581
|
+
mediaBuyId,
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2437
2585
|
return routeIfHandoff(taskRegistry, {
|
|
2438
2586
|
tool: 'create_media_buy',
|
|
2439
2587
|
accountId: reqCtx.account.id,
|
|
@@ -2450,7 +2598,7 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2450
2598
|
}, r => r);
|
|
2451
2599
|
},
|
|
2452
2600
|
}),
|
|
2453
|
-
...(sales
|
|
2601
|
+
...(sales?.updateMediaBuy && {
|
|
2454
2602
|
updateMediaBuy: async (params, ctx) => {
|
|
2455
2603
|
const reqCtx = ctxFor(ctx);
|
|
2456
2604
|
// `media_buy_id` is required on the wire schema, but `validation: 'off'`
|
|
@@ -2469,6 +2617,21 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2469
2617
|
// directly — no separate lookup. Misses are silent; publisher falls
|
|
2470
2618
|
// back to its own DB. Schema-driven via `x-entity` (#1109).
|
|
2471
2619
|
await hydrateForTool(ctxMetadataStore, reqCtx.account?.id, 'update_media_buy', params, logger);
|
|
2620
|
+
// v1.5 seam: hydrate ctx.recipes via reverse-index so the adapter's
|
|
2621
|
+
// updateMediaBuy can apply the same recipe that drove createMediaBuy.
|
|
2622
|
+
// Re-validates capability overlap on any packages-shaped patch
|
|
2623
|
+
// (Resolutions §5).
|
|
2624
|
+
if (proposalStore) {
|
|
2625
|
+
const record = await (0, proposal_1.maybeHydrateRecipesForMediaBuyId)({
|
|
2626
|
+
mediaBuyId: media_buy_id,
|
|
2627
|
+
store: proposalStore,
|
|
2628
|
+
ctx: reqCtx,
|
|
2629
|
+
packages: params.packages,
|
|
2630
|
+
});
|
|
2631
|
+
if (record) {
|
|
2632
|
+
reqCtx.recipes = record.recipes;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2472
2635
|
return projectSync(async () => {
|
|
2473
2636
|
const push = extractPushConfig(params, logger, {
|
|
2474
2637
|
allowPrivateWebhookUrls: pushOpts.allowPrivateWebhookUrls,
|
|
@@ -2511,7 +2674,7 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2511
2674
|
syncCreatives: async (params, ctx) => {
|
|
2512
2675
|
const reqCtx = ctxFor(ctx);
|
|
2513
2676
|
const creatives = params.creatives ?? [];
|
|
2514
|
-
if (!sales
|
|
2677
|
+
if (!sales?.syncCreatives) {
|
|
2515
2678
|
return (0, errors_1.adcpError)('UNSUPPORTED_FEATURE', {
|
|
2516
2679
|
message: 'sync_creatives not supported by this sales platform',
|
|
2517
2680
|
});
|
|
@@ -2531,9 +2694,29 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2531
2694
|
}, result, rows => ({ creatives: rows.map(normalizeRowErrors) }));
|
|
2532
2695
|
}, r => r);
|
|
2533
2696
|
},
|
|
2534
|
-
...(sales
|
|
2697
|
+
...(sales?.getMediaBuyDelivery && {
|
|
2535
2698
|
getMediaBuyDelivery: async (params, ctx) => {
|
|
2536
2699
|
const reqCtx = ctxFor(ctx);
|
|
2700
|
+
// v1.5 seam: hydrate ctx.recipes for delivery reads. Per
|
|
2701
|
+
// Resolutions §5, recipe-driven delivery aggregation needs the
|
|
2702
|
+
// same recipe view the originating createMediaBuy used.
|
|
2703
|
+
if (proposalStore) {
|
|
2704
|
+
const ids = params.media_buy_ids;
|
|
2705
|
+
// The wire shape uses `media_buy_ids[]`; hydrate from the first id
|
|
2706
|
+
// when present (per-buy recipe lookup; if buyer queries multiple
|
|
2707
|
+
// ids, the adapter is responsible for per-id recipe routing).
|
|
2708
|
+
const firstId = Array.isArray(ids) && ids.length > 0 ? ids[0] : undefined;
|
|
2709
|
+
if (firstId) {
|
|
2710
|
+
const record = await (0, proposal_1.maybeHydrateRecipesForMediaBuyId)({
|
|
2711
|
+
mediaBuyId: firstId,
|
|
2712
|
+
store: proposalStore,
|
|
2713
|
+
ctx: reqCtx,
|
|
2714
|
+
});
|
|
2715
|
+
if (record) {
|
|
2716
|
+
reqCtx.recipes = record.recipes;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2537
2720
|
return projectSync(async () => {
|
|
2538
2721
|
const result = await sales.getMediaBuyDelivery(params, reqCtx);
|
|
2539
2722
|
warnIfTruncatedMultiIdResponse('getMediaBuyDelivery', 'media_buy_ids', params.media_buy_ids, result?.media_buy_deliveries, logger);
|
|
@@ -2554,7 +2737,7 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2554
2737
|
// platform-derived handler when absent lets `mergeHandlers` pick up the
|
|
2555
2738
|
// adopter's custom handler from `opts.mediaBuy` instead of throwing
|
|
2556
2739
|
// `sales.getMediaBuys is not a function`.
|
|
2557
|
-
...(sales
|
|
2740
|
+
...(sales?.getMediaBuys && {
|
|
2558
2741
|
getMediaBuys: async (params, ctx) => {
|
|
2559
2742
|
const reqCtx = ctxFor(ctx);
|
|
2560
2743
|
return projectSync(async () => {
|
|
@@ -2566,7 +2749,7 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2566
2749
|
}, r => r);
|
|
2567
2750
|
},
|
|
2568
2751
|
}),
|
|
2569
|
-
...(sales
|
|
2752
|
+
...(sales?.providePerformanceFeedback && {
|
|
2570
2753
|
providePerformanceFeedback: async (params, ctx) => {
|
|
2571
2754
|
const reqCtx = ctxFor(ctx);
|
|
2572
2755
|
// Auto-hydrate `req.media_buy` from the prior createMediaBuy /
|
|
@@ -2581,13 +2764,13 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
|
|
|
2581
2764
|
return projectSync(() => sales.providePerformanceFeedback(params, reqCtx), r => r);
|
|
2582
2765
|
},
|
|
2583
2766
|
}),
|
|
2584
|
-
...(sales
|
|
2767
|
+
...(sales?.listCreativeFormats && {
|
|
2585
2768
|
listCreativeFormats: async (params, ctx) => {
|
|
2586
2769
|
const reqCtx = ctxFor(ctx);
|
|
2587
2770
|
return projectSync(() => sales.listCreativeFormats(params, reqCtx), r => r);
|
|
2588
2771
|
},
|
|
2589
2772
|
}),
|
|
2590
|
-
...(sales
|
|
2773
|
+
...(sales?.listCreatives && {
|
|
2591
2774
|
listCreatives: async (params, ctx) => {
|
|
2592
2775
|
const reqCtx = ctxFor(ctx);
|
|
2593
2776
|
return projectSync(async () => {
|