@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.
Files changed (106) hide show
  1. package/dist/lib/core/SingleAgentClient.d.mts +4 -1
  2. package/dist/lib/core/SingleAgentClient.d.ts +4 -1
  3. package/dist/lib/core/SingleAgentClient.js +25 -4
  4. package/dist/lib/core/SingleAgentClient.mjs +25 -4
  5. package/dist/lib/core/TaskExecutor.d.mts +3 -1
  6. package/dist/lib/core/TaskExecutor.d.ts +3 -1
  7. package/dist/lib/core/TaskExecutor.js +15 -11
  8. package/dist/lib/core/TaskExecutor.mjs +15 -11
  9. package/dist/lib/core/buyer-account-registry.d.mts +30 -2
  10. package/dist/lib/core/buyer-account-registry.d.ts +30 -2
  11. package/dist/lib/core/buyer-account-registry.js +126 -42
  12. package/dist/lib/core/buyer-account-registry.mjs +127 -43
  13. package/dist/lib/index.d.mts +3 -2
  14. package/dist/lib/index.d.ts +3 -2
  15. package/dist/lib/index.js +9 -0
  16. package/dist/lib/index.mjs +12 -1
  17. package/dist/lib/net/agent-transport-fetch.d.mts +4 -0
  18. package/dist/lib/net/agent-transport-fetch.d.ts +4 -0
  19. package/dist/lib/net/agent-transport-fetch.js +18 -5
  20. package/dist/lib/net/agent-transport-fetch.mjs +17 -5
  21. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  22. package/dist/lib/signing/agent-resolver/errors.d.mts +2 -0
  23. package/dist/lib/signing/agent-resolver/errors.d.ts +2 -0
  24. package/dist/lib/signing/agent-resolver/errors.js +6 -0
  25. package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
  26. package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +9 -0
  27. package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +9 -0
  28. package/dist/lib/signing/agent-resolver/fetch-helpers.js +35 -1
  29. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +38 -2
  30. package/dist/lib/signing/agent-resolver/legacy-brand.js +1 -1
  31. package/dist/lib/signing/agent-resolver/legacy-brand.mjs +2 -2
  32. package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +1 -1
  33. package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +1 -1
  34. package/dist/lib/signing/agent-resolver/resolve-agent.js +47 -5
  35. package/dist/lib/signing/agent-resolver/resolve-agent.mjs +55 -6
  36. package/dist/lib/signing/brand-jwks.d.mts +2 -0
  37. package/dist/lib/signing/brand-jwks.d.ts +2 -0
  38. package/dist/lib/signing/brand-jwks.js +10 -2
  39. package/dist/lib/signing/brand-jwks.mjs +10 -2
  40. package/dist/lib/signing/errors.d.mts +4 -2
  41. package/dist/lib/signing/errors.d.ts +4 -2
  42. package/dist/lib/signing/errors.js +3 -1
  43. package/dist/lib/signing/errors.mjs +3 -1
  44. package/dist/lib/signing/verifier.js +1 -1
  45. package/dist/lib/signing/verifier.mjs +1 -1
  46. package/dist/lib/signing/webhook-verifier.js +2 -2
  47. package/dist/lib/signing/webhook-verifier.mjs +3 -3
  48. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  49. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  50. package/dist/lib/types/accept-proposal.d.ts +19 -1
  51. package/dist/lib/types/buy-products.d.ts +19 -1
  52. package/dist/lib/types/check-governance.d.ts +19 -1
  53. package/dist/lib/types/comply-test-controller.d.ts +19 -1
  54. package/dist/lib/types/control-media-buy.d.ts +19 -1
  55. package/dist/lib/types/core.generated.d.mts +14 -1
  56. package/dist/lib/types/core.generated.d.ts +14 -1
  57. package/dist/lib/types/create-media-buy.d.ts +14 -1
  58. package/dist/lib/types/get-media-buys.d.ts +14 -1
  59. package/dist/lib/types/get-products.d.ts +19 -1
  60. package/dist/lib/types/list-products.d.ts +19 -1
  61. package/dist/lib/types/refine-proposals.d.ts +19 -1
  62. package/dist/lib/types/request-proposals.d.ts +19 -1
  63. package/dist/lib/types/schemas.generated.d.ts +12 -3
  64. package/dist/lib/types/schemas.generated.js +4 -1
  65. package/dist/lib/types/schemas.generated.mjs +4 -1
  66. package/dist/lib/types/tools.generated.d.mts +14 -1
  67. package/dist/lib/types/tools.generated.d.ts +14 -1
  68. package/dist/lib/types/update-media-buy.d.ts +14 -1
  69. package/dist/lib/version.d.mts +3 -3
  70. package/dist/lib/version.d.ts +3 -3
  71. package/dist/lib/version.js +3 -3
  72. package/dist/lib/version.mjs +3 -3
  73. package/dist/lib/webhooks/index.d.mts +24 -0
  74. package/dist/lib/webhooks/index.d.ts +24 -0
  75. package/dist/lib/webhooks/index.js +50 -24
  76. package/dist/lib/webhooks/index.mjs +49 -24
  77. package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
  78. package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
  79. package/dist/lib/wholesale-feed-sync/index.js +7 -0
  80. package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
  81. package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
  82. package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
  83. package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
  84. package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
  85. package/dist/lib/wholesale-feed-sync/sync.d.mts +12 -29
  86. package/dist/lib/wholesale-feed-sync/sync.d.ts +12 -29
  87. package/dist/lib/wholesale-feed-sync/sync.js +153 -297
  88. package/dist/lib/wholesale-feed-sync/sync.mjs +158 -297
  89. package/docs/README.md +6 -0
  90. package/docs/TYPE-SUMMARY.md +2 -2
  91. package/docs/guides/BUYER-STORAGE.md +3 -0
  92. package/docs/guides/FIRST-CALL-TO-A-SELLER.md +3 -1
  93. package/docs/guides/account-resolution.md +46 -10
  94. package/docs/llms.txt +2 -2
  95. package/docs/migration-14.0-to-14.1.md +85 -0
  96. package/docs/migration-14.x-rc-worksheet.md +4 -4
  97. package/docs/migration-agent-resolution-3.3.md +5 -3
  98. package/docs/recipes/verifying-inbound-webhooks.md +4 -0
  99. package/package.json +2 -1
  100. package/skills/adcp-brand.previous/SKILL.md +0 -200
  101. package/skills/adcp-creative.previous/SKILL.md +0 -305
  102. package/skills/adcp-governance.previous/SKILL.md +0 -566
  103. package/skills/adcp-measurement.previous/SKILL.md +0 -136
  104. package/skills/adcp-media-buy.previous/SKILL.md +0 -556
  105. package/skills/adcp-si.previous/SKILL.md +0 -206
  106. package/skills/adcp-signals.previous/SKILL.md +0 -204
