@adcp/sdk 14.1.0 → 14.2.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/dist/lib/core/SingleAgentClient.d.mts +4 -1
- package/dist/lib/core/SingleAgentClient.d.ts +4 -1
- package/dist/lib/core/SingleAgentClient.js +25 -4
- package/dist/lib/core/SingleAgentClient.mjs +25 -4
- package/dist/lib/core/TaskExecutor.d.mts +3 -1
- package/dist/lib/core/TaskExecutor.d.ts +3 -1
- package/dist/lib/core/TaskExecutor.js +15 -11
- package/dist/lib/core/TaskExecutor.mjs +15 -11
- package/dist/lib/core/buyer-account-registry.d.mts +30 -2
- package/dist/lib/core/buyer-account-registry.d.ts +30 -2
- package/dist/lib/core/buyer-account-registry.js +126 -42
- package/dist/lib/core/buyer-account-registry.mjs +127 -43
- package/dist/lib/index.d.mts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.mjs +12 -1
- package/dist/lib/net/agent-transport-fetch.d.mts +4 -0
- package/dist/lib/net/agent-transport-fetch.d.ts +4 -0
- package/dist/lib/net/agent-transport-fetch.js +18 -5
- package/dist/lib/net/agent-transport-fetch.mjs +17 -5
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/agent-resolver/errors.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/errors.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/errors.js +6 -0
- package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +9 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +9 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +35 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +38 -2
- package/dist/lib/signing/agent-resolver/legacy-brand.js +1 -1
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +2 -2
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.js +47 -5
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +55 -6
- package/dist/lib/signing/brand-jwks.d.mts +2 -0
- package/dist/lib/signing/brand-jwks.d.ts +2 -0
- package/dist/lib/signing/brand-jwks.js +10 -2
- package/dist/lib/signing/brand-jwks.mjs +10 -2
- package/dist/lib/signing/errors.d.mts +4 -2
- package/dist/lib/signing/errors.d.ts +4 -2
- package/dist/lib/signing/errors.js +3 -1
- package/dist/lib/signing/errors.mjs +3 -1
- package/dist/lib/signing/verifier.js +1 -1
- package/dist/lib/signing/verifier.mjs +1 -1
- package/dist/lib/signing/webhook-verifier.js +2 -2
- package/dist/lib/signing/webhook-verifier.mjs +3 -3
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/accept-proposal.d.ts +19 -1
- package/dist/lib/types/buy-products.d.ts +19 -1
- package/dist/lib/types/check-governance.d.ts +19 -1
- package/dist/lib/types/comply-test-controller.d.ts +19 -1
- package/dist/lib/types/control-media-buy.d.ts +19 -1
- package/dist/lib/types/core.generated.d.mts +14 -1
- package/dist/lib/types/core.generated.d.ts +14 -1
- package/dist/lib/types/create-media-buy.d.ts +14 -1
- package/dist/lib/types/get-media-buys.d.ts +14 -1
- package/dist/lib/types/get-products.d.ts +19 -1
- package/dist/lib/types/list-products.d.ts +19 -1
- package/dist/lib/types/refine-proposals.d.ts +19 -1
- package/dist/lib/types/request-proposals.d.ts +19 -1
- package/dist/lib/types/schemas.generated.d.ts +12 -3
- package/dist/lib/types/schemas.generated.js +4 -1
- package/dist/lib/types/schemas.generated.mjs +4 -1
- package/dist/lib/types/tools.generated.d.mts +14 -1
- package/dist/lib/types/tools.generated.d.ts +14 -1
- package/dist/lib/types/update-media-buy.d.ts +14 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/webhooks/index.d.mts +24 -0
- package/dist/lib/webhooks/index.d.ts +24 -0
- package/dist/lib/webhooks/index.js +50 -24
- package/dist/lib/webhooks/index.mjs +49 -24
- package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
- package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
- package/dist/lib/wholesale-feed-sync/index.js +7 -0
- package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
- package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
- package/dist/lib/wholesale-feed-sync/sync.d.mts +12 -29
- package/dist/lib/wholesale-feed-sync/sync.d.ts +12 -29
- package/dist/lib/wholesale-feed-sync/sync.js +153 -297
- package/dist/lib/wholesale-feed-sync/sync.mjs +158 -297
- package/docs/README.md +6 -0
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUYER-STORAGE.md +3 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +3 -1
- package/docs/guides/account-resolution.md +46 -10
- package/docs/llms.txt +2 -2
- package/docs/migration-14.0-to-14.1.md +85 -0
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-resolution-3.3.md +5 -3
- package/docs/recipes/verifying-inbound-webhooks.md +4 -0
- package/package.json +2 -1
- package/skills/adcp-brand.previous/SKILL.md +0 -200
- package/skills/adcp-creative.previous/SKILL.md +0 -305
- package/skills/adcp-governance.previous/SKILL.md +0 -566
- package/skills/adcp-measurement.previous/SKILL.md +0 -136
- package/skills/adcp-media-buy.previous/SKILL.md +0 -556
- package/skills/adcp-si.previous/SKILL.md +0 -206
- package/skills/adcp-signals.previous/SKILL.md +0 -204
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
refreshWholesaleFeed,
|
|
5
|
+
InMemoryWholesaleFeedMirrorStore,
|
|
6
|
+
applyWholesaleFeedEvent,
|
|
7
|
+
diffWholesaleFeed
|
|
8
|
+
} from "./mirror.mjs";
|
|
4
9
|
import { assertLegacyWholesaleFeedRepresentation } from "./webhook-notification.mjs";
|
|
5
10
|
const bootstrapReadFailures = /* @__PURE__ */ new WeakSet();
|
|
11
|
+
function supersededRepairError() {
|
|
12
|
+
const error = new Error("WholesaleFeedSync: repair superseded repeatedly; retry webhook delivery.");
|
|
13
|
+
bootstrapReadFailures.add(error);
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
6
16
|
const DEFAULT_PROBE_INTERVAL_MS = 6e5;
|
|
7
17
|
const DEFAULT_CAPABILITY_REFRESH_INTERVAL_MS = 864e5;
|
|
8
18
|
const DEFAULT_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
9
|
-
const DEFAULT_BOOTSTRAP_PAGE_LIMIT = 100;
|
|
10
19
|
const VERSION_MISMATCH_RECOVERY_ATTEMPTS = 3;
|
|
11
20
|
const VERSION_MISMATCH_RECOVERY_BACKOFF_MS = 5;
|
|
12
21
|
class WholesaleFeedSync extends EventEmitter {
|
|
@@ -47,11 +56,10 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
47
56
|
probeTimer = null;
|
|
48
57
|
capabilityTimer = null;
|
|
49
58
|
lifecycleEpoch = 0;
|
|
59
|
+
mirrorRevision = 0;
|
|
50
60
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* agents (products-feed + signals-wholesale-only, or vice versa) this
|
|
54
|
-
* may differ from `signals.mode`.
|
|
61
|
+
* Product index view. `mode` identifies the product sync strategy and may
|
|
62
|
+
* differ from `signals.mode` for agents with mixed feed capabilities.
|
|
55
63
|
*/
|
|
56
64
|
products = {
|
|
57
65
|
list: () => [...this.productIndex.values()],
|
|
@@ -86,19 +94,12 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
86
94
|
},
|
|
87
95
|
_mode: "manual",
|
|
88
96
|
/**
|
|
89
|
-
*
|
|
90
|
-
* `
|
|
91
|
-
* agent only supports brief-mode discovery — `signals.list()` will
|
|
92
|
-
* be empty until adopters call into the agent with their own briefs.
|
|
97
|
+
* True when `get_signals` supports `discovery_mode: 'wholesale'`.
|
|
98
|
+
* Otherwise `list()` is empty; use client brief discovery instead.
|
|
93
99
|
*/
|
|
94
100
|
queryable: true
|
|
95
101
|
};
|
|
96
|
-
|
|
97
|
-
* One-shot console warning when adopters call `signals.list()` or
|
|
98
|
-
* `signals.search()` against an agent that doesn't support wholesale
|
|
99
|
-
* signal enumeration. Without this, empty results read as "no signals
|
|
100
|
-
* match" rather than "the agent doesn't browse, only briefs."
|
|
101
|
-
*/
|
|
102
|
+
// Warn once when empty signal results reflect missing wholesale support.
|
|
102
103
|
warnIfSignalsNotQueryable() {
|
|
103
104
|
if (this.signals.queryable || this.signalsQueryableWarned) return;
|
|
104
105
|
if (this._state === "idle" || this._state === "bootstrapping") return;
|
|
@@ -124,14 +125,9 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
124
125
|
}
|
|
125
126
|
// ====== Lifecycle ======
|
|
126
127
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* Safe to call repeatedly — concurrent calls await the in-flight
|
|
132
|
-
* bootstrap and return when it completes (no duplicate bootstrap, no
|
|
133
|
-
* silent drop). Sequential calls re-probe capabilities and re-bootstrap,
|
|
134
|
-
* equivalent to calling `refresh()` after a mode upgrade.
|
|
128
|
+
* Discover capabilities, bootstrap the mirror, and schedule auto-poll probes
|
|
129
|
+
* for versioned sellers. Concurrent calls share the bootstrap; sequential
|
|
130
|
+
* calls re-probe capabilities and refresh the mirror.
|
|
135
131
|
*/
|
|
136
132
|
async start() {
|
|
137
133
|
if (this.startPromise && this.startPromiseEpoch === this.lifecycleEpoch) return this.startPromise;
|
|
@@ -151,7 +147,7 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
151
147
|
if (!await this.restorePersistedState(epoch)) return;
|
|
152
148
|
if (!await this.resolveMode(epoch)) return;
|
|
153
149
|
try {
|
|
154
|
-
await this.
|
|
150
|
+
await this.bootstrapWithRetries({ epoch });
|
|
155
151
|
} finally {
|
|
156
152
|
if (this.isLifecycleCurrent(epoch)) {
|
|
157
153
|
if (this._mode === "auto-poll") this.scheduleProbe(epoch);
|
|
@@ -175,6 +171,7 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
175
171
|
*/
|
|
176
172
|
async reset() {
|
|
177
173
|
this.stop();
|
|
174
|
+
++this.mirrorRevision;
|
|
178
175
|
this.productIndex.clear();
|
|
179
176
|
this.signalIndex.clear();
|
|
180
177
|
this.productWholesaleFeedVersion = void 0;
|
|
@@ -200,7 +197,7 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
200
197
|
async refresh() {
|
|
201
198
|
const epoch = this.lifecycleEpoch;
|
|
202
199
|
this.emit("resyncing", { reason: "manual" });
|
|
203
|
-
await this.
|
|
200
|
+
await this.bootstrapWithRetries({ emitDiffs: true, epoch, propagateFailure: true });
|
|
204
201
|
}
|
|
205
202
|
/**
|
|
206
203
|
* Apply one legacy-view account-level wholesale feed webhook to the local mirror.
|
|
@@ -249,6 +246,11 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
249
246
|
this.rememberLastWebhookEventId(event.event_id);
|
|
250
247
|
throw err;
|
|
251
248
|
}
|
|
249
|
+
if (!this._lastSyncedAt && event.event_type !== "wholesale_feed.bulk_change") {
|
|
250
|
+
if (!await this.recoverFromVersionMismatch(event, epoch)) return;
|
|
251
|
+
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
252
254
|
if (webhook.previous_wholesale_feed_version && currentVersion && webhook.previous_wholesale_feed_version !== currentVersion) {
|
|
253
255
|
if (!await this.recoverFromVersionMismatch(event, epoch)) return;
|
|
254
256
|
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
@@ -329,12 +331,24 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
329
331
|
return true;
|
|
330
332
|
}
|
|
331
333
|
// ====== Private: bootstrap (wholesale enumeration) ======
|
|
334
|
+
async bootstrapWithRetries(options) {
|
|
335
|
+
for (let attempt = 0; attempt < VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
336
|
+
const revision = this.mirrorRevision;
|
|
337
|
+
if (await this.bootstrap({ ...options, conditional: attempt === 0 ? void 0 : false })) return;
|
|
338
|
+
if (!this.isLifecycleCurrent(options.epoch) || revision === this.mirrorRevision) return;
|
|
339
|
+
}
|
|
340
|
+
const error = new Error("WholesaleFeedSync: refresh superseded repeatedly; retry start or refresh.");
|
|
341
|
+
this.handleBootstrapFailure({ error });
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
332
344
|
async bootstrap(options = {}) {
|
|
333
345
|
const epoch = options.epoch ?? this.lifecycleEpoch;
|
|
334
346
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
335
347
|
this.setState("bootstrapping");
|
|
336
348
|
const previousLastSyncedAt = this._lastSyncedAt;
|
|
349
|
+
const expectedRevision = this.mirrorRevision;
|
|
337
350
|
let reportedFailure;
|
|
351
|
+
let committed = false;
|
|
338
352
|
try {
|
|
339
353
|
const previousProducts = new Map(this.productIndex);
|
|
340
354
|
const previousSignals = new Map(this.signalIndex);
|
|
@@ -346,8 +360,12 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
346
360
|
const refreshProducts = entities !== "signals";
|
|
347
361
|
const refreshSignals = entities !== "products" && this.signals.queryable;
|
|
348
362
|
if (refreshProducts) {
|
|
349
|
-
productResult = await this.bootstrapProducts(epoch);
|
|
363
|
+
productResult = await this.bootstrapProducts(epoch, options.conditional);
|
|
350
364
|
if (productResult.cancelled) return false;
|
|
365
|
+
if (this.mirrorRevision !== expectedRevision) {
|
|
366
|
+
this.setState(this._lastSyncedAt ? "syncing" : "idle");
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
351
369
|
if (productResult.failure) {
|
|
352
370
|
const failure = productResult.failure;
|
|
353
371
|
reportedFailure = failure.error;
|
|
@@ -361,8 +379,12 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
361
379
|
}
|
|
362
380
|
}
|
|
363
381
|
if (refreshSignals) {
|
|
364
|
-
signalResult = await this.bootstrapSignals(epoch);
|
|
382
|
+
signalResult = await this.bootstrapSignals(epoch, options.conditional);
|
|
365
383
|
if (signalResult.cancelled) return false;
|
|
384
|
+
if (this.mirrorRevision !== expectedRevision) {
|
|
385
|
+
this.setState(this._lastSyncedAt ? "syncing" : "idle");
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
366
388
|
if (signalResult.failure) {
|
|
367
389
|
const failure = signalResult.failure;
|
|
368
390
|
reportedFailure = failure.error;
|
|
@@ -376,6 +398,12 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
376
398
|
}
|
|
377
399
|
}
|
|
378
400
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
401
|
+
if (this.mirrorRevision !== expectedRevision) {
|
|
402
|
+
this.setState(this._lastSyncedAt ? "syncing" : "idle");
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
++this.mirrorRevision;
|
|
406
|
+
committed = true;
|
|
379
407
|
if (refreshProducts && productResult) {
|
|
380
408
|
this.commitProductMetadata(productResult.metadata);
|
|
381
409
|
if (!productResult.unchanged) {
|
|
@@ -407,6 +435,7 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
407
435
|
return true;
|
|
408
436
|
} catch (err) {
|
|
409
437
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
438
|
+
if (!committed && this.mirrorRevision !== expectedRevision && err !== reportedFailure) return false;
|
|
410
439
|
if (err === reportedFailure) throw err;
|
|
411
440
|
this._lastSyncedAt = previousLastSyncedAt;
|
|
412
441
|
this.setState(this._lastSyncedAt ? "degraded" : "error");
|
|
@@ -423,102 +452,74 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
423
452
|
if (this.listenerCount("error") > 0) this.emit("error", failure);
|
|
424
453
|
return false;
|
|
425
454
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
let cursor;
|
|
429
|
-
const into = /* @__PURE__ */ new Map();
|
|
430
|
-
let metadata = this.currentProductMetadata();
|
|
431
|
-
do {
|
|
432
|
-
const params = {
|
|
433
|
-
buying_mode: "wholesale",
|
|
434
|
-
pagination: { max_results: DEFAULT_BOOTSTRAP_PAGE_LIMIT, ...cursor && { cursor } },
|
|
435
|
-
...this.account && { account: this.account }
|
|
436
|
-
};
|
|
437
|
-
if (!cursor && metadata.wholesaleFeedVersion && this._capabilities.wholesaleFeedVersioning) {
|
|
438
|
-
params.if_wholesale_feed_version = metadata.wholesaleFeedVersion;
|
|
439
|
-
if (metadata.pricingVersion) params.if_pricing_version = metadata.pricingVersion;
|
|
440
|
-
}
|
|
441
|
-
const result = await this.client.getProducts(params);
|
|
442
|
-
if (!this.isLifecycleCurrent(epoch)) return { cancelled: true, unchanged: false, items: into, metadata };
|
|
443
|
-
if (result.success === false || result.status !== void 0 && result.status !== "completed" && result.status !== "success") {
|
|
444
|
-
return {
|
|
445
|
-
cancelled: false,
|
|
446
|
-
unchanged: false,
|
|
447
|
-
items: into,
|
|
448
|
-
metadata,
|
|
449
|
-
failure: { error: new Error(result.error ?? "Product bootstrap failed"), adcpError: result.adcpError }
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
const body = result.data;
|
|
453
|
-
if (!body)
|
|
454
|
-
return {
|
|
455
|
-
cancelled: false,
|
|
456
|
-
unchanged: false,
|
|
457
|
-
items: into,
|
|
458
|
-
metadata,
|
|
459
|
-
failure: { error: new Error("Product bootstrap did not return a completed catalog") }
|
|
460
|
-
};
|
|
461
|
-
if (body.unchanged) {
|
|
462
|
-
metadata = mergeFeedMetadata(metadata, body);
|
|
463
|
-
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
464
|
-
}
|
|
465
|
-
const products = Array.isArray(body.products) ? body.products : [];
|
|
466
|
-
for (const product of products) {
|
|
467
|
-
const id = product.product_id;
|
|
468
|
-
if (typeof id === "string") into.set(id, product);
|
|
469
|
-
}
|
|
470
|
-
metadata = mergeFeedMetadata(metadata, body);
|
|
471
|
-
cursor = body.pagination?.has_more ? body.pagination?.cursor : void 0;
|
|
472
|
-
} while (cursor);
|
|
473
|
-
return { cancelled: false, unchanged: false, items: into, metadata };
|
|
455
|
+
bootstrapProducts(epoch, conditional) {
|
|
456
|
+
return this.bootstrapFeed("product", epoch, conditional);
|
|
474
457
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (result.success === false || result.status !== void 0 && result.status !== "completed" && result.status !== "success") {
|
|
492
|
-
return {
|
|
493
|
-
cancelled: false,
|
|
494
|
-
unchanged: false,
|
|
495
|
-
items: into,
|
|
496
|
-
metadata,
|
|
497
|
-
failure: { error: new Error(result.error ?? "Signal bootstrap failed"), adcpError: result.adcpError }
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
const body = result.data;
|
|
501
|
-
if (!body)
|
|
502
|
-
return {
|
|
503
|
-
cancelled: false,
|
|
504
|
-
unchanged: false,
|
|
505
|
-
items: into,
|
|
506
|
-
metadata,
|
|
507
|
-
failure: { error: new Error("Signal bootstrap did not return a completed catalog") }
|
|
508
|
-
};
|
|
509
|
-
if (body.unchanged) {
|
|
510
|
-
metadata = mergeFeedMetadata(metadata, body);
|
|
511
|
-
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
458
|
+
bootstrapSignals(epoch, conditional) {
|
|
459
|
+
return this.bootstrapFeed("signal", epoch, conditional);
|
|
460
|
+
}
|
|
461
|
+
async bootstrapFeed(entity, epoch, conditional) {
|
|
462
|
+
const store = new InMemoryWholesaleFeedMirrorStore();
|
|
463
|
+
const scope = { agentUrl: "shell", accountKey: "shell", entity };
|
|
464
|
+
const metadata = entity === "product" ? this.currentProductMetadata() : this.currentSignalMetadata();
|
|
465
|
+
const previous = entity === "product" ? [...this.productIndex.values()] : [...this.signalIndex.values()];
|
|
466
|
+
store.restore(scope, { revision: 0, items: previous, ...metadata });
|
|
467
|
+
let readException;
|
|
468
|
+
const read = async (call) => {
|
|
469
|
+
try {
|
|
470
|
+
return await call();
|
|
471
|
+
} catch (cause) {
|
|
472
|
+
readException = cause instanceof Error ? cause : new Error(String(cause));
|
|
473
|
+
throw cause;
|
|
512
474
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
475
|
+
};
|
|
476
|
+
const outcome = await refreshWholesaleFeed({
|
|
477
|
+
client: {
|
|
478
|
+
getAdcpCapabilities: this.client.getAdcpCapabilities.bind(this.client),
|
|
479
|
+
getProducts: (params) => read(() => this.client.getProducts(params)),
|
|
480
|
+
getSignals: (params) => read(() => this.client.getSignals(params))
|
|
481
|
+
},
|
|
482
|
+
store,
|
|
483
|
+
scope,
|
|
484
|
+
account: this.account,
|
|
485
|
+
conditional: conditional ?? this._capabilities.wholesaleFeedVersioning,
|
|
486
|
+
isCurrent: () => this.isLifecycleCurrent(epoch)
|
|
487
|
+
});
|
|
488
|
+
if (outcome.outcome === "superseded") return { cancelled: true, unchanged: false, items: /* @__PURE__ */ new Map(), metadata };
|
|
489
|
+
if (outcome.outcome === "degraded") {
|
|
490
|
+
if (readException) throw readException;
|
|
491
|
+
return {
|
|
492
|
+
cancelled: false,
|
|
493
|
+
unchanged: false,
|
|
494
|
+
items: /* @__PURE__ */ new Map(),
|
|
495
|
+
metadata,
|
|
496
|
+
failure: {
|
|
497
|
+
error: new Error(
|
|
498
|
+
`${entity === "product" ? "Product" : "Signal"} bootstrap failed: ${outcome.cause.message}`,
|
|
499
|
+
{
|
|
500
|
+
cause: outcome.cause
|
|
501
|
+
}
|
|
502
|
+
),
|
|
503
|
+
adcpError: outcome.error
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const snapshot = outcome.snapshot;
|
|
508
|
+
return {
|
|
509
|
+
cancelled: false,
|
|
510
|
+
unchanged: outcome.outcome === "unchanged",
|
|
511
|
+
items: new Map(
|
|
512
|
+
snapshot.items.map((item) => [
|
|
513
|
+
entity === "product" ? item.product_id : item.signal_agent_segment_id,
|
|
514
|
+
item
|
|
515
|
+
])
|
|
516
|
+
),
|
|
517
|
+
metadata: {
|
|
518
|
+
wholesaleFeedVersion: snapshot.wholesaleFeedVersion,
|
|
519
|
+
pricingVersion: snapshot.pricingVersion,
|
|
520
|
+
cacheScope: snapshot.cacheScope
|
|
517
521
|
}
|
|
518
|
-
|
|
519
|
-
cursor = body.pagination?.has_more ? body.pagination?.cursor : void 0;
|
|
520
|
-
} while (cursor);
|
|
521
|
-
return { cancelled: false, unchanged: false, items: into, metadata };
|
|
522
|
+
};
|
|
522
523
|
}
|
|
523
524
|
async recoverFromBulkChange(event, epoch = this.lifecycleEpoch) {
|
|
524
525
|
this.emit("resyncing", { reason: "bulk_change" });
|
|
@@ -529,14 +530,22 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
529
530
|
);
|
|
530
531
|
}
|
|
531
532
|
const entities = affected === "product" ? "products" : "signals";
|
|
532
|
-
|
|
533
|
+
for (let attempt = 0; attempt < VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
534
|
+
if (await this.bootstrap({ emitDiffs: true, entities, epoch, propagateFailure: true })) return true;
|
|
535
|
+
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
536
|
+
}
|
|
537
|
+
throw supersededRepairError();
|
|
533
538
|
}
|
|
534
539
|
async recoverFromVersionMismatch(event, epoch = this.lifecycleEpoch) {
|
|
535
540
|
this.emit("resyncing", { reason: "version_mismatch" });
|
|
536
541
|
const beforeVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
537
542
|
for (let attempt = 1; attempt <= VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
538
543
|
const recovered = await this.bootstrap({ emitDiffs: true, epoch, propagateFailure: true });
|
|
539
|
-
if (!recovered)
|
|
544
|
+
if (!recovered) {
|
|
545
|
+
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
546
|
+
if (attempt === VERSION_MISMATCH_RECOVERY_ATTEMPTS) throw supersededRepairError();
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
540
549
|
const afterVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
541
550
|
if (afterVersion !== beforeVersion) return true;
|
|
542
551
|
if (attempt < VERSION_MISMATCH_RECOVERY_ATTEMPTS) {
|
|
@@ -690,63 +699,16 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
690
699
|
}
|
|
691
700
|
}
|
|
692
701
|
applyEvent(event) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
704
|
-
case "product.priced": {
|
|
705
|
-
const payload = event.payload;
|
|
706
|
-
const existing = this.productIndex.get(payload.product_id);
|
|
707
|
-
if (existing && Array.isArray(payload.pricing_options)) {
|
|
708
|
-
this.productIndex.set(payload.product_id, {
|
|
709
|
-
...existing,
|
|
710
|
-
pricing_options: payload.pricing_options
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
case "product.removed": {
|
|
716
|
-
const payload = event.payload;
|
|
717
|
-
if (typeof payload.product_id === "string") this.productIndex.delete(payload.product_id);
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
case "signal.created":
|
|
721
|
-
case "signal.updated": {
|
|
722
|
-
const payload = event.payload;
|
|
723
|
-
if (typeof payload.signal_agent_segment_id !== "string") return;
|
|
724
|
-
if (payload.signal) {
|
|
725
|
-
this.signalIndex.set(payload.signal_agent_segment_id, payload.signal);
|
|
726
|
-
}
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
case "signal.priced": {
|
|
730
|
-
const payload = event.payload;
|
|
731
|
-
const existing = this.signalIndex.get(payload.signal_agent_segment_id);
|
|
732
|
-
if (existing && Array.isArray(payload.pricing_options)) {
|
|
733
|
-
this.signalIndex.set(payload.signal_agent_segment_id, {
|
|
734
|
-
...existing,
|
|
735
|
-
pricing_options: payload.pricing_options
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
case "signal.removed": {
|
|
741
|
-
const payload = event.payload;
|
|
742
|
-
if (typeof payload.signal_agent_segment_id === "string") {
|
|
743
|
-
this.signalIndex.delete(payload.signal_agent_segment_id);
|
|
744
|
-
}
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
case "wholesale_feed.bulk_change":
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
702
|
+
++this.mirrorRevision;
|
|
703
|
+
const entity = event.event_type.startsWith("product.") ? "product" : "signal";
|
|
704
|
+
const items = applyWholesaleFeedEvent(
|
|
705
|
+
entity,
|
|
706
|
+
entity === "product" ? [...this.productIndex.values()] : [...this.signalIndex.values()],
|
|
707
|
+
event
|
|
708
|
+
);
|
|
709
|
+
if (!items) return;
|
|
710
|
+
if (entity === "product") this.productIndex = new Map(items.map((item) => [item.product_id, item]));
|
|
711
|
+
else this.signalIndex = new Map(items.map((item) => [item.signal_agent_segment_id, item]));
|
|
750
712
|
}
|
|
751
713
|
emitTypedEvent(event) {
|
|
752
714
|
switch (event.event_type) {
|
|
@@ -780,107 +742,23 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
780
742
|
}
|
|
781
743
|
// ====== Private: diff emission (auto-poll / manual refresh) ======
|
|
782
744
|
emitDiffs(previousProducts, previousSignals) {
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const
|
|
745
|
+
const events = [
|
|
746
|
+
...diffWholesaleFeed(
|
|
747
|
+
"product",
|
|
748
|
+
[...previousProducts.values()],
|
|
749
|
+
[...this.productIndex.values()],
|
|
750
|
+
this.productCacheScope
|
|
751
|
+
),
|
|
752
|
+
...diffWholesaleFeed(
|
|
753
|
+
"signal",
|
|
754
|
+
[...previousSignals.values()],
|
|
755
|
+
[...this.signalIndex.values()],
|
|
756
|
+
this.signalCacheScope
|
|
757
|
+
)
|
|
758
|
+
];
|
|
759
|
+
for (const event of events) {
|
|
798
760
|
this.emit("event", { event, synthetic: true });
|
|
799
|
-
this.emit(
|
|
800
|
-
};
|
|
801
|
-
for (const [id, product] of this.productIndex) {
|
|
802
|
-
const prev = previousProducts.get(id);
|
|
803
|
-
if (!prev) {
|
|
804
|
-
emit(
|
|
805
|
-
"product.created",
|
|
806
|
-
makeEvent("product.created", "product", id, {
|
|
807
|
-
product_id: id,
|
|
808
|
-
product,
|
|
809
|
-
applies_to: { scope: this.productCacheScope }
|
|
810
|
-
})
|
|
811
|
-
);
|
|
812
|
-
} else if (priceChanged(prev, product)) {
|
|
813
|
-
emit(
|
|
814
|
-
"product.priced",
|
|
815
|
-
makeEvent("product.priced", "product", id, {
|
|
816
|
-
product_id: id,
|
|
817
|
-
pricing_options: product.pricing_options ?? [],
|
|
818
|
-
applies_to: { scope: this.productCacheScope }
|
|
819
|
-
})
|
|
820
|
-
);
|
|
821
|
-
} else if (!isDeepStrictEqual(prev, product)) {
|
|
822
|
-
emit(
|
|
823
|
-
"product.updated",
|
|
824
|
-
makeEvent("product.updated", "product", id, {
|
|
825
|
-
product_id: id,
|
|
826
|
-
product,
|
|
827
|
-
applies_to: { scope: this.productCacheScope }
|
|
828
|
-
})
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
for (const [id] of previousProducts) {
|
|
833
|
-
if (!this.productIndex.has(id)) {
|
|
834
|
-
emit(
|
|
835
|
-
"product.removed",
|
|
836
|
-
makeEvent("product.removed", "product", id, {
|
|
837
|
-
product_id: id,
|
|
838
|
-
applies_to: { scope: this.productCacheScope }
|
|
839
|
-
})
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
for (const [id, signal] of this.signalIndex) {
|
|
844
|
-
const prev = previousSignals.get(id);
|
|
845
|
-
if (!prev) {
|
|
846
|
-
emit(
|
|
847
|
-
"signal.created",
|
|
848
|
-
makeEvent("signal.created", "signal", id, {
|
|
849
|
-
signal_agent_segment_id: id,
|
|
850
|
-
signal,
|
|
851
|
-
applies_to: { scope: this.signalCacheScope }
|
|
852
|
-
})
|
|
853
|
-
);
|
|
854
|
-
} else if (signalPriceChanged(prev, signal)) {
|
|
855
|
-
emit(
|
|
856
|
-
"signal.priced",
|
|
857
|
-
makeEvent("signal.priced", "signal", id, {
|
|
858
|
-
signal_agent_segment_id: id,
|
|
859
|
-
pricing_options: signal.pricing_options ?? [],
|
|
860
|
-
applies_to: { scope: this.signalCacheScope }
|
|
861
|
-
})
|
|
862
|
-
);
|
|
863
|
-
} else if (!isDeepStrictEqual(prev, signal)) {
|
|
864
|
-
emit(
|
|
865
|
-
"signal.updated",
|
|
866
|
-
makeEvent("signal.updated", "signal", id, {
|
|
867
|
-
signal_agent_segment_id: id,
|
|
868
|
-
signal,
|
|
869
|
-
applies_to: { scope: this.signalCacheScope }
|
|
870
|
-
})
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
for (const [id] of previousSignals) {
|
|
875
|
-
if (!this.signalIndex.has(id)) {
|
|
876
|
-
emit(
|
|
877
|
-
"signal.removed",
|
|
878
|
-
makeEvent("signal.removed", "signal", id, {
|
|
879
|
-
signal_agent_segment_id: id,
|
|
880
|
-
applies_to: { scope: this.signalCacheScope }
|
|
881
|
-
})
|
|
882
|
-
);
|
|
883
|
-
}
|
|
761
|
+
this.emit(event.event_type, { event, synthetic: true });
|
|
884
762
|
}
|
|
885
763
|
}
|
|
886
764
|
// ====== Private: search ======
|
|
@@ -1057,16 +935,6 @@ function assertOptionalDate(value, field, invalid) {
|
|
|
1057
935
|
function parsePersistedDate(value) {
|
|
1058
936
|
return value === void 0 ? void 0 : new Date(value);
|
|
1059
937
|
}
|
|
1060
|
-
function priceChanged(prev, next) {
|
|
1061
|
-
const a = prev.pricing_options;
|
|
1062
|
-
const b = next.pricing_options;
|
|
1063
|
-
return !isDeepStrictEqual(a, b);
|
|
1064
|
-
}
|
|
1065
|
-
function signalPriceChanged(prev, next) {
|
|
1066
|
-
const a = prev.pricing_options;
|
|
1067
|
-
const b = next.pricing_options;
|
|
1068
|
-
return !isDeepStrictEqual(a, b);
|
|
1069
|
-
}
|
|
1070
938
|
function isUuidV7(value) {
|
|
1071
939
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
1072
940
|
}
|
|
@@ -1076,13 +944,6 @@ function compareUuidV7(a, b) {
|
|
|
1076
944
|
const normalizedB = b.toLowerCase();
|
|
1077
945
|
return normalizedA === normalizedB ? 0 : normalizedA > normalizedB ? 1 : -1;
|
|
1078
946
|
}
|
|
1079
|
-
function mergeFeedMetadata(current, body) {
|
|
1080
|
-
return {
|
|
1081
|
-
wholesaleFeedVersion: typeof body.wholesale_feed_version === "string" ? body.wholesale_feed_version : current.wholesaleFeedVersion,
|
|
1082
|
-
pricingVersion: typeof body.pricing_version === "string" ? body.pricing_version : current.pricingVersion,
|
|
1083
|
-
cacheScope: body.cache_scope === "public" || body.cache_scope === "account" ? body.cache_scope : current.cacheScope
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
947
|
async function sleep(ms) {
|
|
1087
948
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1088
949
|
}
|
package/docs/README.md
CHANGED
|
@@ -25,6 +25,12 @@ Use the [buyer quick start](./guides/BUYER-QUICKSTART-3.2.md). For the complete
|
|
|
25
25
|
tool and type inventory, use [llms.txt](./llms.txt) and the
|
|
26
26
|
[type summary](./TYPE-SUMMARY.md).
|
|
27
27
|
|
|
28
|
+
## Upgrade from SDK 14.0
|
|
29
|
+
|
|
30
|
+
Use the [14.0-to-14.1 upgrade checklist](./migration-14.0-to-14.1.md) for
|
|
31
|
+
required changes, opt-in features, and stricter signature verification. SDK
|
|
32
|
+
14.1.0 continues to use AdCP 3.2.1 on the wire.
|
|
33
|
+
|
|
28
34
|
## Upgrade from SDK 13
|
|
29
35
|
|
|
30
36
|
Start with the [release-bound upgrade worksheet](./migration-14.x-rc-worksheet.md),
|
package/docs/TYPE-SUMMARY.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AdCP Type Summary
|
|
2
2
|
|
|
3
|
-
> Generated at: 2026-10-
|
|
4
|
-
> @adcp/sdk v14.
|
|
3
|
+
> Generated at: 2026-10-05
|
|
4
|
+
> @adcp/sdk v14.2.0
|
|
5
5
|
|
|
6
6
|
Curated reference of the types that matter for using the AdCP client. For full generated types see `src/lib/types/tools.generated.ts` and `src/lib/types/core.generated.ts`.
|
|
7
7
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Buyer-owned storage and scheduling
|
|
2
|
+
|
|
3
|
+
See the [adapter contracts and recovery guide](https://github.com/adcontextprotocol/adcp-client/blob/main/docs/development/BUYER-STORAGE.md) for timer-free catalog mirrors, CAS provisioning, dispatch hooks, and caller-owned replay keys.
|
|
@@ -71,7 +71,9 @@ from authoritative `list_accounts`. The notification's status is not trusted as
|
|
|
71
71
|
a snapshot, which avoids reordered deliveries overwriting current state.
|
|
72
72
|
|
|
73
73
|
The registry is in memory by default. Set `accountStorage` to an adapter with
|
|
74
|
-
`get(key)` and `set(key, entry)` for persistence.
|
|
74
|
+
`get(key)` and `set(key, entry)` for persistence. For multiple processes, add
|
|
75
|
+
revision-checked `compareAndSet` writes and dispatch ledger hooks; see
|
|
76
|
+
[buyer storage and scheduling](BUYER-STORAGE.md). Keys partition by seller URI,
|
|
75
77
|
protocol, and caller scope. Client-credentials OAuth uses the stable client ID, token endpoint, scopes, and resource. Authorization-code OAuth pins this client's initial grant fingerprint, keeping distinct user grants apart while automatic refreshes preserve its partition. Create a new client when switching users. Other credential modes fingerprint credentials;
|
|
76
78
|
for continuity across token rotations, supply `accountRegistryScope` from a
|
|
77
79
|
trusted stable principal identifier. Never share that scope between tenants.
|