@adcp/sdk 14.1.0 → 14.3.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 (139) 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/reporting/inspection.d.mts +3 -3
  22. package/dist/lib/reporting/inspection.d.ts +3 -3
  23. package/dist/lib/reporting/inspection.js +229 -84
  24. package/dist/lib/reporting/inspection.mjs +229 -84
  25. package/dist/lib/reporting/ledger/status-ingest.js +3 -19
  26. package/dist/lib/reporting/ledger/status-ingest.mjs +2 -18
  27. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  28. package/dist/lib/server/create-adcp-server.js +5 -2
  29. package/dist/lib/server/create-adcp-server.mjs +5 -2
  30. package/dist/lib/signing/agent-resolver/errors.d.mts +2 -0
  31. package/dist/lib/signing/agent-resolver/errors.d.ts +2 -0
  32. package/dist/lib/signing/agent-resolver/errors.js +6 -0
  33. package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
  34. package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +9 -0
  35. package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +9 -0
  36. package/dist/lib/signing/agent-resolver/fetch-helpers.js +35 -1
  37. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +38 -2
  38. package/dist/lib/signing/agent-resolver/legacy-brand.js +1 -1
  39. package/dist/lib/signing/agent-resolver/legacy-brand.mjs +2 -2
  40. package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +1 -1
  41. package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +1 -1
  42. package/dist/lib/signing/agent-resolver/resolve-agent.js +47 -5
  43. package/dist/lib/signing/agent-resolver/resolve-agent.mjs +55 -6
  44. package/dist/lib/signing/brand-jwks.d.mts +2 -0
  45. package/dist/lib/signing/brand-jwks.d.ts +2 -0
  46. package/dist/lib/signing/brand-jwks.js +10 -2
  47. package/dist/lib/signing/brand-jwks.mjs +10 -2
  48. package/dist/lib/signing/canonicalize.d.mts +2 -0
  49. package/dist/lib/signing/canonicalize.d.ts +2 -0
  50. package/dist/lib/signing/canonicalize.js +1 -1
  51. package/dist/lib/signing/canonicalize.mjs +1 -1
  52. package/dist/lib/signing/errors.d.mts +4 -2
  53. package/dist/lib/signing/errors.d.ts +4 -2
  54. package/dist/lib/signing/errors.js +3 -1
  55. package/dist/lib/signing/errors.mjs +3 -1
  56. package/dist/lib/signing/verifier.js +2 -2
  57. package/dist/lib/signing/verifier.mjs +2 -2
  58. package/dist/lib/signing/webhook-verifier.js +2 -2
  59. package/dist/lib/signing/webhook-verifier.mjs +3 -3
  60. package/dist/lib/testing/storyboard/request-signing/builder.js +13 -1
  61. package/dist/lib/testing/storyboard/request-signing/builder.mjs +13 -1
  62. package/dist/lib/testing/storyboard/request-signing/grader.js +1 -0
  63. package/dist/lib/testing/storyboard/request-signing/grader.mjs +1 -0
  64. package/dist/lib/testing/storyboard/request-signing/vector-loader.js +1 -1
  65. package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +1 -1
  66. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  67. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  68. package/dist/lib/types/accept-proposal.d.ts +19 -1
  69. package/dist/lib/types/buy-products.d.ts +19 -1
  70. package/dist/lib/types/check-governance.d.ts +19 -1
  71. package/dist/lib/types/comply-test-controller.d.ts +19 -1
  72. package/dist/lib/types/control-media-buy.d.ts +19 -1
  73. package/dist/lib/types/core.generated.d.mts +14 -1
  74. package/dist/lib/types/core.generated.d.ts +14 -1
  75. package/dist/lib/types/create-media-buy.d.ts +14 -1
  76. package/dist/lib/types/get-media-buys.d.ts +14 -1
  77. package/dist/lib/types/get-products.d.ts +19 -1
  78. package/dist/lib/types/list-products.d.ts +19 -1
  79. package/dist/lib/types/refine-proposals.d.ts +19 -1
  80. package/dist/lib/types/request-proposals.d.ts +19 -1
  81. package/dist/lib/types/schemas.generated.d.ts +12 -3
  82. package/dist/lib/types/schemas.generated.js +4 -1
  83. package/dist/lib/types/schemas.generated.mjs +4 -1
  84. package/dist/lib/types/tools.generated.d.mts +14 -1
  85. package/dist/lib/types/tools.generated.d.ts +14 -1
  86. package/dist/lib/types/update-media-buy.d.ts +14 -1
  87. package/dist/lib/utils/pointer-utils.d.mts +2 -0
  88. package/dist/lib/utils/pointer-utils.d.ts +2 -0
  89. package/dist/lib/utils/pointer-utils.js +43 -0
  90. package/dist/lib/utils/pointer-utils.mjs +19 -0
  91. package/dist/lib/validation/diagnostic-limits.d.mts +4 -0
  92. package/dist/lib/validation/diagnostic-limits.d.ts +4 -0
  93. package/dist/lib/validation/diagnostic-limits.js +57 -0
  94. package/dist/lib/validation/diagnostic-limits.mjs +32 -0
  95. package/dist/lib/validation/schema-errors.d.mts +3 -1
  96. package/dist/lib/validation/schema-errors.d.ts +3 -1
  97. package/dist/lib/validation/schema-errors.js +18 -3
  98. package/dist/lib/validation/schema-errors.mjs +18 -3
  99. package/dist/lib/validation/schema-validator.js +56 -4
  100. package/dist/lib/validation/schema-validator.mjs +56 -4
  101. package/dist/lib/version.d.mts +3 -3
  102. package/dist/lib/version.d.ts +3 -3
  103. package/dist/lib/version.js +3 -3
  104. package/dist/lib/version.mjs +3 -3
  105. package/dist/lib/webhooks/index.d.mts +24 -0
  106. package/dist/lib/webhooks/index.d.ts +24 -0
  107. package/dist/lib/webhooks/index.js +50 -24
  108. package/dist/lib/webhooks/index.mjs +49 -24
  109. package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
  110. package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
  111. package/dist/lib/wholesale-feed-sync/index.js +7 -0
  112. package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
  113. package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
  114. package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
  115. package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
  116. package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
  117. package/dist/lib/wholesale-feed-sync/sync.d.mts +12 -29
  118. package/dist/lib/wholesale-feed-sync/sync.d.ts +12 -29
  119. package/dist/lib/wholesale-feed-sync/sync.js +153 -297
  120. package/dist/lib/wholesale-feed-sync/sync.mjs +158 -297
  121. package/docs/README.md +6 -0
  122. package/docs/TYPE-SUMMARY.md +2 -2
  123. package/docs/guides/BUYER-STORAGE.md +3 -0
  124. package/docs/guides/FIRST-CALL-TO-A-SELLER.md +3 -1
  125. package/docs/guides/account-resolution.md +46 -10
  126. package/docs/llms.txt +2 -2
  127. package/docs/migration-14.0-to-14.1.md +85 -0
  128. package/docs/migration-14.x-rc-worksheet.md +4 -4
  129. package/docs/migration-agent-resolution-3.3.md +5 -3
  130. package/docs/recipes/verifying-inbound-webhooks.md +4 -0
  131. package/package.json +3 -2
  132. package/skills/call-adcp-agent/SKILL.md +4 -1
  133. package/skills/adcp-brand.previous/SKILL.md +0 -200
  134. package/skills/adcp-creative.previous/SKILL.md +0 -305
  135. package/skills/adcp-governance.previous/SKILL.md +0 -566
  136. package/skills/adcp-measurement.previous/SKILL.md +0 -136
  137. package/skills/adcp-media-buy.previous/SKILL.md +0 -556
  138. package/skills/adcp-si.previous/SKILL.md +0 -206
  139. package/skills/adcp-signals.previous/SKILL.md +0 -204