@@ -22,9 +22,12 @@ __export(resolve_agent_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(resolve_agent_exports);
24
24
  var import_tldts = require("tldts");
25
+ var import_node_net = require("node:net");
25
26
  var import_protocols = require('../../protocols/index.js');
26
27
  var import_domain = require('../../brand/domain.js');
27
28
  var import_capabilities_types = require('./capabilities-types.js');
29
+ var import_net = require('../../net/index.js');
30
+ var import_agent_transport_fetch = require('../../net/agent-transport-fetch.js');
28
31
  var import_operator_authorization = require('./operator-authorization.js');
29
32
  var import_legacy_brand = require('./legacy-brand.js');
30
33
  var import_consistency = require('./consistency.js');
@@ -42,7 +45,7 @@ async function resolveAgent(agentUrl, options = {}) {
42
45
  const caps = options.bodyCaps ?? {};
43
46
  const brandJsonCap = caps.brandJsonBytes ?? import_fetch_helpers.MAX_BRAND_JSON_BYTES;
44
47
  const jwksCap = caps.jwksBytes ?? import_fetch_helpers.MAX_JWKS_BYTES;
45
- const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp");
48
+ const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp", allowPrivateIp, timeoutMs);
46
49
  let capabilitiesPayload;
47
50
  let capabilitiesFetchedAt;
48
51
  try {
@@ -59,7 +62,7 @@ async function resolveAgent(agentUrl, options = {}) {
59
62
  } catch (err) {
60
63
  const detail = {
61
64
  agent_url: agentUrl,
62
- dns_error: err instanceof import_fetch_helpers.SafeFetchError ? err.transport : "fetch_failed",
65
+ ...(0, import_fetch_helpers.classifyDiscoveryFailure)(err),
63
66
  last_attempt_at: now()
64
67
  };
65
68
  pushTrace(trace, { step: 1, name: "fetch_capabilities", ok: false, url: agentUrl, detail });
@@ -368,10 +371,49 @@ function checkAllowPrivateIp(requested) {
368
371
  "resolveAgent: allowPrivateIp=true refused outside {NODE_ENV=test, NODE_ENV=development}. Set NODE_ENV appropriately for dev/test, or set ADCP_RESOLVER_ALLOW_PRIVATE_IP=1 as an explicit ops acknowledgment if you genuinely need private-IP discovery in this process."
369
372
  );
370
373
  }
371
- function defaultFetchCapabilities(agentUrl, protocol) {
374
+ function defaultFetchCapabilities(agentUrl, protocol, allowPrivateIp, timeoutMs) {
372
375
  return async () => {
373
- const client = protocol === "a2a" ? (0, import_protocols.createA2AClient)(agentUrl) : (0, import_protocols.createMCPClient)(agentUrl);
374
- return client.callTool("get_adcp_capabilities", {});
376
+ const url = new URL(agentUrl);
377
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
378
+ if (!allowPrivateIp && url.protocol !== "https:" || (0, import_node_net.isIP)(hostname) && ((0, import_net.isAlwaysBlocked)(hostname) || !allowPrivateIp && (0, import_net.isPrivateIp)(hostname)))
379
+ throw new import_agent_transport_fetch.AgentTransportPolicyError("Signing discovery URL refused by address or HTTPS policy.");
380
+ let failure;
381
+ const upstream = (0, import_agent_transport_fetch.createAgentTransportFetch)(agentUrl, {
382
+ allowPrivateIp,
383
+ allowPrivateInitialOrigin: false,
384
+ requireHttps: true
385
+ });
386
+ let postSucceeded = false;
387
+ const trustedFetchFn = async (input, init) => {
388
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
389
+ try {
390
+ const response = await upstream(input, init);
391
+ const isGet = method.toUpperCase() === "GET";
392
+ const observed = response.status >= 400 ? { dns_error: "fetch_failed", http_status: response.status } : void 0;
393
+ if (protocol === "mcp" && method.toUpperCase() === "POST" && response.ok) postSucceeded = true;
394
+ if (!(protocol === "mcp" && isGet && (postSucceeded || response.status === 405)) && !(isGet && observed && failure && !(0, import_fetch_helpers.isPermanentDiscoveryFailure)(failure) && (0, import_fetch_helpers.isPermanentDiscoveryFailure)(observed)))
395
+ failure = observed;
396
+ return response;
397
+ } catch (error) {
398
+ if (!(protocol === "mcp" && method.toUpperCase() === "GET" && postSucceeded))
399
+ failure = (0, import_fetch_helpers.classifyDiscoveryFailure)(error);
400
+ throw error;
401
+ }
402
+ };
403
+ const transport = { trustedFetchFn, requestTimeoutMs: timeoutMs };
404
+ const client = protocol === "a2a" ? (0, import_protocols.createA2AClient)(agentUrl, void 0, void 0, void 0, void 0, transport) : (0, import_protocols.createMCPClient)(agentUrl, void 0, void 0, void 0, void 0, transport);
405
+ try {
406
+ return await client.callTool("get_adcp_capabilities", {});
407
+ } catch (error) {
408
+ if (failure)
409
+ throw new import_fetch_helpers.SafeFetchError(
410
+ "capabilities",
411
+ failure.dns_error,
412
+ "Capabilities fetch failed",
413
+ failure.http_status
414
+ );
415
+ throw error;
416
+ }
375
417
  };
376
418
  }
377
419
  function findAuthorizedOperator(brandJson, agentEtld1, _now, _options) {
@@ -1,7 +1,10 @@
1
1
  import { parse as parseTld } from "tldts";
2
+ import { isIP } from "node:net";
2
3
  import { createA2AClient, createMCPClient } from "../../protocols/index.mjs";
3
4
  import { isDevelopmentBrandDomain } from "../../brand/domain.mjs";
4
5
  import { readBrandJsonUrl, readIdentityPosture } from "./capabilities-types.mjs";
6
+ import { isAlwaysBlocked, isPrivateIp } from "../../net/index.mjs";
7
+ import { AgentTransportPolicyError, createAgentTransportFetch } from "../../net/agent-transport-fetch.mjs";
5
8
  import { checkDelegatedOperatorAuthorization } from "./operator-authorization.mjs";
6
9
  import { fetchLegacyBrandJson } from "./legacy-brand.mjs";
7
10
  import {
@@ -11,7 +14,14 @@ import {
11
14
  } from "./consistency.mjs";
12
15
  import { AgentResolverError } from "./errors.mjs";
13
16
  import { eTldPlusOne, sameEtldPlusOne } from "./etld.mjs";
14
- import { MAX_BRAND_JSON_BYTES, MAX_JWKS_BYTES, safeFetchJson, SafeFetchError } from "./fetch-helpers.mjs";
17
+ import {
18
+ MAX_BRAND_JSON_BYTES,
19
+ MAX_JWKS_BYTES,
20
+ safeFetchJson,
21
+ SafeFetchError,
22
+ classifyDiscoveryFailure,
23
+ isPermanentDiscoveryFailure
24
+ } from "./fetch-helpers.mjs";
15
25
  import { unwrapProtocolResponse } from "../protocol-response.mjs";
16
26
  import { selectAgentByUrl, agentJwksUri, AgentSelectorError, canonicalAgentUrl } from "./select-agent.mjs";
17
27
  const DEFAULT_TIMEOUT_MS = 1e4;
@@ -23,7 +33,7 @@ async function resolveAgent(agentUrl, options = {}) {
23
33
  const caps = options.bodyCaps ?? {};
24
34
  const brandJsonCap = caps.brandJsonBytes ?? MAX_BRAND_JSON_BYTES;
25
35
  const jwksCap = caps.jwksBytes ?? MAX_JWKS_BYTES;
26
- const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp");
36
+ const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp", allowPrivateIp, timeoutMs);
27
37
  let capabilitiesPayload;
28
38
  let capabilitiesFetchedAt;
29
39
  try {
@@ -40,7 +50,7 @@ async function resolveAgent(agentUrl, options = {}) {
40
50
  } catch (err) {
41
51
  const detail = {
42
52
  agent_url: agentUrl,
43
- dns_error: err instanceof SafeFetchError ? err.transport : "fetch_failed",
53
+ ...classifyDiscoveryFailure(err),
44
54
  last_attempt_at: now()
45
55
  };
46
56
  pushTrace(trace, { step: 1, name: "fetch_capabilities", ok: false, url: agentUrl, detail });
@@ -349,10 +359,49 @@ function checkAllowPrivateIp(requested) {
349
359
  "resolveAgent: allowPrivateIp=true refused outside {NODE_ENV=test, NODE_ENV=development}. Set NODE_ENV appropriately for dev/test, or set ADCP_RESOLVER_ALLOW_PRIVATE_IP=1 as an explicit ops acknowledgment if you genuinely need private-IP discovery in this process."
350
360
  );
351
361
  }
352
- function defaultFetchCapabilities(agentUrl, protocol) {
362
+ function defaultFetchCapabilities(agentUrl, protocol, allowPrivateIp, timeoutMs) {
353
363
  return async () => {
354
- const client = protocol === "a2a" ? createA2AClient(agentUrl) : createMCPClient(agentUrl);
355
- return client.callTool("get_adcp_capabilities", {});
364
+ const url = new URL(agentUrl);
365
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
366
+ if (!allowPrivateIp && url.protocol !== "https:" || isIP(hostname) && (isAlwaysBlocked(hostname) || !allowPrivateIp && isPrivateIp(hostname)))
367
+ throw new AgentTransportPolicyError("Signing discovery URL refused by address or HTTPS policy.");
368
+ let failure;
369
+ const upstream = createAgentTransportFetch(agentUrl, {
370
+ allowPrivateIp,
371
+ allowPrivateInitialOrigin: false,
372
+ requireHttps: true
373
+ });
374
+ let postSucceeded = false;
375
+ const trustedFetchFn = async (input, init) => {
376
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
377
+ try {
378
+ const response = await upstream(input, init);
379
+ const isGet = method.toUpperCase() === "GET";
380
+ const observed = response.status >= 400 ? { dns_error: "fetch_failed", http_status: response.status } : void 0;
381
+ if (protocol === "mcp" && method.toUpperCase() === "POST" && response.ok) postSucceeded = true;
382
+ if (!(protocol === "mcp" && isGet && (postSucceeded || response.status === 405)) && !(isGet && observed && failure && !isPermanentDiscoveryFailure(failure) && isPermanentDiscoveryFailure(observed)))
383
+ failure = observed;
384
+ return response;
385
+ } catch (error) {
386
+ if (!(protocol === "mcp" && method.toUpperCase() === "GET" && postSucceeded))
387
+ failure = classifyDiscoveryFailure(error);
388
+ throw error;
389
+ }
390
+ };
391
+ const transport = { trustedFetchFn, requestTimeoutMs: timeoutMs };
392
+ const client = protocol === "a2a" ? createA2AClient(agentUrl, void 0, void 0, void 0, void 0, transport) : createMCPClient(agentUrl, void 0, void 0, void 0, void 0, transport);
393
+ try {
394
+ return await client.callTool("get_adcp_capabilities", {});
395
+ } catch (error) {
396
+ if (failure)
397
+ throw new SafeFetchError(
398
+ "capabilities",
399
+ failure.dns_error,
400
+ "Capabilities fetch failed",
401
+ failure.http_status
402
+ );
403
+ throw error;
404
+ }
356
405
  };
357
406
  }
358
407
  function findAuthorizedOperator(brandJson, agentEtld1, _now, _options) {
@@ -14,6 +14,7 @@ export type BrandJsonResolverErrorCode = 'invalid_url' | 'invalid_house' | 'redi
14
14
  */
15
15
  export declare class BrandJsonResolverError extends Error {
16
16
  readonly code: BrandJsonResolverErrorCode;
17
+ readonly recovery: 'terminal' | 'transient';
17
18
  readonly cause?: unknown;
18
19
  /** HTTP status for `fetch_failed` responses, when a response was received. */
19
20
  readonly httpStatus?: number;
@@ -79,6 +80,7 @@ export declare class BrandJsonJwksResolver implements JwksResolver {
79
80
  private resolver?;
80
81
  private onboarding?;
81
82
  private lastOnboardingAttempt;
83
+ private lastOnboardingError?;
82
84
  constructor(brandJsonUrl: string, options: BrandJsonJwksResolverOptions);
83
85
  private createResolver;
84
86
  private confirmedResolver;
@@ -14,6 +14,7 @@ export type BrandJsonResolverErrorCode = 'invalid_url' | 'invalid_house' | 'redi
14
14
  */
15
15
  export declare class BrandJsonResolverError extends Error {
16
16
  readonly code: BrandJsonResolverErrorCode;
17
+ readonly recovery: 'terminal' | 'transient';
17
18
  readonly cause?: unknown;
18
19
  /** HTTP status for `fetch_failed` responses, when a response was received. */
19
20
  readonly httpStatus?: number;
@@ -79,6 +80,7 @@ export declare class BrandJsonJwksResolver implements JwksResolver {
79
80
  private resolver?;
80
81
  private onboarding?;
81
82
  private lastOnboardingAttempt;
83
+ private lastOnboardingError?;
82
84
  constructor(brandJsonUrl: string, options: BrandJsonJwksResolverOptions);
83
85
  private createResolver;
84
86
  private confirmedResolver;
@@ -23,12 +23,14 @@ __export(brand_jwks_exports, {
23
23
  fetchBrandJson: () => fetchBrandJson
24
24
  });
25
25
  module.exports = __toCommonJS(brand_jwks_exports);
26
+ var import_fetch_helpers = require('./agent-resolver/fetch-helpers.js');
26
27
  var import_net = require('../net/index.js');
27
28
  var import_resolved_agent_jwks = require('./agent-resolver/resolved-agent-jwks.js');
28
29
  var import_strict_json = require('./agent-resolver/strict-json.js');
29
30
  var import_select_agent = require('./agent-resolver/select-agent.js');
30
31
  class BrandJsonResolverError extends Error {
31
32
  code;
33
+ recovery;
32
34
  cause;
33
35
  /** HTTP status for `fetch_failed` responses, when a response was received. */
34
36
  httpStatus;
@@ -38,6 +40,7 @@ class BrandJsonResolverError extends Error {
38
40
  this.code = code;
39
41
  this.httpStatus = details.httpStatus;
40
42
  this.cause = details.cause;
43
+ this.recovery = code !== "fetch_failed" || (0, import_fetch_helpers.isPermanentDiscoveryFailure)((0, import_fetch_helpers.classifyDiscoveryFailure)(this)) ? "terminal" : "transient";
41
44
  }
42
45
  }
43
46
  const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
@@ -63,6 +66,7 @@ class BrandJsonJwksResolver {
63
66
  resolver;
64
67
  onboarding;
65
68
  lastOnboardingAttempt = Number.NEGATIVE_INFINITY;
69
+ lastOnboardingError;
66
70
  createResolver(agentUrl, operatorUrl) {
67
71
  const options = this.options;
68
72
  return new import_resolved_agent_jwks.ResolvedAgentJwksResolver(agentUrl, options.protocol ?? "mcp", {
@@ -86,7 +90,7 @@ class BrandJsonJwksResolver {
86
90
  if (this.onboarding) return this.onboarding;
87
91
  const now = this.options.now?.() ?? Date.now() / 1e3;
88
92
  if (now - this.lastOnboardingAttempt < 30)
89
- throw new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
93
+ throw this.lastOnboardingError ?? new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
90
94
  this.lastOnboardingAttempt = now;
91
95
  this.onboarding = (async () => {
92
96
  const record = await fetchBrandJson({
@@ -99,8 +103,12 @@ class BrandJsonJwksResolver {
99
103
  const resolver = this.createResolver(agentUrl, record.finalUrl);
100
104
  await resolver.forceRefresh();
101
105
  this.resolver = resolver;
106
+ this.lastOnboardingError = void 0;
102
107
  return resolver;
103
- })().finally(() => {
108
+ })().catch((error) => {
109
+ this.lastOnboardingError = error;
110
+ throw error;
111
+ }).finally(() => {
104
112
  this.onboarding = void 0;
105
113
  });
106
114
  return this.onboarding;
@@ -1,9 +1,11 @@
1
+ import { classifyDiscoveryFailure, isPermanentDiscoveryFailure } from "./agent-resolver/fetch-helpers.mjs";
1
2
  import { ssrfSafeFetch } from "../net/index.mjs";
2
3
  import { ResolvedAgentJwksResolver } from "./agent-resolver/resolved-agent-jwks.mjs";
3
4
  import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
4
5
  import { canonicalAgentUrl } from "./agent-resolver/select-agent.mjs";
5
6
  class BrandJsonResolverError extends Error {
6
7
  code;
8
+ recovery;
7
9
  cause;
8
10
  /** HTTP status for `fetch_failed` responses, when a response was received. */
9
11
  httpStatus;
@@ -13,6 +15,7 @@ class BrandJsonResolverError extends Error {
13
15
  this.code = code;
14
16
  this.httpStatus = details.httpStatus;
15
17
  this.cause = details.cause;
18
+ this.recovery = code !== "fetch_failed" || isPermanentDiscoveryFailure(classifyDiscoveryFailure(this)) ? "terminal" : "transient";
16
19
  }
17
20
  }
18
21
  const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
@@ -38,6 +41,7 @@ class BrandJsonJwksResolver {
38
41
  resolver;
39
42
  onboarding;
40
43
  lastOnboardingAttempt = Number.NEGATIVE_INFINITY;
44
+ lastOnboardingError;
41
45
  createResolver(agentUrl, operatorUrl) {
42
46
  const options = this.options;
43
47
  return new ResolvedAgentJwksResolver(agentUrl, options.protocol ?? "mcp", {
@@ -61,7 +65,7 @@ class BrandJsonJwksResolver {
61
65
  if (this.onboarding) return this.onboarding;
62
66
  const now = this.options.now?.() ?? Date.now() / 1e3;
63
67
  if (now - this.lastOnboardingAttempt < 30)
64
- throw new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
68
+ throw this.lastOnboardingError ?? new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
65
69
  this.lastOnboardingAttempt = now;
66
70
  this.onboarding = (async () => {
67
71
  const record = await fetchBrandJson({
@@ -74,8 +78,12 @@ class BrandJsonJwksResolver {
74
78
  const resolver = this.createResolver(agentUrl, record.finalUrl);
75
79
  await resolver.forceRefresh();
76
80
  this.resolver = resolver;
81
+ this.lastOnboardingError = void 0;
77
82
  return resolver;
78
- })().finally(() => {
83
+ })().catch((error) => {
84
+ this.lastOnboardingError = error;
85
+ throw error;
86
+ }).finally(() => {
79
87
  this.onboarding = void 0;
80
88
  });
81
89
  return this.onboarding;
@@ -1,12 +1,14 @@
1
1
  import { ADCPError } from '../errors/index.mjs';
2
2
  import type { RequestSigningErrorCode } from '../types/core.generated.mjs';
3
- export { RequestSigningErrorCodeMetadata } from '../types/enums.generated.mjs';
3
+ import { RequestSigningErrorCodeMetadata } from '../types/enums.generated.mjs';
4
+ export { RequestSigningErrorCodeMetadata };
4
5
  /** Canonical rc.6 request-signing transport vocabulary. */
5
6
  export type RequestSignatureErrorCode = RequestSigningErrorCode;
6
7
  export declare class RequestSignatureError extends ADCPError {
7
8
  readonly code: RequestSignatureErrorCode;
8
9
  readonly failedStep: number;
9
- constructor(code: RequestSignatureErrorCode, failedStep: number, message: string, details?: unknown);
10
+ readonly recovery: 'transient' | 'correctable' | 'terminal';
11
+ constructor(code: RequestSignatureErrorCode, failedStep: number, message: string, details?: unknown, recovery?: 'transient' | 'correctable' | 'terminal');
10
12
  }
11
13
  /**
12
14
  * Error codes surfaced by the RFC 9421 webhook-signing verifier. Maps onto
@@ -1,12 +1,14 @@
1
1
  import { ADCPError } from '../errors';
2
2
  import type { RequestSigningErrorCode } from '../types/core.generated';
3
- export { RequestSigningErrorCodeMetadata } from '../types/enums.generated';
3
+ import { RequestSigningErrorCodeMetadata } from '../types/enums.generated';
4
+ export { RequestSigningErrorCodeMetadata };
4
5
  /** Canonical rc.6 request-signing transport vocabulary. */
5
6
  export type RequestSignatureErrorCode = RequestSigningErrorCode;
6
7
  export declare class RequestSignatureError extends ADCPError {
7
8
  readonly code: RequestSignatureErrorCode;
8
9
  readonly failedStep: number;
9
- constructor(code: RequestSignatureErrorCode, failedStep: number, message: string, details?: unknown);
10
+ readonly recovery: 'transient' | 'correctable' | 'terminal';
11
+ constructor(code: RequestSignatureErrorCode, failedStep: number, message: string, details?: unknown, recovery?: 'transient' | 'correctable' | 'terminal');
10
12
  }
11
13
  /**
12
14
  * Error codes surfaced by the RFC 9421 webhook-signing verifier. Maps onto
@@ -30,10 +30,12 @@ var import_enums = require('../types/enums.generated.js');
30
30
  class RequestSignatureError extends import_errors.ADCPError {
31
31
  code;
32
32
  failedStep;
33
- constructor(code, failedStep, message, details) {
33
+ recovery;
34
+ constructor(code, failedStep, message, details, recovery) {
34
35
  super(message, details);
35
36
  this.code = code;
36
37
  this.failedStep = failedStep;
38
+ this.recovery = recovery ?? import_enums.RequestSigningErrorCodeMetadata[code].recovery;
37
39
  }
38
40
  }
39
41
  class WebhookSignatureError extends import_errors.ADCPError {
@@ -3,10 +3,12 @@ import { RequestSigningErrorCodeMetadata } from "../types/enums.generated.mjs";
3
3
  class RequestSignatureError extends ADCPError {
4
4
  code;
5
5
  failedStep;
6
- constructor(code, failedStep, message, details) {
6
+ recovery;
7
+ constructor(code, failedStep, message, details, recovery) {
7
8
  super(message, details);
8
9
  this.code = code;
9
10
  this.failedStep = failedStep;
11
+ this.recovery = recovery ?? RequestSigningErrorCodeMetadata[code].recovery;
10
12
  }
11
13
  }
12
14
  class WebhookSignatureError extends ADCPError {
@@ -141,7 +141,7 @@ async function verifyRequestSignature(request, options) {
141
141
  }
142
142
  } catch (err) {
143
143
  if (err instanceof import_errors2.AgentResolverError) {
144
- throw new import_errors.RequestSignatureError(err.code, 7, err.message, err.detail);
144
+ throw new import_errors.RequestSignatureError(err.code, 7, err.message, err.detail, err.recovery);
145
145
  }
146
146
  throw err;
147
147
  }
@@ -134,7 +134,7 @@ async function verifyRequestSignature(request, options) {
134
134
  }
135
135
  } catch (err) {
136
136
  if (err instanceof AgentResolverError) {
137
- throw new RequestSignatureError(err.code, 7, err.message, err.detail);
137
+ throw new RequestSignatureError(err.code, 7, err.message, err.detail, err.recovery);
138
138
  }
139
139
  throw err;
140
140
  }
@@ -37,8 +37,8 @@ var import_replay = require('./replay.js');
37
37
  var import_revocation = require('./revocation.js');
38
38
  var import_types = require('./types.js');
39
39
  function keyDiscoveryRetryable(error) {
40
- if (error instanceof import_errors2.AgentResolverError) return import_errors.RequestSigningErrorCodeMetadata[error.code].recovery === "transient";
41
- if (error instanceof import_brand_jwks.BrandJsonResolverError) return error.code === "fetch_failed";
40
+ if (error instanceof import_errors2.AgentResolverError) return error.recovery === "transient";
41
+ if (error instanceof import_brand_jwks.BrandJsonResolverError) return error.recovery === "transient";
42
42
  return true;
43
43
  }
44
44
  const WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
@@ -1,6 +1,6 @@
1
1
  import { buildSignatureBase, canonicalTargetUri, getHeaderValue } from "./canonicalize.mjs";
2
2
  import { contentDigestMatches } from "./content-digest.mjs";
3
- import { RequestSignatureError, WebhookSignatureError, RequestSigningErrorCodeMetadata } from "./errors.mjs";
3
+ import { RequestSignatureError, WebhookSignatureError } from "./errors.mjs";
4
4
  import { AgentResolverError } from "./agent-resolver/errors.mjs";
5
5
  import { BrandJsonResolverError } from "./brand-jwks.mjs";
6
6
  import { parseSignature, parseSignatureInput } from "./parser.mjs";
@@ -10,8 +10,8 @@ import { InMemoryReplayStore } from "./replay.mjs";
10
10
  import { InMemoryRevocationStore } from "./revocation.mjs";
11
11
  import { ALLOWED_ALGS, CLOCK_SKEW_TOLERANCE_SECONDS, MAX_SIGNATURE_WINDOW_SECONDS } from "./types.mjs";
12
12
  function keyDiscoveryRetryable(error) {
13
- if (error instanceof AgentResolverError) return RequestSigningErrorCodeMetadata[error.code].recovery === "transient";
14
- if (error instanceof BrandJsonResolverError) return error.code === "fetch_failed";
13
+ if (error instanceof AgentResolverError) return error.recovery === "transient";
14
+ if (error instanceof BrandJsonResolverError) return error.recovery === "transient";
15
15
  return true;
16
16
  }
17
17
  const WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "14.1.0";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "14.2.0";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "14.1.0";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "14.2.0";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -145,6 +145,11 @@ export type ActivationKey = {
145
145
  */
146
146
  export type AdvertiserIndustry = 'automotive' | 'automotive.electric_vehicles' | 'automotive.parts_accessories' | 'automotive.luxury' | 'beauty_cosmetics' | 'beauty_cosmetics.skincare' | 'beauty_cosmetics.fragrance' | 'beauty_cosmetics.haircare' | 'cannabis' | 'cpg' | 'cpg.personal_care' | 'cpg.household' | 'dating' | 'education' | 'education.higher_education' | 'education.online_learning' | 'education.k12' | 'energy_utilities' | 'energy_utilities.renewable' | 'fashion_apparel' | 'fashion_apparel.luxury' | 'fashion_apparel.sportswear' | 'finance' | 'finance.banking' | 'finance.insurance' | 'finance.investment' | 'finance.cryptocurrency' | 'food_beverage' | 'food_beverage.alcohol' | 'food_beverage.restaurants' | 'food_beverage.packaged_goods' | 'gambling_betting' | 'gambling_betting.sports_betting' | 'gambling_betting.casino' | 'gaming' | 'gaming.mobile' | 'gaming.console_pc' | 'gaming.esports' | 'government_nonprofit' | 'government_nonprofit.political' | 'government_nonprofit.charity' | 'healthcare' | 'healthcare.pharmaceutical' | 'healthcare.medical_devices' | 'healthcare.wellness' | 'home_garden' | 'home_garden.furniture' | 'home_garden.home_improvement' | 'media_entertainment' | 'media_entertainment.podcasts' | 'media_entertainment.music' | 'media_entertainment.film_tv' | 'media_entertainment.publishing' | 'media_entertainment.live_events' | 'pets' | 'professional_services' | 'professional_services.legal' | 'professional_services.consulting' | 'real_estate' | 'real_estate.residential' | 'real_estate.commercial' | 'recruitment_hr' | 'retail' | 'retail.ecommerce' | 'retail.department_stores' | 'sports_fitness' | 'sports_fitness.equipment' | 'sports_fitness.teams_leagues' | 'technology' | 'technology.software' | 'technology.hardware' | 'technology.ai_ml' | 'telecom' | 'telecom.mobile_carriers' | 'telecom.internet_providers' | 'transportation_logistics' | 'travel_hospitality' | 'travel_hospitality.airlines' | 'travel_hospitality.hotels' | 'travel_hospitality.cruise' | 'travel_hospitality.tourism';
147
147
 
148
+ /**
149
+ * User-level bases that may resolve age for demographic targeting execution. Population estimates are deliberately absent because aggregate composition cannot establish an individual impression's age.
150
+ */
151
+ export type AgeDeterminationBasis = 'verified' | 'declared' | 'inferred';
152
+
148
153
  /**
149
154
  * Methods for verifying user age for compliance. Does not include 'inferred' as it is not accepted for regulatory compliance.
150
155
  */
@@ -1083,7 +1088,20 @@ export type DemographicSystem = 'nielsen' | 'nielsen_audio' | 'barb' | 'agf' | '
1083
1088
  * Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.
1084
1089
  */
1085
1090
  export interface DemographicTargetingIntent {
1086
- age: DemographicAgeRange & {};
1091
+ age: DemographicAgeRange & {
1092
+ /**
1093
+ * User-level age determination bases the buyer permits for this predicate. Sellers MUST use only these bases or reject the request; they MUST NOT silently fall back to another basis. When omitted, the buyer imposes no determination-basis constraint beyond product capability and any age_restriction. population_estimate is structurally unavailable because aggregate evidence cannot identify an eligible user.
1094
+ *
1095
+ * @minItems 1
1096
+ */
1097
+ accepted_bases?: [AgeDeterminationBasis, ...AgeDeterminationBasis[]];
1098
+ /**
1099
+ * Verification methods acceptable when verified is an accepted basis. This field narrows verified demographic execution; age_restriction.accepted_methods independently governs legal eligibility and the two sets are intersected when both are present.
1100
+ *
1101
+ * @minItems 1
1102
+ */
1103
+ accepted_verification_methods?: [AgeVerificationMethod, ...AgeVerificationMethod[]];
1104
+ };
1087
1105
  }
1088
1106
 
1089
1107
  /**
@@ -145,6 +145,11 @@ export type ActivationKey = {
145
145
  */
146
146
  export type AdvertiserIndustry = 'automotive' | 'automotive.electric_vehicles' | 'automotive.parts_accessories' | 'automotive.luxury' | 'beauty_cosmetics' | 'beauty_cosmetics.skincare' | 'beauty_cosmetics.fragrance' | 'beauty_cosmetics.haircare' | 'cannabis' | 'cpg' | 'cpg.personal_care' | 'cpg.household' | 'dating' | 'education' | 'education.higher_education' | 'education.online_learning' | 'education.k12' | 'energy_utilities' | 'energy_utilities.renewable' | 'fashion_apparel' | 'fashion_apparel.luxury' | 'fashion_apparel.sportswear' | 'finance' | 'finance.banking' | 'finance.insurance' | 'finance.investment' | 'finance.cryptocurrency' | 'food_beverage' | 'food_beverage.alcohol' | 'food_beverage.restaurants' | 'food_beverage.packaged_goods' | 'gambling_betting' | 'gambling_betting.sports_betting' | 'gambling_betting.casino' | 'gaming' | 'gaming.mobile' | 'gaming.console_pc' | 'gaming.esports' | 'government_nonprofit' | 'government_nonprofit.political' | 'government_nonprofit.charity' | 'healthcare' | 'healthcare.pharmaceutical' | 'healthcare.medical_devices' | 'healthcare.wellness' | 'home_garden' | 'home_garden.furniture' | 'home_garden.home_improvement' | 'media_entertainment' | 'media_entertainment.podcasts' | 'media_entertainment.music' | 'media_entertainment.film_tv' | 'media_entertainment.publishing' | 'media_entertainment.live_events' | 'pets' | 'professional_services' | 'professional_services.legal' | 'professional_services.consulting' | 'real_estate' | 'real_estate.residential' | 'real_estate.commercial' | 'recruitment_hr' | 'retail' | 'retail.ecommerce' | 'retail.department_stores' | 'sports_fitness' | 'sports_fitness.equipment' | 'sports_fitness.teams_leagues' | 'technology' | 'technology.software' | 'technology.hardware' | 'technology.ai_ml' | 'telecom' | 'telecom.mobile_carriers' | 'telecom.internet_providers' | 'transportation_logistics' | 'travel_hospitality' | 'travel_hospitality.airlines' | 'travel_hospitality.hotels' | 'travel_hospitality.cruise' | 'travel_hospitality.tourism';
147
147
 
148
+ /**
149
+ * User-level bases that may resolve age for demographic targeting execution. Population estimates are deliberately absent because aggregate composition cannot establish an individual impression's age.
150
+ */
151
+ export type AgeDeterminationBasis = 'verified' | 'declared' | 'inferred';
152
+
148
153
  /**
149
154
  * Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.
150
155
  */
@@ -1152,7 +1157,20 @@ export type DemographicSystem = 'nielsen' | 'nielsen_audio' | 'barb' | 'agf' | '
1152
1157
  * Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.
1153
1158
  */
1154
1159
  export interface DemographicTargetingIntent {
1155
- age: DemographicAgeRange & {};
1160
+ age: DemographicAgeRange & {
1161
+ /**
1162
+ * User-level age determination bases the buyer permits for this predicate. Sellers MUST use only these bases or reject the request; they MUST NOT silently fall back to another basis. When omitted, the buyer imposes no determination-basis constraint beyond product capability and any age_restriction. population_estimate is structurally unavailable because aggregate evidence cannot identify an eligible user.
1163
+ *
1164
+ * @minItems 1
1165
+ */
1166
+ accepted_bases?: [AgeDeterminationBasis, ...AgeDeterminationBasis[]];
1167
+ /**
1168
+ * Verification methods acceptable when verified is an accepted basis. This field narrows verified demographic execution; age_restriction.accepted_methods independently governs legal eligibility and the two sets are intersected when both are present.
1169
+ *
1170
+ * @minItems 1
1171
+ */
1172
+ accepted_verification_methods?: [AgeVerificationMethod, ...AgeVerificationMethod[]];
1173
+ };
1156
1174
  }
1157
1175
 
1158
1176
  /**
@@ -445,6 +445,11 @@ export type ActivationKey = {
445
445
  */
446
446
  export type AdvertiserIndustry = 'automotive' | 'automotive.electric_vehicles' | 'automotive.parts_accessories' | 'automotive.luxury' | 'beauty_cosmetics' | 'beauty_cosmetics.skincare' | 'beauty_cosmetics.fragrance' | 'beauty_cosmetics.haircare' | 'cannabis' | 'cpg' | 'cpg.personal_care' | 'cpg.household' | 'dating' | 'education' | 'education.higher_education' | 'education.online_learning' | 'education.k12' | 'energy_utilities' | 'energy_utilities.renewable' | 'fashion_apparel' | 'fashion_apparel.luxury' | 'fashion_apparel.sportswear' | 'finance' | 'finance.banking' | 'finance.insurance' | 'finance.investment' | 'finance.cryptocurrency' | 'food_beverage' | 'food_beverage.alcohol' | 'food_beverage.restaurants' | 'food_beverage.packaged_goods' | 'gambling_betting' | 'gambling_betting.sports_betting' | 'gambling_betting.casino' | 'gaming' | 'gaming.mobile' | 'gaming.console_pc' | 'gaming.esports' | 'government_nonprofit' | 'government_nonprofit.political' | 'government_nonprofit.charity' | 'healthcare' | 'healthcare.pharmaceutical' | 'healthcare.medical_devices' | 'healthcare.wellness' | 'home_garden' | 'home_garden.furniture' | 'home_garden.home_improvement' | 'media_entertainment' | 'media_entertainment.podcasts' | 'media_entertainment.music' | 'media_entertainment.film_tv' | 'media_entertainment.publishing' | 'media_entertainment.live_events' | 'pets' | 'professional_services' | 'professional_services.legal' | 'professional_services.consulting' | 'real_estate' | 'real_estate.residential' | 'real_estate.commercial' | 'recruitment_hr' | 'retail' | 'retail.ecommerce' | 'retail.department_stores' | 'sports_fitness' | 'sports_fitness.equipment' | 'sports_fitness.teams_leagues' | 'technology' | 'technology.software' | 'technology.hardware' | 'technology.ai_ml' | 'telecom' | 'telecom.mobile_carriers' | 'telecom.internet_providers' | 'transportation_logistics' | 'travel_hospitality' | 'travel_hospitality.airlines' | 'travel_hospitality.hotels' | 'travel_hospitality.cruise' | 'travel_hospitality.tourism';
447
447
 
448
+ /**
449
+ * User-level bases that may resolve age for demographic targeting execution. Population estimates are deliberately absent because aggregate composition cannot establish an individual impression's age.
450
+ */
451
+ export type AgeDeterminationBasis = 'verified' | 'declared' | 'inferred';
452
+
448
453
  /**
449
454
  * Methods for verifying user age for compliance. Does not include 'inferred' as it is not accepted for regulatory compliance.
450
455
  */
@@ -1585,7 +1590,20 @@ export type DemographicSystem = 'nielsen' | 'nielsen_audio' | 'barb' | 'agf' | '
1585
1590
  * Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.
1586
1591
  */
1587
1592
  export interface DemographicTargetingIntent {
1588
- age: DemographicAgeRange & {};
1593
+ age: DemographicAgeRange & {
1594
+ /**
1595
+ * User-level age determination bases the buyer permits for this predicate. Sellers MUST use only these bases or reject the request; they MUST NOT silently fall back to another basis. When omitted, the buyer imposes no determination-basis constraint beyond product capability and any age_restriction. population_estimate is structurally unavailable because aggregate evidence cannot identify an eligible user.
1596
+ *
1597
+ * @minItems 1
1598
+ */
1599
+ accepted_bases?: [AgeDeterminationBasis, ...AgeDeterminationBasis[]];
1600
+ /**
1601
+ * Verification methods acceptable when verified is an accepted basis. This field narrows verified demographic execution; age_restriction.accepted_methods independently governs legal eligibility and the two sets are intersected when both are present.
1602
+ *
1603
+ * @minItems 1
1604
+ */
1605
+ accepted_verification_methods?: [AgeVerificationMethod, ...AgeVerificationMethod[]];
1606
+ };
1589
1607
  }
1590
1608
 
1591
1609
  /**
@@ -486,6 +486,11 @@ export interface AdInventoryConfiguration {
486
486
  supported_formats?: string[];
487
487
  }
488
488
 
489
+ /**
490
+ * User-level bases that may resolve age for demographic targeting execution. Population estimates are deliberately absent because aggregate composition cannot establish an individual impression's age.
491
+ */
492
+ export type AgeDeterminationBasis = 'verified' | 'declared' | 'inferred';
493
+
489
494
  /**
490
495
  * Methods for verifying user age for compliance. Does not include 'inferred' as it is not accepted for regulatory compliance.
491
496
  */
@@ -5526,7 +5531,20 @@ export interface DemographicTargetingCapability {
5526
5531
  * Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.
5527
5532
  */
5528
5533
  export interface DemographicTargetingIntent {
5529
- age: DemographicAgeRange & {};
5534
+ age: DemographicAgeRange & {
5535
+ /**
5536
+ * User-level age determination bases the buyer permits for this predicate. Sellers MUST use only these bases or reject the request; they MUST NOT silently fall back to another basis. When omitted, the buyer imposes no determination-basis constraint beyond product capability and any age_restriction. population_estimate is structurally unavailable because aggregate evidence cannot identify an eligible user.
5537
+ *
5538
+ * @minItems 1
5539
+ */
5540
+ accepted_bases?: [AgeDeterminationBasis, ...AgeDeterminationBasis[]];
5541
+ /**
5542
+ * Verification methods acceptable when verified is an accepted basis. This field narrows verified demographic execution; age_restriction.accepted_methods independently governs legal eligibility and the two sets are intersected when both are present.
5543
+ *
5544
+ * @minItems 1
5545
+ */
5546
+ accepted_verification_methods?: [AgeVerificationMethod, ...AgeVerificationMethod[]];
5547
+ };
5530
5548
  }
5531
5549
 
5532
5550
  /**