@adcp/sdk 14.0.0 → 14.1.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/adapters/implicit-account-store.d.mts +12 -7
- package/dist/lib/adapters/implicit-account-store.d.ts +12 -7
- package/dist/lib/adapters/implicit-account-store.js +69 -15
- package/dist/lib/adapters/implicit-account-store.mjs +69 -15
- package/dist/lib/core/AgentClient.d.mts +1 -0
- package/dist/lib/core/AgentClient.d.ts +1 -0
- package/dist/lib/core/AgentClient.js +3 -0
- package/dist/lib/core/AgentClient.mjs +3 -0
- package/dist/lib/core/SingleAgentClient.d.mts +31 -1
- package/dist/lib/core/SingleAgentClient.d.ts +31 -1
- package/dist/lib/core/SingleAgentClient.js +355 -35
- package/dist/lib/core/SingleAgentClient.mjs +365 -37
- package/dist/lib/core/TaskExecutor.js +2 -1
- package/dist/lib/core/TaskExecutor.mjs +2 -1
- package/dist/lib/core/account-key.d.mts +3 -0
- package/dist/lib/core/account-key.d.ts +3 -0
- package/dist/lib/core/account-key.js +41 -0
- package/dist/lib/core/account-key.mjs +17 -0
- package/dist/lib/core/account-resolution.d.mts +2 -0
- package/dist/lib/core/account-resolution.d.ts +2 -0
- package/dist/lib/core/buyer-account-registry.d.mts +65 -0
- package/dist/lib/core/buyer-account-registry.d.ts +65 -0
- package/dist/lib/core/buyer-account-registry.js +518 -0
- package/dist/lib/core/buyer-account-registry.mjs +494 -0
- package/dist/lib/core/product-cache.d.mts +18 -0
- package/dist/lib/core/product-cache.d.ts +18 -0
- package/dist/lib/core/product-cache.js +137 -0
- package/dist/lib/core/product-cache.mjs +112 -0
- package/dist/lib/errors/index.d.mts +40 -1
- package/dist/lib/errors/index.d.ts +40 -1
- package/dist/lib/errors/index.js +69 -3
- package/dist/lib/errors/index.mjs +64 -3
- package/dist/lib/governance/authorization.d.mts +17 -1
- package/dist/lib/governance/authorization.d.ts +17 -1
- package/dist/lib/governance/authorization.js +55 -7
- package/dist/lib/governance/authorization.mjs +59 -7
- package/dist/lib/governance/index.d.mts +2 -2
- package/dist/lib/governance/index.d.ts +2 -2
- package/dist/lib/governance/index.js +2 -0
- package/dist/lib/governance/index.mjs +3 -1
- package/dist/lib/index.d.mts +5 -3
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.js +20 -0
- package/dist/lib/index.mjs +19 -0
- package/dist/lib/protocols/a2a.js +9 -1
- package/dist/lib/protocols/a2a.mjs +9 -1
- package/dist/lib/protocols/index.js +9 -2
- package/dist/lib/protocols/index.mjs +9 -2
- package/dist/lib/protocols/mcp-modern.js +2 -1
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +5 -2
- package/dist/lib/protocols/mcp.mjs +5 -2
- package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
- package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
- package/dist/lib/protocols/rawResponseCapture.js +41 -29
- package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
- package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
- package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
- package/dist/lib/protocols/signedRequestRejection.js +209 -0
- package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
- package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
- package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
- package/dist/lib/protocols/transportDiagnostics.js +2 -0
- package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
- package/dist/lib/registry/types.generated.d.mts +112 -45
- package/dist/lib/registry/types.generated.d.ts +112 -45
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/account-provisioning.d.mts +2 -0
- package/dist/lib/server/account-provisioning.d.ts +2 -0
- package/dist/lib/server/account-provisioning.js +30 -0
- package/dist/lib/server/account-provisioning.mjs +6 -0
- package/dist/lib/server/account-reference-warnings.d.mts +12 -0
- package/dist/lib/server/account-reference-warnings.d.ts +12 -0
- package/dist/lib/server/account-reference-warnings.js +48 -0
- package/dist/lib/server/account-reference-warnings.mjs +23 -0
- package/dist/lib/server/auth-signature.js +1 -0
- package/dist/lib/server/auth-signature.mjs +1 -0
- package/dist/lib/server/create-adcp-server.d.mts +34 -0
- package/dist/lib/server/create-adcp-server.d.ts +34 -0
- package/dist/lib/server/create-adcp-server.js +225 -14
- package/dist/lib/server/create-adcp-server.mjs +225 -14
- package/dist/lib/server/decisioning/account.d.mts +2 -0
- package/dist/lib/server/decisioning/account.d.ts +2 -0
- package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
- package/dist/lib/server/index.d.mts +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.js +0 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
- package/dist/lib/signing/agent-resolver/errors.d.mts +1 -1
- package/dist/lib/signing/agent-resolver/errors.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +2 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +2 -1
- package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
- package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.js +101 -132
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +102 -133
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
- package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
- package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
- package/dist/lib/signing/brand-jwks.d.mts +27 -75
- package/dist/lib/signing/brand-jwks.d.ts +27 -75
- package/dist/lib/signing/brand-jwks.js +112 -182
- package/dist/lib/signing/brand-jwks.mjs +112 -182
- package/dist/lib/signing/errors.d.mts +3 -1
- package/dist/lib/signing/errors.d.ts +3 -1
- package/dist/lib/signing/errors.js +4 -1
- package/dist/lib/signing/errors.mjs +4 -1
- package/dist/lib/signing/jwks-https.d.mts +7 -0
- package/dist/lib/signing/jwks-https.d.ts +7 -0
- package/dist/lib/signing/jwks-https.js +31 -8
- package/dist/lib/signing/jwks-https.mjs +31 -8
- package/dist/lib/signing/jwks.d.mts +8 -0
- package/dist/lib/signing/jwks.d.ts +8 -0
- package/dist/lib/signing/middleware.js +2 -1
- package/dist/lib/signing/middleware.mjs +2 -1
- package/dist/lib/signing/publisher-pins.d.mts +11 -0
- package/dist/lib/signing/publisher-pins.d.ts +11 -0
- package/dist/lib/signing/publisher-pins.js +125 -0
- package/dist/lib/signing/publisher-pins.mjs +101 -0
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/types.d.mts +5 -0
- package/dist/lib/signing/types.d.ts +5 -0
- package/dist/lib/signing/verifier.js +49 -4
- package/dist/lib/signing/verifier.mjs +49 -4
- package/dist/lib/signing/webhook-verifier.d.mts +7 -2
- package/dist/lib/signing/webhook-verifier.d.ts +7 -2
- package/dist/lib/signing/webhook-verifier.js +42 -2
- package/dist/lib/signing/webhook-verifier.mjs +43 -3
- package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
- package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
- package/dist/lib/testing/storyboard/account-policy.js +35 -0
- package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
- package/dist/lib/testing/storyboard/context.js +6 -0
- package/dist/lib/testing/storyboard/context.mjs +6 -0
- package/dist/lib/testing/storyboard/request-builder.js +12 -2
- package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
- package/dist/lib/testing/storyboard/runner.js +3 -2
- package/dist/lib/testing/storyboard/runner.mjs +3 -2
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -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/wholesale-feed-sync/sync.d.mts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.d.ts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.js +92 -21
- package/dist/lib/wholesale-feed-sync/sync.mjs +92 -21
- package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
- package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUILD-AN-AGENT.md +2 -2
- package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +104 -0
- package/docs/guides/SIGNING-GUIDE.md +16 -7
- package/docs/guides/account-resolution.md +86 -0
- package/docs/llms.txt +3 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-4.x-to-5.x.md +1 -0
- package/docs/migration-agent-resolution-3.3.md +123 -0
- package/docs/recipes/verifying-inbound-webhooks.md +56 -15
- package/package.json +2 -2
|
@@ -25,6 +25,7 @@ var import_node_events = require("node:events");
|
|
|
25
25
|
var import_node_util = require("node:util");
|
|
26
26
|
var import_node_crypto = require("node:crypto");
|
|
27
27
|
var import_webhook_notification = require('./webhook-notification.js');
|
|
28
|
+
const bootstrapReadFailures = /* @__PURE__ */ new WeakSet();
|
|
28
29
|
const DEFAULT_PROBE_INTERVAL_MS = 6e5;
|
|
29
30
|
const DEFAULT_CAPABILITY_REFRESH_INTERVAL_MS = 864e5;
|
|
30
31
|
const DEFAULT_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
@@ -172,12 +173,13 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
172
173
|
const epoch = this.lifecycleEpoch;
|
|
173
174
|
if (!await this.restorePersistedState(epoch)) return;
|
|
174
175
|
if (!await this.resolveMode(epoch)) return;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
try {
|
|
177
|
+
await this.bootstrap({ epoch });
|
|
178
|
+
} finally {
|
|
179
|
+
if (this.isLifecycleCurrent(epoch)) {
|
|
180
|
+
if (this._mode === "auto-poll") this.scheduleProbe(epoch);
|
|
181
|
+
if (this.capabilityRefreshIntervalMs > 0) this.scheduleCapabilityRefresh(epoch);
|
|
182
|
+
}
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
/** Stop all background activity. Preserves in-memory state and version tokens. */
|
|
@@ -187,7 +189,8 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
187
189
|
if (this.capabilityTimer) clearTimeout(this.capabilityTimer);
|
|
188
190
|
this.probeTimer = null;
|
|
189
191
|
this.capabilityTimer = null;
|
|
190
|
-
if (this._state === "syncing" || this._state === "bootstrapping"
|
|
192
|
+
if (this._state === "syncing" || this._state === "bootstrapping" || this._state === "degraded")
|
|
193
|
+
this.setState("idle");
|
|
191
194
|
}
|
|
192
195
|
/**
|
|
193
196
|
* Stop, clear all indexes and version tokens. Call `start()` again to
|
|
@@ -220,7 +223,7 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
220
223
|
async refresh() {
|
|
221
224
|
const epoch = this.lifecycleEpoch;
|
|
222
225
|
this.emit("resyncing", { reason: "manual" });
|
|
223
|
-
await this.bootstrap({ emitDiffs: true, epoch });
|
|
226
|
+
await this.bootstrap({ emitDiffs: true, epoch, propagateFailure: true });
|
|
224
227
|
}
|
|
225
228
|
/**
|
|
226
229
|
* Apply one legacy-view account-level wholesale feed webhook to the local mirror.
|
|
@@ -279,8 +282,10 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
279
282
|
try {
|
|
280
283
|
if (!await this.recoverFromBulkChange(event, epoch)) return;
|
|
281
284
|
} catch (err) {
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
if (!(err instanceof Error && bootstrapReadFailures.has(err))) {
|
|
286
|
+
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
287
|
+
this.rememberLastWebhookEventId(event.event_id);
|
|
288
|
+
}
|
|
284
289
|
throw err;
|
|
285
290
|
}
|
|
286
291
|
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
@@ -351,6 +356,8 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
351
356
|
const epoch = options.epoch ?? this.lifecycleEpoch;
|
|
352
357
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
353
358
|
this.setState("bootstrapping");
|
|
359
|
+
const previousLastSyncedAt = this._lastSyncedAt;
|
|
360
|
+
let reportedFailure;
|
|
354
361
|
try {
|
|
355
362
|
const previousProducts = new Map(this.productIndex);
|
|
356
363
|
const previousSignals = new Map(this.signalIndex);
|
|
@@ -364,10 +371,32 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
364
371
|
if (refreshProducts) {
|
|
365
372
|
productResult = await this.bootstrapProducts(epoch);
|
|
366
373
|
if (productResult.cancelled) return false;
|
|
374
|
+
if (productResult.failure) {
|
|
375
|
+
const failure = productResult.failure;
|
|
376
|
+
reportedFailure = failure.error;
|
|
377
|
+
this.handleBootstrapFailure(failure);
|
|
378
|
+
if (options.propagateFailure) {
|
|
379
|
+
bootstrapReadFailures.add(failure.error);
|
|
380
|
+
if (failure.adcpError) Object.assign(failure.error, { adcpError: failure.adcpError });
|
|
381
|
+
throw failure.error;
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
367
385
|
}
|
|
368
386
|
if (refreshSignals) {
|
|
369
387
|
signalResult = await this.bootstrapSignals(epoch);
|
|
370
388
|
if (signalResult.cancelled) return false;
|
|
389
|
+
if (signalResult.failure) {
|
|
390
|
+
const failure = signalResult.failure;
|
|
391
|
+
reportedFailure = failure.error;
|
|
392
|
+
this.handleBootstrapFailure(failure);
|
|
393
|
+
if (options.propagateFailure) {
|
|
394
|
+
bootstrapReadFailures.add(failure.error);
|
|
395
|
+
if (failure.adcpError) Object.assign(failure.error, { adcpError: failure.adcpError });
|
|
396
|
+
throw failure.error;
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
371
400
|
}
|
|
372
401
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
373
402
|
if (refreshProducts && productResult) {
|
|
@@ -401,13 +430,22 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
401
430
|
return true;
|
|
402
431
|
} catch (err) {
|
|
403
432
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
404
|
-
|
|
433
|
+
if (err === reportedFailure) throw err;
|
|
434
|
+
this._lastSyncedAt = previousLastSyncedAt;
|
|
435
|
+
this.setState(this._lastSyncedAt ? "degraded" : "error");
|
|
405
436
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
406
437
|
this.errorHandler?.(error);
|
|
407
|
-
this.emit("error", { error });
|
|
438
|
+
if (this.listenerCount("error") > 0) this.emit("error", { error });
|
|
439
|
+
if (options.propagateFailure) bootstrapReadFailures.add(error);
|
|
408
440
|
throw error;
|
|
409
441
|
}
|
|
410
442
|
}
|
|
443
|
+
handleBootstrapFailure(failure) {
|
|
444
|
+
this.setState(this._lastSyncedAt ? "degraded" : "error");
|
|
445
|
+
this.errorHandler?.(failure.error);
|
|
446
|
+
if (this.listenerCount("error") > 0) this.emit("error", failure);
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
411
449
|
/** Returns `true` when the seller short-circuited with `unchanged: true`. */
|
|
412
450
|
async bootstrapProducts(epoch) {
|
|
413
451
|
let cursor;
|
|
@@ -425,8 +463,24 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
425
463
|
}
|
|
426
464
|
const result = await this.client.getProducts(params);
|
|
427
465
|
if (!this.isLifecycleCurrent(epoch)) return { cancelled: true, unchanged: false, items: into, metadata };
|
|
466
|
+
if (result.success === false || result.status !== void 0 && result.status !== "completed" && result.status !== "success") {
|
|
467
|
+
return {
|
|
468
|
+
cancelled: false,
|
|
469
|
+
unchanged: false,
|
|
470
|
+
items: into,
|
|
471
|
+
metadata,
|
|
472
|
+
failure: { error: new Error(result.error ?? "Product bootstrap failed"), adcpError: result.adcpError }
|
|
473
|
+
};
|
|
474
|
+
}
|
|
428
475
|
const body = result.data;
|
|
429
|
-
if (!body)
|
|
476
|
+
if (!body)
|
|
477
|
+
return {
|
|
478
|
+
cancelled: false,
|
|
479
|
+
unchanged: false,
|
|
480
|
+
items: into,
|
|
481
|
+
metadata,
|
|
482
|
+
failure: { error: new Error("Product bootstrap did not return a completed catalog") }
|
|
483
|
+
};
|
|
430
484
|
if (body.unchanged) {
|
|
431
485
|
metadata = mergeFeedMetadata(metadata, body);
|
|
432
486
|
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
@@ -457,8 +511,24 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
457
511
|
}
|
|
458
512
|
const result = await this.client.getSignals(params);
|
|
459
513
|
if (!this.isLifecycleCurrent(epoch)) return { cancelled: true, unchanged: false, items: into, metadata };
|
|
514
|
+
if (result.success === false || result.status !== void 0 && result.status !== "completed" && result.status !== "success") {
|
|
515
|
+
return {
|
|
516
|
+
cancelled: false,
|
|
517
|
+
unchanged: false,
|
|
518
|
+
items: into,
|
|
519
|
+
metadata,
|
|
520
|
+
failure: { error: new Error(result.error ?? "Signal bootstrap failed"), adcpError: result.adcpError }
|
|
521
|
+
};
|
|
522
|
+
}
|
|
460
523
|
const body = result.data;
|
|
461
|
-
if (!body)
|
|
524
|
+
if (!body)
|
|
525
|
+
return {
|
|
526
|
+
cancelled: false,
|
|
527
|
+
unchanged: false,
|
|
528
|
+
items: into,
|
|
529
|
+
metadata,
|
|
530
|
+
failure: { error: new Error("Signal bootstrap did not return a completed catalog") }
|
|
531
|
+
};
|
|
462
532
|
if (body.unchanged) {
|
|
463
533
|
metadata = mergeFeedMetadata(metadata, body);
|
|
464
534
|
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
@@ -482,13 +552,13 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
482
552
|
);
|
|
483
553
|
}
|
|
484
554
|
const entities = affected === "product" ? "products" : "signals";
|
|
485
|
-
return this.bootstrap({ emitDiffs: true, entities, epoch });
|
|
555
|
+
return this.bootstrap({ emitDiffs: true, entities, epoch, propagateFailure: true });
|
|
486
556
|
}
|
|
487
557
|
async recoverFromVersionMismatch(event, epoch = this.lifecycleEpoch) {
|
|
488
558
|
this.emit("resyncing", { reason: "version_mismatch" });
|
|
489
559
|
const beforeVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
490
560
|
for (let attempt = 1; attempt <= VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
491
|
-
const recovered = await this.bootstrap({ emitDiffs: true, epoch });
|
|
561
|
+
const recovered = await this.bootstrap({ emitDiffs: true, epoch, propagateFailure: true });
|
|
492
562
|
if (!recovered) return false;
|
|
493
563
|
const afterVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
494
564
|
if (afterVersion !== beforeVersion) return true;
|
|
@@ -510,11 +580,12 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
510
580
|
await this.probeVersion(epoch);
|
|
511
581
|
} catch (err) {
|
|
512
582
|
if (!this.isLifecycleCurrent(epoch)) return;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
583
|
+
if (this._state !== "degraded" && this._state !== "error") {
|
|
584
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
585
|
+
this.handleBootstrapFailure({ error });
|
|
586
|
+
}
|
|
516
587
|
}
|
|
517
|
-
if (this.isLifecycleCurrent(epoch) && this._state === "syncing" && this._mode === "auto-poll") {
|
|
588
|
+
if (this.isLifecycleCurrent(epoch) && (this._state === "syncing" || this._state === "degraded" || this._state === "error") && this._mode === "auto-poll") {
|
|
518
589
|
this.scheduleProbe(epoch);
|
|
519
590
|
}
|
|
520
591
|
}
|
|
@@ -541,7 +612,7 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
541
612
|
this.emit("error", { error });
|
|
542
613
|
this.errorHandler?.(error);
|
|
543
614
|
}
|
|
544
|
-
if (this.isLifecycleCurrent(epoch) && this._state === "syncing" && this.capabilityRefreshIntervalMs > 0) {
|
|
615
|
+
if (this.isLifecycleCurrent(epoch) && (this._state === "syncing" || this._state === "degraded" || this._state === "error") && this.capabilityRefreshIntervalMs > 0) {
|
|
545
616
|
this.scheduleCapabilityRefresh(epoch);
|
|
546
617
|
}
|
|
547
618
|
}
|
|
@@ -2,6 +2,7 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { assertLegacyWholesaleFeedRepresentation } from "./webhook-notification.mjs";
|
|
5
|
+
const bootstrapReadFailures = /* @__PURE__ */ new WeakSet();
|
|
5
6
|
const DEFAULT_PROBE_INTERVAL_MS = 6e5;
|
|
6
7
|
const DEFAULT_CAPABILITY_REFRESH_INTERVAL_MS = 864e5;
|
|
7
8
|
const DEFAULT_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
@@ -149,12 +150,13 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
149
150
|
const epoch = this.lifecycleEpoch;
|
|
150
151
|
if (!await this.restorePersistedState(epoch)) return;
|
|
151
152
|
if (!await this.resolveMode(epoch)) return;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
try {
|
|
154
|
+
await this.bootstrap({ epoch });
|
|
155
|
+
} finally {
|
|
156
|
+
if (this.isLifecycleCurrent(epoch)) {
|
|
157
|
+
if (this._mode === "auto-poll") this.scheduleProbe(epoch);
|
|
158
|
+
if (this.capabilityRefreshIntervalMs > 0) this.scheduleCapabilityRefresh(epoch);
|
|
159
|
+
}
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
/** Stop all background activity. Preserves in-memory state and version tokens. */
|
|
@@ -164,7 +166,8 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
164
166
|
if (this.capabilityTimer) clearTimeout(this.capabilityTimer);
|
|
165
167
|
this.probeTimer = null;
|
|
166
168
|
this.capabilityTimer = null;
|
|
167
|
-
if (this._state === "syncing" || this._state === "bootstrapping"
|
|
169
|
+
if (this._state === "syncing" || this._state === "bootstrapping" || this._state === "degraded")
|
|
170
|
+
this.setState("idle");
|
|
168
171
|
}
|
|
169
172
|
/**
|
|
170
173
|
* Stop, clear all indexes and version tokens. Call `start()` again to
|
|
@@ -197,7 +200,7 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
197
200
|
async refresh() {
|
|
198
201
|
const epoch = this.lifecycleEpoch;
|
|
199
202
|
this.emit("resyncing", { reason: "manual" });
|
|
200
|
-
await this.bootstrap({ emitDiffs: true, epoch });
|
|
203
|
+
await this.bootstrap({ emitDiffs: true, epoch, propagateFailure: true });
|
|
201
204
|
}
|
|
202
205
|
/**
|
|
203
206
|
* Apply one legacy-view account-level wholesale feed webhook to the local mirror.
|
|
@@ -256,8 +259,10 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
256
259
|
try {
|
|
257
260
|
if (!await this.recoverFromBulkChange(event, epoch)) return;
|
|
258
261
|
} catch (err) {
|
|
259
|
-
|
|
260
|
-
|
|
262
|
+
if (!(err instanceof Error && bootstrapReadFailures.has(err))) {
|
|
263
|
+
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
264
|
+
this.rememberLastWebhookEventId(event.event_id);
|
|
265
|
+
}
|
|
261
266
|
throw err;
|
|
262
267
|
}
|
|
263
268
|
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
@@ -328,6 +333,8 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
328
333
|
const epoch = options.epoch ?? this.lifecycleEpoch;
|
|
329
334
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
330
335
|
this.setState("bootstrapping");
|
|
336
|
+
const previousLastSyncedAt = this._lastSyncedAt;
|
|
337
|
+
let reportedFailure;
|
|
331
338
|
try {
|
|
332
339
|
const previousProducts = new Map(this.productIndex);
|
|
333
340
|
const previousSignals = new Map(this.signalIndex);
|
|
@@ -341,10 +348,32 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
341
348
|
if (refreshProducts) {
|
|
342
349
|
productResult = await this.bootstrapProducts(epoch);
|
|
343
350
|
if (productResult.cancelled) return false;
|
|
351
|
+
if (productResult.failure) {
|
|
352
|
+
const failure = productResult.failure;
|
|
353
|
+
reportedFailure = failure.error;
|
|
354
|
+
this.handleBootstrapFailure(failure);
|
|
355
|
+
if (options.propagateFailure) {
|
|
356
|
+
bootstrapReadFailures.add(failure.error);
|
|
357
|
+
if (failure.adcpError) Object.assign(failure.error, { adcpError: failure.adcpError });
|
|
358
|
+
throw failure.error;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
344
362
|
}
|
|
345
363
|
if (refreshSignals) {
|
|
346
364
|
signalResult = await this.bootstrapSignals(epoch);
|
|
347
365
|
if (signalResult.cancelled) return false;
|
|
366
|
+
if (signalResult.failure) {
|
|
367
|
+
const failure = signalResult.failure;
|
|
368
|
+
reportedFailure = failure.error;
|
|
369
|
+
this.handleBootstrapFailure(failure);
|
|
370
|
+
if (options.propagateFailure) {
|
|
371
|
+
bootstrapReadFailures.add(failure.error);
|
|
372
|
+
if (failure.adcpError) Object.assign(failure.error, { adcpError: failure.adcpError });
|
|
373
|
+
throw failure.error;
|
|
374
|
+
}
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
348
377
|
}
|
|
349
378
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
350
379
|
if (refreshProducts && productResult) {
|
|
@@ -378,13 +407,22 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
378
407
|
return true;
|
|
379
408
|
} catch (err) {
|
|
380
409
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
381
|
-
|
|
410
|
+
if (err === reportedFailure) throw err;
|
|
411
|
+
this._lastSyncedAt = previousLastSyncedAt;
|
|
412
|
+
this.setState(this._lastSyncedAt ? "degraded" : "error");
|
|
382
413
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
383
414
|
this.errorHandler?.(error);
|
|
384
|
-
this.emit("error", { error });
|
|
415
|
+
if (this.listenerCount("error") > 0) this.emit("error", { error });
|
|
416
|
+
if (options.propagateFailure) bootstrapReadFailures.add(error);
|
|
385
417
|
throw error;
|
|
386
418
|
}
|
|
387
419
|
}
|
|
420
|
+
handleBootstrapFailure(failure) {
|
|
421
|
+
this.setState(this._lastSyncedAt ? "degraded" : "error");
|
|
422
|
+
this.errorHandler?.(failure.error);
|
|
423
|
+
if (this.listenerCount("error") > 0) this.emit("error", failure);
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
388
426
|
/** Returns `true` when the seller short-circuited with `unchanged: true`. */
|
|
389
427
|
async bootstrapProducts(epoch) {
|
|
390
428
|
let cursor;
|
|
@@ -402,8 +440,24 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
402
440
|
}
|
|
403
441
|
const result = await this.client.getProducts(params);
|
|
404
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
|
+
}
|
|
405
452
|
const body = result.data;
|
|
406
|
-
if (!body)
|
|
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
|
+
};
|
|
407
461
|
if (body.unchanged) {
|
|
408
462
|
metadata = mergeFeedMetadata(metadata, body);
|
|
409
463
|
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
@@ -434,8 +488,24 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
434
488
|
}
|
|
435
489
|
const result = await this.client.getSignals(params);
|
|
436
490
|
if (!this.isLifecycleCurrent(epoch)) return { cancelled: true, unchanged: false, items: into, metadata };
|
|
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
|
+
}
|
|
437
500
|
const body = result.data;
|
|
438
|
-
if (!body)
|
|
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
|
+
};
|
|
439
509
|
if (body.unchanged) {
|
|
440
510
|
metadata = mergeFeedMetadata(metadata, body);
|
|
441
511
|
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
@@ -459,13 +529,13 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
459
529
|
);
|
|
460
530
|
}
|
|
461
531
|
const entities = affected === "product" ? "products" : "signals";
|
|
462
|
-
return this.bootstrap({ emitDiffs: true, entities, epoch });
|
|
532
|
+
return this.bootstrap({ emitDiffs: true, entities, epoch, propagateFailure: true });
|
|
463
533
|
}
|
|
464
534
|
async recoverFromVersionMismatch(event, epoch = this.lifecycleEpoch) {
|
|
465
535
|
this.emit("resyncing", { reason: "version_mismatch" });
|
|
466
536
|
const beforeVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
467
537
|
for (let attempt = 1; attempt <= VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
468
|
-
const recovered = await this.bootstrap({ emitDiffs: true, epoch });
|
|
538
|
+
const recovered = await this.bootstrap({ emitDiffs: true, epoch, propagateFailure: true });
|
|
469
539
|
if (!recovered) return false;
|
|
470
540
|
const afterVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
471
541
|
if (afterVersion !== beforeVersion) return true;
|
|
@@ -487,11 +557,12 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
487
557
|
await this.probeVersion(epoch);
|
|
488
558
|
} catch (err) {
|
|
489
559
|
if (!this.isLifecycleCurrent(epoch)) return;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
560
|
+
if (this._state !== "degraded" && this._state !== "error") {
|
|
561
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
562
|
+
this.handleBootstrapFailure({ error });
|
|
563
|
+
}
|
|
493
564
|
}
|
|
494
|
-
if (this.isLifecycleCurrent(epoch) && this._state === "syncing" && this._mode === "auto-poll") {
|
|
565
|
+
if (this.isLifecycleCurrent(epoch) && (this._state === "syncing" || this._state === "degraded" || this._state === "error") && this._mode === "auto-poll") {
|
|
495
566
|
this.scheduleProbe(epoch);
|
|
496
567
|
}
|
|
497
568
|
}
|
|
@@ -518,7 +589,7 @@ class WholesaleFeedSync extends EventEmitter {
|
|
|
518
589
|
this.emit("error", { error });
|
|
519
590
|
this.errorHandler?.(error);
|
|
520
591
|
}
|
|
521
|
-
if (this.isLifecycleCurrent(epoch) && this._state === "syncing" && this.capabilityRefreshIntervalMs > 0) {
|
|
592
|
+
if (this.isLifecycleCurrent(epoch) && (this._state === "syncing" || this._state === "degraded" || this._state === "error") && this.capabilityRefreshIntervalMs > 0) {
|
|
522
593
|
this.scheduleCapabilityRefresh(epoch);
|
|
523
594
|
}
|
|
524
595
|
}
|
|
@@ -22,7 +22,7 @@ export type WholesaleFeedSyncMode = 'manual' | 'auto-poll';
|
|
|
22
22
|
/**
|
|
23
23
|
* Lifecycle state of the sync engine.
|
|
24
24
|
*/
|
|
25
|
-
export type WholesaleFeedSyncState = 'idle' | 'bootstrapping' | 'syncing' | 'error';
|
|
25
|
+
export type WholesaleFeedSyncState = 'idle' | 'bootstrapping' | 'syncing' | 'degraded' | 'error';
|
|
26
26
|
/**
|
|
27
27
|
* Subset of `SingleAgentClient` that {@link WholesaleFeedSync} actually uses.
|
|
28
28
|
* Lets tests inject a minimal stub without constructing a full client.
|
|
@@ -178,8 +178,8 @@ export interface ResolvedCapabilities {
|
|
|
178
178
|
* picks a mode. Useful for UI mode badges.
|
|
179
179
|
* - `resyncing` — emitted before a `wholesale_feed.bulk_change` recovery
|
|
180
180
|
* re-bootstrap, webhook-version mismatch repair, or manual refresh.
|
|
181
|
-
* - `error` —
|
|
182
|
-
*
|
|
181
|
+
* - `error` — initial bootstrap, re-sync, or background probe failure.
|
|
182
|
+
* Failed refreshes preserve the last good mirror and retry on the next tick.
|
|
183
183
|
* - `stateChange` — fires on every {@link WholesaleFeedSyncState} transition.
|
|
184
184
|
*/
|
|
185
185
|
export interface WholesaleFeedSyncEvents {
|
|
@@ -200,6 +200,7 @@ export interface WholesaleFeedSyncEvents {
|
|
|
200
200
|
}];
|
|
201
201
|
error: [{
|
|
202
202
|
error: Error;
|
|
203
|
+
adcpError?: import('../core/ConversationTypes.mjs').AdcpErrorInfo;
|
|
203
204
|
}];
|
|
204
205
|
stateChange: [{
|
|
205
206
|
from: WholesaleFeedSyncState;
|
|
@@ -22,7 +22,7 @@ export type WholesaleFeedSyncMode = 'manual' | 'auto-poll';
|
|
|
22
22
|
/**
|
|
23
23
|
* Lifecycle state of the sync engine.
|
|
24
24
|
*/
|
|
25
|
-
export type WholesaleFeedSyncState = 'idle' | 'bootstrapping' | 'syncing' | 'error';
|
|
25
|
+
export type WholesaleFeedSyncState = 'idle' | 'bootstrapping' | 'syncing' | 'degraded' | 'error';
|
|
26
26
|
/**
|
|
27
27
|
* Subset of `SingleAgentClient` that {@link WholesaleFeedSync} actually uses.
|
|
28
28
|
* Lets tests inject a minimal stub without constructing a full client.
|
|
@@ -178,8 +178,8 @@ export interface ResolvedCapabilities {
|
|
|
178
178
|
* picks a mode. Useful for UI mode badges.
|
|
179
179
|
* - `resyncing` — emitted before a `wholesale_feed.bulk_change` recovery
|
|
180
180
|
* re-bootstrap, webhook-version mismatch repair, or manual refresh.
|
|
181
|
-
* - `error` —
|
|
182
|
-
*
|
|
181
|
+
* - `error` — initial bootstrap, re-sync, or background probe failure.
|
|
182
|
+
* Failed refreshes preserve the last good mirror and retry on the next tick.
|
|
183
183
|
* - `stateChange` — fires on every {@link WholesaleFeedSyncState} transition.
|
|
184
184
|
*/
|
|
185
185
|
export interface WholesaleFeedSyncEvents {
|
|
@@ -200,6 +200,7 @@ export interface WholesaleFeedSyncEvents {
|
|
|
200
200
|
}];
|
|
201
201
|
error: [{
|
|
202
202
|
error: Error;
|
|
203
|
+
adcpError?: import('../core/ConversationTypes').AdcpErrorInfo;
|
|
203
204
|
}];
|
|
204
205
|
stateChange: [{
|
|
205
206
|
from: WholesaleFeedSyncState;
|
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-04
|
|
4
|
+
> @adcp/sdk v14.1.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
|
|
|
@@ -671,7 +671,7 @@ import {
|
|
|
671
671
|
requireAuthenticatedOrSigned,
|
|
672
672
|
mcpToolNameResolver,
|
|
673
673
|
} from '@adcp/sdk/server';
|
|
674
|
-
import {
|
|
674
|
+
import { ResolvedAgentJwksResolver } from '@adcp/sdk/signing/server';
|
|
675
675
|
|
|
676
676
|
serve(
|
|
677
677
|
() =>
|
|
@@ -692,7 +692,7 @@ serve(
|
|
|
692
692
|
authenticate: requireAuthenticatedOrSigned({
|
|
693
693
|
signature: verifySignatureAsAuthenticator({
|
|
694
694
|
capability: { supported: true, required_for: ['create_media_buy', 'update_media_buy'], covers_content_digest: 'either' },
|
|
695
|
-
jwks: new
|
|
695
|
+
jwks: new ResolvedAgentJwksResolver('https://buyer.example/mcp', 'mcp'),
|
|
696
696
|
resolveOperation: mcpToolNameResolver,
|
|
697
697
|
}),
|
|
698
698
|
fallback: verifyApiKey({ keys: { sk_live_abc: { principal: 'acct_42' } } }),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Call a seller with AdCP 3.2
|
|
2
2
|
|
|
3
|
+
For a new seller, start with [account setup and public discovery](./FIRST-CALL-TO-A-SELLER.md).
|
|
4
|
+
|
|
3
5
|
For product possibility, accepted change rights, and current execution routes, use the [MediaBuy action assessment helpers](./MEDIA-BUY-ACTION-ASSESSMENT.md).
|
|
4
6
|
|
|
5
7
|
Requires Node.js `^20.19.0 || >=22.12.0`. Install SDK 14
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# First call to a seller
|
|
2
|
+
|
|
3
|
+
An account reference selects a provisioned account at that seller. Discovery
|
|
4
|
+
does not provision it. Until setup is complete, send a top-level `brand` on
|
|
5
|
+
tools that support public discovery, and omit `account`.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { AgentClient, createProductCache } from '@adcp/sdk';
|
|
9
|
+
|
|
10
|
+
const client = new AgentClient(sellerConfig, {
|
|
11
|
+
accountPolicy: 'auto',
|
|
12
|
+
productCache: createProductCache({ publicTtl: 60_000, accountTtl: 30_000 }),
|
|
13
|
+
});
|
|
14
|
+
const capabilities = await client.getCapabilities();
|
|
15
|
+
const brand = { domain: 'advertiser.example' };
|
|
16
|
+
const account = { brand, operator: 'agency.example' };
|
|
17
|
+
|
|
18
|
+
// Only use public discovery when the seller permits it.
|
|
19
|
+
if (!capabilities.account?.requiredForProducts) {
|
|
20
|
+
const publicCatalog = await client.getProducts({ brand, brief: 'Display inventory' });
|
|
21
|
+
if (!publicCatalog.success) console.error(publicCatalog.adcpError);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Choose billing terms before accepting them. This returns seller status and handle.
|
|
25
|
+
const setup = await client.accounts.ensure(account, {
|
|
26
|
+
billing: 'operator',
|
|
27
|
+
paymentTerms: 'net_30',
|
|
28
|
+
});
|
|
29
|
+
if (setup.status === 'active') {
|
|
30
|
+
const pricedCatalog = await client.getProducts({ account, brand, brief: 'Display inventory' });
|
|
31
|
+
if (!pricedCatalog.success) console.error(pricedCatalog.adcpError);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For agent-billed provisioning, pass `billingEntity` with the seller's required
|
|
36
|
+
business identity. `paymentTerms` and `billingEntity` are also accepted by
|
|
37
|
+
`resolveAccount()`. That helper keeps its existing pending-approval exception;
|
|
38
|
+
`accounts.ensure()` returns the status so callers can present the setup flow.
|
|
39
|
+
Non-active entries refresh through `list_accounts` when a seller handle is known.
|
|
40
|
+
An existing registry entry prevents repeated provisioning and silent acceptance
|
|
41
|
+
of new terms. To change terms, explicitly call `syncAccounts()`.
|
|
42
|
+
An explicit `ensure` with conflicting known setup terms refuses the change.
|
|
43
|
+
If the seller no longer lists an account, explicitly reestablish it with
|
|
44
|
+
`syncAccounts` after choosing terms; status repair never silently re-provisions it.
|
|
45
|
+
Caller cancellation stops waiting for setup; the single seller operation continues
|
|
46
|
+
so another caller cannot accidentally accept different terms concurrently.
|
|
47
|
+
Terminal setup failures permit an explicit retry. For interrupted async setup,
|
|
48
|
+
read `accounts.get(key).pendingTaskId`, reconcile that seller task, and pass its
|
|
49
|
+
completed rows to `accounts.observeSync([key], rows)` (or let the original
|
|
50
|
+
`syncAccounts` completion handle update the registry). Do not resubmit unresolved tasks.
|
|
51
|
+
|
|
52
|
+
For a seller that requires operator authentication, use `resolveAccount()` to
|
|
53
|
+
select an active account returned by `list_accounts`. An account ID echoed by
|
|
54
|
+
`sync_accounts` is a seller handle; implicit sellers still require the natural
|
|
55
|
+
key on subsequent calls. Do not replace it with `{ account_id }` automatically.
|
|
56
|
+
|
|
57
|
+
`accountPolicy` defaults to `'off'` to preserve existing requests. Opt-in
|
|
58
|
+
`'auto'` omits unknown accounts on public discovery tools that support `brand`;
|
|
59
|
+
it refuses required-account discovery, async discovery, and mutations until
|
|
60
|
+
the registry knows the key. `'strict'` refuses every unknown account-carrying
|
|
61
|
+
request. Both policies require an active account before spend commitments.
|
|
62
|
+
Explicit account filters on `list_accounts` remain filters.
|
|
63
|
+
Call `listAccounts` first when adopting these policies with existing opaque
|
|
64
|
+
account IDs so the registry knows those seller handles.
|
|
65
|
+
|
|
66
|
+
Provisioning is remembered per seller and caller. `syncAccounts()` and
|
|
67
|
+
`listAccounts()` update the registry on completed results; failed rows and dry
|
|
68
|
+
runs do not establish accounts. After verifying an `account.status_changed`
|
|
69
|
+
notification, call `client.accounts.applyStatusChange({ account_id })` to repair
|
|
70
|
+
from authoritative `list_accounts`. The notification's status is not trusted as
|
|
71
|
+
a snapshot, which avoids reordered deliveries overwriting current state.
|
|
72
|
+
|
|
73
|
+
The registry is in memory by default. Set `accountStorage` to an adapter with
|
|
74
|
+
`get(key)` and `set(key, entry)` for persistence. Keys partition by seller URI,
|
|
75
|
+
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
|
+
for continuity across token rotations, supply `accountRegistryScope` from a
|
|
77
|
+
trusted stable principal identifier. Never share that scope between tenants.
|
|
78
|
+
Request-signing key identity is also part of the default fingerprint, so distinct
|
|
79
|
+
signers stay isolated. Key rotation requires reprovisioning or a trusted stable
|
|
80
|
+
`accountRegistryScope` for the same principal.
|
|
81
|
+
Registry memory and aliases per handle are bounded by `accountRegistryMaxEntries` (default 10,000); durable adapters allow entry eviction and reload. Without storage, reaching capacity throws a setup error; raise the limit or configure storage for large rosters. `resolveAccount()` uses the memoized registry only when you opt in with `accountPolicy: 'auto' | 'strict'`, `accountStorage`, `accountRegistryScope`, or `accountRegistryMaxEntries`; that internal use does not enable observations of unrelated rosters. Otherwise it keeps the 14.0 behavior: every call sends `sync_accounts` and reflects the seller's current status. Memoized entries require explicit `syncAccounts()` or authoritative status repair when seller linkage expires or is replaced externally. Stored entries contain account references, seller handles, status, optional pending task IDs, and hashes of explicitly chosen setup terms;
|
|
82
|
+
billing entities and tokens are not persisted. Manual feed mode recovers through
|
|
83
|
+
an explicit `refresh()`; auto-poll mode also retries initial failures.
|
|
84
|
+
|
|
85
|
+
Typed `AccountNotFoundError`, `AccountSetupRequiredError`, and
|
|
86
|
+
`AccountPaymentRequiredError` carry `fault: 'buyer_setup'`. Health trackers
|
|
87
|
+
should exclude them from seller-health failure counts. Failed task results still
|
|
88
|
+
expose the protocol code in `result.adcpError` and the typed exception in
|
|
89
|
+
`result.errorInstance`.
|
|
90
|
+
|
|
91
|
+
The optional product cache uses TTLs in milliseconds and keeps at most `maxEntries` entries (default 1,000). It keeps public and
|
|
92
|
+
account-scoped responses apart and stores the feed version and pricing version
|
|
93
|
+
with each snapshot. Missing `cache_scope` prevents caching. Stale entries are
|
|
94
|
+
conditionally revalidated using their own tokens; failures remain visible to
|
|
95
|
+
the caller and never erase the last valid entry. Caller-authored feed or pricing validators
|
|
96
|
+
retain their unchanged-response semantics. Caches are scoped to the client
|
|
97
|
+
instance so different local verification policies cannot share filtered results.
|
|
98
|
+
|
|
99
|
+
`WholesaleFeedSync` similarly keeps its last good product and signal mirrors on
|
|
100
|
+
a failed refresh. Its error event includes `adcpError`; a mirror becomes
|
|
101
|
+
`degraded` until a successful refresh restores `syncing`. Initial failure sets
|
|
102
|
+
`error`. Exhaustive state switches must handle the new `degraded` member.
|
|
103
|
+
|
|
104
|
+
Manual `WholesaleFeedSync.refresh()` and webhook repairs reject failed catalog reads, including failed task results. A failed webhook repair remains eligible for redelivery; acknowledge it only after repair succeeds. Bootstrap `start()` still reports failed task results through its state and error callback/event, and schedules recovery in auto-poll mode.
|