@@ -65,6 +65,7 @@ var import_envelope_status_compat = require('../utils/envelope-status-compat.js'
65
65
  var import_schema_validator = require('../validation/schema-validator.js');
66
66
  var import_sync_reporting_status_envelope = require('../validation/sync-reporting-status-envelope.js');
67
67
  var import_schema_errors = require('../validation/schema-errors.js');
68
+ var import_diagnostic_limits = require('../validation/diagnostic-limits.js');
68
69
  var import_idempotency2 = require('./idempotency/index.js');
69
70
  var import_webhook_emitter = require('./webhook-emitter.js');
70
71
  var import_middleware = require('../signing/middleware.js');
@@ -2287,7 +2288,7 @@ function createAdcpServer(config) {
2287
2288
  const validationMode = effectiveRequestValidationMode(extra);
2288
2289
  const accountIssues = protocolTaskAccountExtensionIssues(params);
2289
2290
  const outcome = validationMode === "off" ? { valid: true, issues: [], schemaId: void 0 } : (0, import_schema_validator.validateRequest)(toolName, params, requestServedRelease(params)?.validationVersion ?? adcpVersion);
2290
- const issues = [...outcome.valid ? [] : outcome.issues, ...accountIssues];
2291
+ const issues = [...accountIssues, ...outcome.valid ? [] : outcome.issues];
2291
2292
  if (issues.length === 0) return void 0;
2292
2293
  if (validationMode === "strict" || accountIssues.length > 0) {
2293
2294
  return (0, import_errors2.adcpError)(
@@ -2838,7 +2839,9 @@ function createAdcpServer(config) {
2838
2839
  })
2839
2840
  );
2840
2841
  }
2841
- let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter((i) => !(i.keyword === "required" && i.pointer === "/idempotency_key")) : outcome.issues;
2842
+ let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter(
2843
+ (i) => !(i.keyword === "required" && i.pointer === "/idempotency_key" && !(0, import_diagnostic_limits.validationDiagnosticsTruncated)([i]))
2844
+ ) : outcome.issues;
2842
2845
  if (toolName === "sync_accounts" && effectiveFrameworkRequestValidationMode === "strict") {
2843
2846
  const eventIssues = issues.filter(isAccountNotificationEventIssue);
2844
2847
  if (eventIssues.length > 0 && eventIssues.length === issues.length) {
@@ -98,6 +98,7 @@ import { STATUS_FREE_SYNC_RESPONSE_TOOLS } from "../utils/envelope-status-compat
98
98
  import { validateRequest, validateResponse, formatIssues } from "../validation/schema-validator.mjs";
99
99
  import { validateSyncReportingStatusEnvelope } from "../validation/sync-reporting-status-envelope.mjs";
100
100
  import { buildAdcpValidationErrorPayload } from "../validation/schema-errors.mjs";
101
+ import { validationDiagnosticsTruncated } from "../validation/diagnostic-limits.mjs";
101
102
  import { hashPayload, IdempotencyClaimOwnershipError } from "./idempotency/index.mjs";
102
103
  import {
103
104
  createWebhookEmitter
@@ -2359,7 +2360,7 @@ function createAdcpServer(config) {
2359
2360
  const validationMode = effectiveRequestValidationMode(extra);
2360
2361
  const accountIssues = protocolTaskAccountExtensionIssues(params);
2361
2362
  const outcome = validationMode === "off" ? { valid: true, issues: [], schemaId: void 0 } : validateRequest(toolName, params, requestServedRelease(params)?.validationVersion ?? adcpVersion);
2362
- const issues = [...outcome.valid ? [] : outcome.issues, ...accountIssues];
2363
+ const issues = [...accountIssues, ...outcome.valid ? [] : outcome.issues];
2363
2364
  if (issues.length === 0) return void 0;
2364
2365
  if (validationMode === "strict" || accountIssues.length > 0) {
2365
2366
  return adcpError(
@@ -2910,7 +2911,9 @@ function createAdcpServer(config) {
2910
2911
  })
2911
2912
  );
2912
2913
  }
2913
- let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter((i) => !(i.keyword === "required" && i.pointer === "/idempotency_key")) : outcome.issues;
2914
+ let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter(
2915
+ (i) => !(i.keyword === "required" && i.pointer === "/idempotency_key" && !validationDiagnosticsTruncated([i]))
2916
+ ) : outcome.issues;
2914
2917
  if (toolName === "sync_accounts" && effectiveFrameworkRequestValidationMode === "strict") {
2915
2918
  const eventIssues = issues.filter(isAccountNotificationEventIssue);
2916
2919
  if (eventIssues.length > 0 && eventIssues.length === issues.length) {
@@ -46,6 +46,8 @@ export interface AgentResolverErrorDetail {
46
46
  export declare class AgentResolverError extends Error {
47
47
  readonly code: AgentResolverErrorCode;
48
48
  readonly detail: AgentResolverErrorDetail;
49
+ /** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
50
+ readonly recovery: 'transient' | 'correctable' | 'terminal';
49
51
  readonly [ATTACKER_INFLUENCED]: ReadonlyArray<keyof AgentResolverErrorDetail>;
50
52
  constructor(code: AgentResolverErrorCode, message: string, detail?: AgentResolverErrorDetail, attackerInfluencedFields?: ReadonlyArray<keyof AgentResolverErrorDetail>);
51
53
  }
@@ -46,6 +46,8 @@ export interface AgentResolverErrorDetail {
46
46
  export declare class AgentResolverError extends Error {
47
47
  readonly code: AgentResolverErrorCode;
48
48
  readonly detail: AgentResolverErrorDetail;
49
+ /** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
50
+ readonly recovery: 'transient' | 'correctable' | 'terminal';
49
51
  readonly [ATTACKER_INFLUENCED]: ReadonlyArray<keyof AgentResolverErrorDetail>;
50
52
  constructor(code: AgentResolverErrorCode, message: string, detail?: AgentResolverErrorDetail, attackerInfluencedFields?: ReadonlyArray<keyof AgentResolverErrorDetail>);
51
53
  }
@@ -23,16 +23,22 @@ __export(errors_exports, {
23
23
  attackerInfluencedFields: () => attackerInfluencedFields
24
24
  });
25
25
  module.exports = __toCommonJS(errors_exports);
26
+ var import_fetch_helpers = require('./fetch-helpers.js');
27
+ var import_enums = require('../../types/enums.generated.js');
26
28
  const ATTACKER_INFLUENCED = /* @__PURE__ */ Symbol.for("adcp.attacker-influenced");
27
29
  class AgentResolverError extends Error {
28
30
  code;
29
31
  detail;
32
+ /** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
33
+ recovery;
30
34
  [ATTACKER_INFLUENCED];
31
35
  constructor(code, message, detail = {}, attackerInfluencedFields2 = []) {
32
36
  super(message);
33
37
  this.name = "AgentResolverError";
34
38
  this.code = code;
35
39
  this.detail = detail;
40
+ const discovery = code === "request_signature_capabilities_unreachable" || code === "request_signature_brand_json_unreachable";
41
+ this.recovery = discovery && (0, import_fetch_helpers.isPermanentDiscoveryFailure)(detail) ? "terminal" : import_enums.RequestSigningErrorCodeMetadata[code].recovery;
36
42
  this[ATTACKER_INFLUENCED] = attackerInfluencedFields2;
37
43
  }
38
44
  }
@@ -1,13 +1,19 @@
1
+ import { isPermanentDiscoveryFailure } from "./fetch-helpers.mjs";
2
+ import { RequestSigningErrorCodeMetadata } from "../../types/enums.generated.mjs";
1
3
  const ATTACKER_INFLUENCED = /* @__PURE__ */ Symbol.for("adcp.attacker-influenced");
2
4
  class AgentResolverError extends Error {
3
5
  code;
4
6
  detail;
7
+ /** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
8
+ recovery;
5
9
  [ATTACKER_INFLUENCED];
6
10
  constructor(code, message, detail = {}, attackerInfluencedFields2 = []) {
7
11
  super(message);
8
12
  this.name = "AgentResolverError";
9
13
  this.code = code;
10
14
  this.detail = detail;
15
+ const discovery = code === "request_signature_capabilities_unreachable" || code === "request_signature_brand_json_unreachable";
16
+ this.recovery = discovery && isPermanentDiscoveryFailure(detail) ? "terminal" : RequestSigningErrorCodeMetadata[code].recovery;
11
17
  this[ATTACKER_INFLUENCED] = attackerInfluencedFields2;
12
18
  }
13
19
  }
@@ -40,3 +40,12 @@ export declare function safeFetchJson(url: string, kind: FetchKind, options: {
40
40
  maxBodyBytes: number;
41
41
  lookup?: SsrfDnsLookup;
42
42
  }): Promise<SafeFetchJsonResult>;
43
+ /** Coarse causes only: never expose messages, resolved addresses, or topology. */
44
+ export declare function classifyDiscoveryFailure(error: unknown): {
45
+ dns_error: SafeFetchError['transport'];
46
+ http_status?: number;
47
+ };
48
+ export declare function isPermanentDiscoveryFailure(detail: {
49
+ dns_error?: string;
50
+ http_status?: number;
51
+ }): boolean;
@@ -40,3 +40,12 @@ export declare function safeFetchJson(url: string, kind: FetchKind, options: {
40
40
  maxBodyBytes: number;
41
41
  lookup?: SsrfDnsLookup;
42
42
  }): Promise<SafeFetchJsonResult>;
43
+ /** Coarse causes only: never expose messages, resolved addresses, or topology. */
44
+ export declare function classifyDiscoveryFailure(error: unknown): {
45
+ dns_error: SafeFetchError['transport'];
46
+ http_status?: number;
47
+ };
48
+ export declare function isPermanentDiscoveryFailure(detail: {
49
+ dns_error?: string;
50
+ http_status?: number;
51
+ }): boolean;
@@ -24,10 +24,13 @@ __export(fetch_helpers_exports, {
24
24
  MAX_CAPABILITIES_BYTES: () => MAX_CAPABILITIES_BYTES,
25
25
  MAX_JWKS_BYTES: () => MAX_JWKS_BYTES,
26
26
  SafeFetchError: () => SafeFetchError,
27
+ classifyDiscoveryFailure: () => classifyDiscoveryFailure,
28
+ isPermanentDiscoveryFailure: () => isPermanentDiscoveryFailure,
27
29
  safeFetchJson: () => safeFetchJson
28
30
  });
29
31
  module.exports = __toCommonJS(fetch_helpers_exports);
30
32
  var import_net = require('../../net/index.js');
33
+ var import_agent_transport_fetch = require('../../net/agent-transport-fetch.js');
31
34
  var import_strict_json = require('./strict-json.js');
32
35
  const MAX_CAPABILITIES_BYTES = 65536;
33
36
  const MAX_BRAND_JSON_BYTES = 262144;
@@ -60,7 +63,11 @@ async function safeFetchJson(url, kind, options) {
60
63
  res = await (0, import_net.ssrfSafeFetch)(url, fetchOpts);
61
64
  } catch (err) {
62
65
  if (err instanceof import_net.SsrfRefusedError) {
63
- throw new SafeFetchError(kind, "ssrf_refused", `${kind} fetch refused: ${err.code}`);
66
+ throw new SafeFetchError(
67
+ kind,
68
+ import_net.SSRF_TRANSIENT_CODES.has(err.code) ? err.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused",
69
+ `${kind} fetch refused: ${err.code}`
70
+ );
64
71
  }
65
72
  const message = err instanceof Error ? err.message : String(err);
66
73
  if (/abort|timed?\s*out/i.test(message)) {
@@ -88,6 +95,31 @@ async function safeFetchJson(url, kind, options) {
88
95
  fetchedAt: Math.floor(Date.now() / 1e3)
89
96
  };
90
97
  }
98
+ function classifyDiscoveryFailure(error) {
99
+ const seen = /* @__PURE__ */ new Set();
100
+ for (let depth = 0; error && typeof error === "object" && depth < 8 && !seen.has(error); depth++) {
101
+ seen.add(error);
102
+ if (error instanceof SafeFetchError)
103
+ return { dns_error: error.transport, ...error.httpStatus !== void 0 && { http_status: error.httpStatus } };
104
+ if (error instanceof import_agent_transport_fetch.AgentTransportPolicyError) return { dns_error: "ssrf_refused" };
105
+ if (error instanceof import_net.SsrfRefusedError)
106
+ return {
107
+ dns_error: import_net.SSRF_TRANSIENT_CODES.has(error.code) ? error.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused"
108
+ };
109
+ const obj = error;
110
+ const status = obj.httpStatus ?? obj.status ?? obj.statusCode;
111
+ if (typeof status === "number" && status >= 400 && status <= 599)
112
+ return { dns_error: "fetch_failed", http_status: status };
113
+ if (obj.code === "ENOTFOUND" || obj.code === "EAI_AGAIN") return { dns_error: "dns_error" };
114
+ if (obj.name === "AbortError" || obj.name === "TimeoutError" || obj.code === "ETIMEDOUT" || typeof obj.code === "string" && obj.code.startsWith("UND_ERR_") && obj.code.endsWith("_TIMEOUT"))
115
+ return { dns_error: "timeout" };
116
+ error = obj.cause;
117
+ }
118
+ return { dns_error: "fetch_failed" };
119
+ }
120
+ function isPermanentDiscoveryFailure(detail) {
121
+ return detail.dns_error === "ssrf_refused" || detail.http_status !== void 0 && detail.http_status >= 400 && detail.http_status < 500;
122
+ }
91
123
  // Annotate the CommonJS export names for ESM import in node:
92
124
  0 && (module.exports = {
93
125
  DEFAULT_CONNECT_TIMEOUT_MS,
@@ -96,5 +128,7 @@ async function safeFetchJson(url, kind, options) {
96
128
  MAX_CAPABILITIES_BYTES,
97
129
  MAX_JWKS_BYTES,
98
130
  SafeFetchError,
131
+ classifyDiscoveryFailure,
132
+ isPermanentDiscoveryFailure,
99
133
  safeFetchJson
100
134
  });
@@ -1,4 +1,9 @@
1
- import { ssrfSafeFetch, SsrfRefusedError } from "../../net/index.mjs";
1
+ import {
2
+ ssrfSafeFetch,
3
+ SsrfRefusedError,
4
+ SSRF_TRANSIENT_CODES
5
+ } from "../../net/index.mjs";
6
+ import { AgentTransportPolicyError } from "../../net/agent-transport-fetch.mjs";
2
7
  import { parseStrictJson, StrictJsonError } from "./strict-json.mjs";
3
8
  const MAX_CAPABILITIES_BYTES = 65536;
4
9
  const MAX_BRAND_JSON_BYTES = 262144;
@@ -31,7 +36,11 @@ async function safeFetchJson(url, kind, options) {
31
36
  res = await ssrfSafeFetch(url, fetchOpts);
32
37
  } catch (err) {
33
38
  if (err instanceof SsrfRefusedError) {
34
- throw new SafeFetchError(kind, "ssrf_refused", `${kind} fetch refused: ${err.code}`);
39
+ throw new SafeFetchError(
40
+ kind,
41
+ SSRF_TRANSIENT_CODES.has(err.code) ? err.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused",
42
+ `${kind} fetch refused: ${err.code}`
43
+ );
35
44
  }
36
45
  const message = err instanceof Error ? err.message : String(err);
37
46
  if (/abort|timed?\s*out/i.test(message)) {
@@ -59,6 +68,31 @@ async function safeFetchJson(url, kind, options) {
59
68
  fetchedAt: Math.floor(Date.now() / 1e3)
60
69
  };
61
70
  }
71
+ function classifyDiscoveryFailure(error) {
72
+ const seen = /* @__PURE__ */ new Set();
73
+ for (let depth = 0; error && typeof error === "object" && depth < 8 && !seen.has(error); depth++) {
74
+ seen.add(error);
75
+ if (error instanceof SafeFetchError)
76
+ return { dns_error: error.transport, ...error.httpStatus !== void 0 && { http_status: error.httpStatus } };
77
+ if (error instanceof AgentTransportPolicyError) return { dns_error: "ssrf_refused" };
78
+ if (error instanceof SsrfRefusedError)
79
+ return {
80
+ dns_error: SSRF_TRANSIENT_CODES.has(error.code) ? error.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused"
81
+ };
82
+ const obj = error;
83
+ const status = obj.httpStatus ?? obj.status ?? obj.statusCode;
84
+ if (typeof status === "number" && status >= 400 && status <= 599)
85
+ return { dns_error: "fetch_failed", http_status: status };
86
+ if (obj.code === "ENOTFOUND" || obj.code === "EAI_AGAIN") return { dns_error: "dns_error" };
87
+ if (obj.name === "AbortError" || obj.name === "TimeoutError" || obj.code === "ETIMEDOUT" || typeof obj.code === "string" && obj.code.startsWith("UND_ERR_") && obj.code.endsWith("_TIMEOUT"))
88
+ return { dns_error: "timeout" };
89
+ error = obj.cause;
90
+ }
91
+ return { dns_error: "fetch_failed" };
92
+ }
93
+ function isPermanentDiscoveryFailure(detail) {
94
+ return detail.dns_error === "ssrf_refused" || detail.http_status !== void 0 && detail.http_status >= 400 && detail.http_status < 500;
95
+ }
62
96
  export {
63
97
  DEFAULT_CONNECT_TIMEOUT_MS,
64
98
  DEFAULT_TOTAL_TIMEOUT_MS,
@@ -66,5 +100,7 @@ export {
66
100
  MAX_CAPABILITIES_BYTES,
67
101
  MAX_JWKS_BYTES,
68
102
  SafeFetchError,
103
+ classifyDiscoveryFailure,
104
+ isPermanentDiscoveryFailure,
69
105
  safeFetchJson
70
106
  };
@@ -42,7 +42,7 @@ async function fetchLegacyBrandJson(agentUrl, options) {
42
42
  } catch (err) {
43
43
  throw new import_fetch_helpers.SafeFetchError(
44
44
  "brand.json",
45
- "fetch_failed",
45
+ (0, import_fetch_helpers.classifyDiscoveryFailure)(err).dns_error,
46
46
  err instanceof import_brand_jwks.BrandJsonResolverError && err.code === "invalid_body" ? "brand.json body failed strict-JSON parse" : "Legacy brand.json discovery failed",
47
47
  err instanceof import_brand_jwks.BrandJsonResolverError ? err.httpStatus : void 0
48
48
  );
@@ -1,6 +1,6 @@
1
1
  import { fetchBrandJson, BrandJsonResolverError } from "../brand-jwks.mjs";
2
2
  import { eTldPlusOne } from "./etld.mjs";
3
- import { SafeFetchError } from "./fetch-helpers.mjs";
3
+ import { SafeFetchError, classifyDiscoveryFailure } from "./fetch-helpers.mjs";
4
4
  async function fetchLegacyBrandJson(agentUrl, options) {
5
5
  const agent = new URL(agentUrl);
6
6
  let startUrl = `${agent.origin}/.well-known/brand.json`;
@@ -19,7 +19,7 @@ async function fetchLegacyBrandJson(agentUrl, options) {
19
19
  } catch (err) {
20
20
  throw new SafeFetchError(
21
21
  "brand.json",
22
- "fetch_failed",
22
+ classifyDiscoveryFailure(err).dns_error,
23
23
  err instanceof BrandJsonResolverError && err.code === "invalid_body" ? "brand.json body failed strict-JSON parse" : "Legacy brand.json discovery failed",
24
24
  err instanceof BrandJsonResolverError ? err.httpStatus : void 0
25
25
  );
@@ -28,7 +28,7 @@
28
28
  * `SsrfRefusedError` carries.
29
29
  */
30
30
  import type { IdentityKeyOriginPurpose, IdentityPosture } from './capabilities-types.mjs';
31
- import type { SsrfDnsLookup } from '../../net/index.mjs';
31
+ import { type SsrfDnsLookup } from '../../net/index.mjs';
32
32
  import { type ConsistencyResult } from './consistency.mjs';
33
33
  import { type AgentResolverErrorDetail } from './errors.mjs';
34
34
  import { type AgentEntry } from './select-agent.mjs';
@@ -28,7 +28,7 @@
28
28
  * `SsrfRefusedError` carries.
29
29
  */
30
30
  import type { IdentityKeyOriginPurpose, IdentityPosture } from './capabilities-types';
31
- import type { SsrfDnsLookup } from '../../net';
31
+ import { type SsrfDnsLookup } from '../../net';
32
32
  import { type ConsistencyResult } from './consistency';
33
33
  import { type AgentResolverErrorDetail } from './errors';
34
34
  import { type AgentEntry } from './select-agent';
@@ -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;