@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.
Files changed (185) hide show
  1. package/dist/lib/adapters/implicit-account-store.d.mts +12 -7
  2. package/dist/lib/adapters/implicit-account-store.d.ts +12 -7
  3. package/dist/lib/adapters/implicit-account-store.js +69 -15
  4. package/dist/lib/adapters/implicit-account-store.mjs +69 -15
  5. package/dist/lib/core/AgentClient.d.mts +1 -0
  6. package/dist/lib/core/AgentClient.d.ts +1 -0
  7. package/dist/lib/core/AgentClient.js +3 -0
  8. package/dist/lib/core/AgentClient.mjs +3 -0
  9. package/dist/lib/core/SingleAgentClient.d.mts +31 -1
  10. package/dist/lib/core/SingleAgentClient.d.ts +31 -1
  11. package/dist/lib/core/SingleAgentClient.js +355 -35
  12. package/dist/lib/core/SingleAgentClient.mjs +365 -37
  13. package/dist/lib/core/TaskExecutor.js +2 -1
  14. package/dist/lib/core/TaskExecutor.mjs +2 -1
  15. package/dist/lib/core/account-key.d.mts +3 -0
  16. package/dist/lib/core/account-key.d.ts +3 -0
  17. package/dist/lib/core/account-key.js +41 -0
  18. package/dist/lib/core/account-key.mjs +17 -0
  19. package/dist/lib/core/account-resolution.d.mts +2 -0
  20. package/dist/lib/core/account-resolution.d.ts +2 -0
  21. package/dist/lib/core/buyer-account-registry.d.mts +65 -0
  22. package/dist/lib/core/buyer-account-registry.d.ts +65 -0
  23. package/dist/lib/core/buyer-account-registry.js +518 -0
  24. package/dist/lib/core/buyer-account-registry.mjs +494 -0
  25. package/dist/lib/core/product-cache.d.mts +18 -0
  26. package/dist/lib/core/product-cache.d.ts +18 -0
  27. package/dist/lib/core/product-cache.js +137 -0
  28. package/dist/lib/core/product-cache.mjs +112 -0
  29. package/dist/lib/errors/index.d.mts +40 -1
  30. package/dist/lib/errors/index.d.ts +40 -1
  31. package/dist/lib/errors/index.js +69 -3
  32. package/dist/lib/errors/index.mjs +64 -3
  33. package/dist/lib/governance/authorization.d.mts +17 -1
  34. package/dist/lib/governance/authorization.d.ts +17 -1
  35. package/dist/lib/governance/authorization.js +55 -7
  36. package/dist/lib/governance/authorization.mjs +59 -7
  37. package/dist/lib/governance/index.d.mts +2 -2
  38. package/dist/lib/governance/index.d.ts +2 -2
  39. package/dist/lib/governance/index.js +2 -0
  40. package/dist/lib/governance/index.mjs +3 -1
  41. package/dist/lib/index.d.mts +5 -3
  42. package/dist/lib/index.d.ts +5 -3
  43. package/dist/lib/index.js +20 -0
  44. package/dist/lib/index.mjs +19 -0
  45. package/dist/lib/protocols/a2a.js +9 -1
  46. package/dist/lib/protocols/a2a.mjs +9 -1
  47. package/dist/lib/protocols/index.js +9 -2
  48. package/dist/lib/protocols/index.mjs +9 -2
  49. package/dist/lib/protocols/mcp-modern.js +2 -1
  50. package/dist/lib/protocols/mcp-modern.mjs +2 -1
  51. package/dist/lib/protocols/mcp.js +5 -2
  52. package/dist/lib/protocols/mcp.mjs +5 -2
  53. package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
  54. package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
  55. package/dist/lib/protocols/rawResponseCapture.js +41 -29
  56. package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
  57. package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
  58. package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
  59. package/dist/lib/protocols/signedRequestRejection.js +209 -0
  60. package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
  61. package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
  62. package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
  63. package/dist/lib/protocols/transportDiagnostics.js +2 -0
  64. package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
  65. package/dist/lib/registry/types.generated.d.mts +112 -45
  66. package/dist/lib/registry/types.generated.d.ts +112 -45
  67. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  68. package/dist/lib/server/account-provisioning.d.mts +2 -0
  69. package/dist/lib/server/account-provisioning.d.ts +2 -0
  70. package/dist/lib/server/account-provisioning.js +30 -0
  71. package/dist/lib/server/account-provisioning.mjs +6 -0
  72. package/dist/lib/server/account-reference-warnings.d.mts +12 -0
  73. package/dist/lib/server/account-reference-warnings.d.ts +12 -0
  74. package/dist/lib/server/account-reference-warnings.js +48 -0
  75. package/dist/lib/server/account-reference-warnings.mjs +23 -0
  76. package/dist/lib/server/auth-signature.js +1 -0
  77. package/dist/lib/server/auth-signature.mjs +1 -0
  78. package/dist/lib/server/create-adcp-server.d.mts +34 -0
  79. package/dist/lib/server/create-adcp-server.d.ts +34 -0
  80. package/dist/lib/server/create-adcp-server.js +225 -14
  81. package/dist/lib/server/create-adcp-server.mjs +225 -14
  82. package/dist/lib/server/decisioning/account.d.mts +2 -0
  83. package/dist/lib/server/decisioning/account.d.ts +2 -0
  84. package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
  85. package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
  86. package/dist/lib/server/index.d.mts +2 -2
  87. package/dist/lib/server/index.d.ts +2 -2
  88. package/dist/lib/server/index.js +2 -0
  89. package/dist/lib/server/index.mjs +3 -1
  90. package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
  91. package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
  92. package/dist/lib/signing/agent-resolver/consistency.js +0 -1
  93. package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
  94. package/dist/lib/signing/agent-resolver/errors.d.mts +1 -1
  95. package/dist/lib/signing/agent-resolver/errors.d.ts +1 -1
  96. package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +2 -0
  97. package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +2 -0
  98. package/dist/lib/signing/agent-resolver/fetch-helpers.js +2 -1
  99. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +2 -1
  100. package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
  101. package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
  102. package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
  103. package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
  104. package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
  105. package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
  106. package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
  107. package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
  108. package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
  109. package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
  110. package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
  111. package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
  112. package/dist/lib/signing/agent-resolver/resolve-agent.js +101 -132
  113. package/dist/lib/signing/agent-resolver/resolve-agent.mjs +102 -133
  114. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
  115. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
  116. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
  117. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
  118. package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
  119. package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
  120. package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
  121. package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
  122. package/dist/lib/signing/brand-jwks.d.mts +27 -75
  123. package/dist/lib/signing/brand-jwks.d.ts +27 -75
  124. package/dist/lib/signing/brand-jwks.js +112 -182
  125. package/dist/lib/signing/brand-jwks.mjs +112 -182
  126. package/dist/lib/signing/errors.d.mts +3 -1
  127. package/dist/lib/signing/errors.d.ts +3 -1
  128. package/dist/lib/signing/errors.js +4 -1
  129. package/dist/lib/signing/errors.mjs +4 -1
  130. package/dist/lib/signing/jwks-https.d.mts +7 -0
  131. package/dist/lib/signing/jwks-https.d.ts +7 -0
  132. package/dist/lib/signing/jwks-https.js +31 -8
  133. package/dist/lib/signing/jwks-https.mjs +31 -8
  134. package/dist/lib/signing/jwks.d.mts +8 -0
  135. package/dist/lib/signing/jwks.d.ts +8 -0
  136. package/dist/lib/signing/middleware.js +2 -1
  137. package/dist/lib/signing/middleware.mjs +2 -1
  138. package/dist/lib/signing/publisher-pins.d.mts +11 -0
  139. package/dist/lib/signing/publisher-pins.d.ts +11 -0
  140. package/dist/lib/signing/publisher-pins.js +125 -0
  141. package/dist/lib/signing/publisher-pins.mjs +101 -0
  142. package/dist/lib/signing/server.d.mts +1 -0
  143. package/dist/lib/signing/server.d.ts +1 -0
  144. package/dist/lib/signing/types.d.mts +5 -0
  145. package/dist/lib/signing/types.d.ts +5 -0
  146. package/dist/lib/signing/verifier.js +49 -4
  147. package/dist/lib/signing/verifier.mjs +49 -4
  148. package/dist/lib/signing/webhook-verifier.d.mts +7 -2
  149. package/dist/lib/signing/webhook-verifier.d.ts +7 -2
  150. package/dist/lib/signing/webhook-verifier.js +42 -2
  151. package/dist/lib/signing/webhook-verifier.mjs +43 -3
  152. package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
  153. package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
  154. package/dist/lib/testing/storyboard/account-policy.js +35 -0
  155. package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
  156. package/dist/lib/testing/storyboard/context.js +6 -0
  157. package/dist/lib/testing/storyboard/context.mjs +6 -0
  158. package/dist/lib/testing/storyboard/request-builder.js +12 -2
  159. package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
  160. package/dist/lib/testing/storyboard/runner.js +3 -2
  161. package/dist/lib/testing/storyboard/runner.mjs +3 -2
  162. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  163. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  164. package/dist/lib/version.d.mts +3 -3
  165. package/dist/lib/version.d.ts +3 -3
  166. package/dist/lib/version.js +3 -3
  167. package/dist/lib/version.mjs +3 -3
  168. package/dist/lib/wholesale-feed-sync/sync.d.mts +1 -0
  169. package/dist/lib/wholesale-feed-sync/sync.d.ts +1 -0
  170. package/dist/lib/wholesale-feed-sync/sync.js +92 -21
  171. package/dist/lib/wholesale-feed-sync/sync.mjs +92 -21
  172. package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
  173. package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
  174. package/docs/TYPE-SUMMARY.md +2 -2
  175. package/docs/guides/BUILD-AN-AGENT.md +2 -2
  176. package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
  177. package/docs/guides/FIRST-CALL-TO-A-SELLER.md +104 -0
  178. package/docs/guides/SIGNING-GUIDE.md +16 -7
  179. package/docs/guides/account-resolution.md +86 -0
  180. package/docs/llms.txt +3 -2
  181. package/docs/migration-14.x-rc-worksheet.md +4 -4
  182. package/docs/migration-4.x-to-5.x.md +1 -0
  183. package/docs/migration-agent-resolution-3.3.md +123 -0
  184. package/docs/recipes/verifying-inbound-webhooks.md +56 -15
  185. package/package.json +2 -2
@@ -1,16 +1,5 @@
1
- /**
2
- * Step 5 of the brand_json_url discovery algorithm: locate the brand.json
3
- * `agents[]` entry whose `url` byte-equals the agent URL the verifier is
4
- * resolving. The byte-equal rule is deliberate (security.mdx §"Discovering
5
- * an agent's signing keys via `brand_json_url`" step 5) — no canonicalization,
6
- * because the most common operator misconfiguration is a trailing-slash or
7
- * scheme mismatch (`https://x.com/mcp` vs `https://x.com/mcp/`), and silently
8
- * canonicalizing past that hides the bug. The match must be exact.
9
- *
10
- * Both flat (`agents[]` at top level) and house-portfolio
11
- * (`house.agents[]` + `brands[].agents[]`) brand.json shapes are supported,
12
- * because either may carry the entry that matches the agent URL.
13
- */
1
+ /** Shared identifier rules, including query escapes (unlike the signing profile's raw query). */
2
+ export declare function canonicalAgentUrl(raw: string): string;
14
3
  export type AgentSelectorErrorCode = 'agent_not_in_brand_json' | 'brand_json_ambiguous';
15
4
  export interface AgentEntry {
16
5
  url: string;
@@ -18,6 +7,12 @@ export interface AgentEntry {
18
7
  /** Other fields preserved verbatim — the resolver passes the entry to the caller. */
19
8
  [key: string]: unknown;
20
9
  }
10
+ export interface AgentSelector {
11
+ agentType?: string;
12
+ agentId?: string;
13
+ }
14
+ /** The key source always belongs to the matched entry, never to the artifact. */
15
+ export declare function agentJwksUri(entry: AgentEntry): string;
21
16
  export declare class AgentSelectorError extends Error {
22
17
  readonly code: AgentSelectorErrorCode;
23
18
  readonly detail: {
@@ -40,7 +35,9 @@ export declare class AgentSelectorError extends Error {
40
35
  */
41
36
  export declare function collectAgentEntries(brandJson: unknown): AgentEntry[];
42
37
  /**
43
- * Find the unique `agents[]` entry whose `url` byte-equals `agentUrl`.
38
+ * Find the unique canonical match. Portfolio repetitions across collections
39
+ * count once when their type and canonical JWKS source agree. Duplicates
40
+ * within one collection remain ambiguous. Type/id only narrow URL matches.
44
41
  *
45
42
  * - Returns the matched entry on a unique hit.
46
43
  * - Throws `AgentSelectorError('agent_not_in_brand_json')` on zero matches.
@@ -50,4 +47,6 @@ export declare function collectAgentEntries(brandJson: unknown): AgentEntry[];
50
47
  * fields. `matched_entries` reflects counterparty-controlled state and is
51
48
  * marked attacker-influenceable in the resolver's error mapping.
52
49
  */
53
- export declare function selectAgentByUrl(brandJson: unknown, agentUrl: string): AgentEntry;
50
+ export declare function selectAgentByUrl(brandJson: unknown, agentUrl: string, selector?: AgentSelector): AgentEntry;
51
+ /** Select a relying party's collection without incorporating sibling brands. */
52
+ export declare function relyingPartyAgentRecord(brandJson: unknown, brandDomain: string): unknown;
@@ -1,16 +1,5 @@
1
- /**
2
- * Step 5 of the brand_json_url discovery algorithm: locate the brand.json
3
- * `agents[]` entry whose `url` byte-equals the agent URL the verifier is
4
- * resolving. The byte-equal rule is deliberate (security.mdx §"Discovering
5
- * an agent's signing keys via `brand_json_url`" step 5) — no canonicalization,
6
- * because the most common operator misconfiguration is a trailing-slash or
7
- * scheme mismatch (`https://x.com/mcp` vs `https://x.com/mcp/`), and silently
8
- * canonicalizing past that hides the bug. The match must be exact.
9
- *
10
- * Both flat (`agents[]` at top level) and house-portfolio
11
- * (`house.agents[]` + `brands[].agents[]`) brand.json shapes are supported,
12
- * because either may carry the entry that matches the agent URL.
13
- */
1
+ /** Shared identifier rules, including query escapes (unlike the signing profile's raw query). */
2
+ export declare function canonicalAgentUrl(raw: string): string;
14
3
  export type AgentSelectorErrorCode = 'agent_not_in_brand_json' | 'brand_json_ambiguous';
15
4
  export interface AgentEntry {
16
5
  url: string;
@@ -18,6 +7,12 @@ export interface AgentEntry {
18
7
  /** Other fields preserved verbatim — the resolver passes the entry to the caller. */
19
8
  [key: string]: unknown;
20
9
  }
10
+ export interface AgentSelector {
11
+ agentType?: string;
12
+ agentId?: string;
13
+ }
14
+ /** The key source always belongs to the matched entry, never to the artifact. */
15
+ export declare function agentJwksUri(entry: AgentEntry): string;
21
16
  export declare class AgentSelectorError extends Error {
22
17
  readonly code: AgentSelectorErrorCode;
23
18
  readonly detail: {
@@ -40,7 +35,9 @@ export declare class AgentSelectorError extends Error {
40
35
  */
41
36
  export declare function collectAgentEntries(brandJson: unknown): AgentEntry[];
42
37
  /**
43
- * Find the unique `agents[]` entry whose `url` byte-equals `agentUrl`.
38
+ * Find the unique canonical match. Portfolio repetitions across collections
39
+ * count once when their type and canonical JWKS source agree. Duplicates
40
+ * within one collection remain ambiguous. Type/id only narrow URL matches.
44
41
  *
45
42
  * - Returns the matched entry on a unique hit.
46
43
  * - Throws `AgentSelectorError('agent_not_in_brand_json')` on zero matches.
@@ -50,4 +47,6 @@ export declare function collectAgentEntries(brandJson: unknown): AgentEntry[];
50
47
  * fields. `matched_entries` reflects counterparty-controlled state and is
51
48
  * marked attacker-influenceable in the resolver's error mapping.
52
49
  */
53
- export declare function selectAgentByUrl(brandJson: unknown, agentUrl: string): AgentEntry;
50
+ export declare function selectAgentByUrl(brandJson: unknown, agentUrl: string, selector?: AgentSelector): AgentEntry;
51
+ /** Select a relying party's collection without incorporating sibling brands. */
52
+ export declare function relyingPartyAgentRecord(brandJson: unknown, brandDomain: string): unknown;
@@ -19,10 +19,34 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var select_agent_exports = {};
20
20
  __export(select_agent_exports, {
21
21
  AgentSelectorError: () => AgentSelectorError,
22
+ agentJwksUri: () => agentJwksUri,
23
+ canonicalAgentUrl: () => canonicalAgentUrl,
22
24
  collectAgentEntries: () => collectAgentEntries,
25
+ relyingPartyAgentRecord: () => relyingPartyAgentRecord,
23
26
  selectAgentByUrl: () => selectAgentByUrl
24
27
  });
25
28
  module.exports = __toCommonJS(select_agent_exports);
29
+ var import_canonicalize = require('../canonicalize.js');
30
+ function canonicalAgentUrl(raw) {
31
+ const authority = /^[a-z][a-z0-9+.\-]*:\/\/([^/?#]*)/i.exec(raw)?.[1];
32
+ if (authority) (0, import_canonicalize.rejectNonAsciiHost)(`https://${authority.slice(authority.lastIndexOf("@") + 1)}`);
33
+ const canonical = (0, import_canonicalize.canonicalTargetUri)(raw, "3.2");
34
+ if (!/^https?:\/\//.test(canonical)) throw new TypeError("Agent identifier must be an HTTP(S) URL");
35
+ return canonical.replace(/%([0-9a-f]{2})/gi, (_escape, hex) => {
36
+ const character = String.fromCharCode(parseInt(hex, 16));
37
+ return /^[a-z0-9._~-]$/i.test(character) ? character : `%${hex.toUpperCase()}`;
38
+ });
39
+ }
40
+ function agentJwksUri(entry) {
41
+ if (entry.jwks_uri !== void 0) {
42
+ if (typeof entry.jwks_uri !== "string" || !entry.jwks_uri) throw new TypeError("Invalid jwks_uri");
43
+ const source = new URL(entry.jwks_uri);
44
+ if (source.username || source.password) throw new TypeError("JWKS URI must not contain userinfo");
45
+ canonicalAgentUrl(entry.jwks_uri);
46
+ return entry.jwks_uri;
47
+ }
48
+ return `${new URL(canonicalAgentUrl(entry.url)).origin}/.well-known/jwks.json`;
49
+ }
26
50
  class AgentSelectorError extends Error {
27
51
  code;
28
52
  detail;
@@ -34,22 +58,31 @@ class AgentSelectorError extends Error {
34
58
  }
35
59
  }
36
60
  function collectAgentEntries(brandJson) {
61
+ return agentCollections(brandJson).flat();
62
+ }
63
+ function agentCollections(brandJson) {
37
64
  if (!brandJson || typeof brandJson !== "object") return [];
38
- const entries = [];
65
+ const collections = [];
39
66
  const obj = brandJson;
40
- pushAgentArray(obj.agents, entries);
67
+ const add = (value) => {
68
+ const entries = [];
69
+ pushAgentArray(value, entries);
70
+ collections.push(entries);
71
+ };
72
+ if (obj.agents !== void 0) {
73
+ add(obj.agents);
74
+ return collections;
75
+ }
41
76
  const house = obj.house;
42
77
  if (house && typeof house === "object") {
43
- pushAgentArray(house.agents, entries);
44
- }
45
- if (Array.isArray(obj.brands)) {
46
- for (const brand of obj.brands) {
47
- if (brand && typeof brand === "object") {
48
- pushAgentArray(brand.agents, entries);
78
+ add(house.agents);
79
+ if (Array.isArray(obj.brands)) {
80
+ for (const brand of obj.brands) {
81
+ if (brand && typeof brand === "object") add(brand.agents);
49
82
  }
50
83
  }
51
84
  }
52
- return entries;
85
+ return collections;
53
86
  }
54
87
  function pushAgentArray(value, out) {
55
88
  if (!Array.isArray(value)) return;
@@ -62,13 +95,37 @@ function pushAgentArray(value, out) {
62
95
  }
63
96
  }
64
97
  }
65
- function selectAgentByUrl(brandJson, agentUrl) {
66
- const entries = collectAgentEntries(brandJson);
67
- const matches = entries.filter((e) => e.url === agentUrl);
98
+ function selectAgentByUrl(brandJson, agentUrl, selector = {}) {
99
+ const canonicalUrl = canonicalAgentUrl(agentUrl);
100
+ const matches = [];
101
+ const seen = /* @__PURE__ */ new Set();
102
+ for (const collection of agentCollections(brandJson)) {
103
+ const local = collection.filter((e) => {
104
+ try {
105
+ return canonicalAgentUrl(e.url) === canonicalUrl && (selector.agentType === void 0 || e.type === selector.agentType) && (selector.agentId === void 0 || e.id === selector.agentId);
106
+ } catch {
107
+ return false;
108
+ }
109
+ });
110
+ if (local.length > 1) {
111
+ throw new AgentSelectorError("brand_json_ambiguous", `Multiple brand.json agent entries match ${agentUrl}`, {
112
+ agent_url: agentUrl,
113
+ matched_count: local.length,
114
+ matched_entries: local
115
+ });
116
+ }
117
+ for (const entry of local) {
118
+ const source = JSON.stringify([entry.type, canonicalAgentUrl(agentJwksUri(entry))]);
119
+ if (!seen.has(source)) {
120
+ seen.add(source);
121
+ matches.push(entry);
122
+ }
123
+ }
124
+ }
68
125
  if (matches.length === 0) {
69
126
  throw new AgentSelectorError(
70
127
  "agent_not_in_brand_json",
71
- `No brand.json agent entry has url byte-equal to ${agentUrl}`,
128
+ `No brand.json agent entry has canonical URL ${canonicalUrl}`,
72
129
  { agent_url: agentUrl }
73
130
  );
74
131
  }
@@ -81,9 +138,37 @@ function selectAgentByUrl(brandJson, agentUrl) {
81
138
  }
82
139
  return matches[0];
83
140
  }
141
+ function relyingPartyAgentRecord(brandJson, brandDomain) {
142
+ if (!brandJson || typeof brandJson !== "object") throw new TypeError("Invalid brand.json");
143
+ const obj = brandJson;
144
+ if (!obj.house || typeof obj.house !== "object") return obj;
145
+ const domainHost = (domain) => {
146
+ if (typeof domain !== "string" || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.?$/i.test(domain))
147
+ throw new TypeError("Invalid brand domain");
148
+ return new URL(canonicalAgentUrl(`https://${domain}`)).hostname;
149
+ };
150
+ const host = domainHost(brandDomain);
151
+ const house = obj.house;
152
+ if (house.domain !== void 0 && domainHost(house.domain) === host) {
153
+ return { agents: house.agents };
154
+ }
155
+ const brands = Array.isArray(obj.brands) ? obj.brands : [];
156
+ const matches = brands.filter((b) => {
157
+ try {
158
+ return b && typeof b === "object" && typeof b.url === "string" && new URL(canonicalAgentUrl(b.url)).hostname === host;
159
+ } catch {
160
+ return false;
161
+ }
162
+ });
163
+ if (matches.length !== 1) throw new TypeError("Brand domain is absent or ambiguous in portfolio");
164
+ return { agents: matches[0].agents === void 0 ? house.agents : matches[0].agents };
165
+ }
84
166
  // Annotate the CommonJS export names for ESM import in node:
85
167
  0 && (module.exports = {
86
168
  AgentSelectorError,
169
+ agentJwksUri,
170
+ canonicalAgentUrl,
87
171
  collectAgentEntries,
172
+ relyingPartyAgentRecord,
88
173
  selectAgentByUrl
89
174
  });
@@ -1,3 +1,24 @@
1
+ import { canonicalTargetUri, rejectNonAsciiHost } from "../canonicalize.mjs";
2
+ function canonicalAgentUrl(raw) {
3
+ const authority = /^[a-z][a-z0-9+.\-]*:\/\/([^/?#]*)/i.exec(raw)?.[1];
4
+ if (authority) rejectNonAsciiHost(`https://${authority.slice(authority.lastIndexOf("@") + 1)}`);
5
+ const canonical = canonicalTargetUri(raw, "3.2");
6
+ if (!/^https?:\/\//.test(canonical)) throw new TypeError("Agent identifier must be an HTTP(S) URL");
7
+ return canonical.replace(/%([0-9a-f]{2})/gi, (_escape, hex) => {
8
+ const character = String.fromCharCode(parseInt(hex, 16));
9
+ return /^[a-z0-9._~-]$/i.test(character) ? character : `%${hex.toUpperCase()}`;
10
+ });
11
+ }
12
+ function agentJwksUri(entry) {
13
+ if (entry.jwks_uri !== void 0) {
14
+ if (typeof entry.jwks_uri !== "string" || !entry.jwks_uri) throw new TypeError("Invalid jwks_uri");
15
+ const source = new URL(entry.jwks_uri);
16
+ if (source.username || source.password) throw new TypeError("JWKS URI must not contain userinfo");
17
+ canonicalAgentUrl(entry.jwks_uri);
18
+ return entry.jwks_uri;
19
+ }
20
+ return `${new URL(canonicalAgentUrl(entry.url)).origin}/.well-known/jwks.json`;
21
+ }
1
22
  class AgentSelectorError extends Error {
2
23
  code;
3
24
  detail;
@@ -9,22 +30,31 @@ class AgentSelectorError extends Error {
9
30
  }
10
31
  }
11
32
  function collectAgentEntries(brandJson) {
33
+ return agentCollections(brandJson).flat();
34
+ }
35
+ function agentCollections(brandJson) {
12
36
  if (!brandJson || typeof brandJson !== "object") return [];
13
- const entries = [];
37
+ const collections = [];
14
38
  const obj = brandJson;
15
- pushAgentArray(obj.agents, entries);
39
+ const add = (value) => {
40
+ const entries = [];
41
+ pushAgentArray(value, entries);
42
+ collections.push(entries);
43
+ };
44
+ if (obj.agents !== void 0) {
45
+ add(obj.agents);
46
+ return collections;
47
+ }
16
48
  const house = obj.house;
17
49
  if (house && typeof house === "object") {
18
- pushAgentArray(house.agents, entries);
19
- }
20
- if (Array.isArray(obj.brands)) {
21
- for (const brand of obj.brands) {
22
- if (brand && typeof brand === "object") {
23
- pushAgentArray(brand.agents, entries);
50
+ add(house.agents);
51
+ if (Array.isArray(obj.brands)) {
52
+ for (const brand of obj.brands) {
53
+ if (brand && typeof brand === "object") add(brand.agents);
24
54
  }
25
55
  }
26
56
  }
27
- return entries;
57
+ return collections;
28
58
  }
29
59
  function pushAgentArray(value, out) {
30
60
  if (!Array.isArray(value)) return;
@@ -37,13 +67,37 @@ function pushAgentArray(value, out) {
37
67
  }
38
68
  }
39
69
  }
40
- function selectAgentByUrl(brandJson, agentUrl) {
41
- const entries = collectAgentEntries(brandJson);
42
- const matches = entries.filter((e) => e.url === agentUrl);
70
+ function selectAgentByUrl(brandJson, agentUrl, selector = {}) {
71
+ const canonicalUrl = canonicalAgentUrl(agentUrl);
72
+ const matches = [];
73
+ const seen = /* @__PURE__ */ new Set();
74
+ for (const collection of agentCollections(brandJson)) {
75
+ const local = collection.filter((e) => {
76
+ try {
77
+ return canonicalAgentUrl(e.url) === canonicalUrl && (selector.agentType === void 0 || e.type === selector.agentType) && (selector.agentId === void 0 || e.id === selector.agentId);
78
+ } catch {
79
+ return false;
80
+ }
81
+ });
82
+ if (local.length > 1) {
83
+ throw new AgentSelectorError("brand_json_ambiguous", `Multiple brand.json agent entries match ${agentUrl}`, {
84
+ agent_url: agentUrl,
85
+ matched_count: local.length,
86
+ matched_entries: local
87
+ });
88
+ }
89
+ for (const entry of local) {
90
+ const source = JSON.stringify([entry.type, canonicalAgentUrl(agentJwksUri(entry))]);
91
+ if (!seen.has(source)) {
92
+ seen.add(source);
93
+ matches.push(entry);
94
+ }
95
+ }
96
+ }
43
97
  if (matches.length === 0) {
44
98
  throw new AgentSelectorError(
45
99
  "agent_not_in_brand_json",
46
- `No brand.json agent entry has url byte-equal to ${agentUrl}`,
100
+ `No brand.json agent entry has canonical URL ${canonicalUrl}`,
47
101
  { agent_url: agentUrl }
48
102
  );
49
103
  }
@@ -56,8 +110,36 @@ function selectAgentByUrl(brandJson, agentUrl) {
56
110
  }
57
111
  return matches[0];
58
112
  }
113
+ function relyingPartyAgentRecord(brandJson, brandDomain) {
114
+ if (!brandJson || typeof brandJson !== "object") throw new TypeError("Invalid brand.json");
115
+ const obj = brandJson;
116
+ if (!obj.house || typeof obj.house !== "object") return obj;
117
+ const domainHost = (domain) => {
118
+ if (typeof domain !== "string" || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.?$/i.test(domain))
119
+ throw new TypeError("Invalid brand domain");
120
+ return new URL(canonicalAgentUrl(`https://${domain}`)).hostname;
121
+ };
122
+ const host = domainHost(brandDomain);
123
+ const house = obj.house;
124
+ if (house.domain !== void 0 && domainHost(house.domain) === host) {
125
+ return { agents: house.agents };
126
+ }
127
+ const brands = Array.isArray(obj.brands) ? obj.brands : [];
128
+ const matches = brands.filter((b) => {
129
+ try {
130
+ return b && typeof b === "object" && typeof b.url === "string" && new URL(canonicalAgentUrl(b.url)).hostname === host;
131
+ } catch {
132
+ return false;
133
+ }
134
+ });
135
+ if (matches.length !== 1) throw new TypeError("Brand domain is absent or ambiguous in portfolio");
136
+ return { agents: matches[0].agents === void 0 ? house.agents : matches[0].agents };
137
+ }
59
138
  export {
60
139
  AgentSelectorError,
140
+ agentJwksUri,
141
+ canonicalAgentUrl,
61
142
  collectAgentEntries,
143
+ relyingPartyAgentRecord,
62
144
  selectAgentByUrl
63
145
  };
@@ -1,32 +1,10 @@
1
- /**
2
- * Receiver-side ergonomic: resolve a sender's JWKS by fetching their
3
- * `brand.json`, extracting the `jwks_uri` for the agent whose webhooks we're
4
- * verifying, and delegating to {@link HttpsJwksResolver}.
5
- *
6
- * Hand the resulting instance to `verifyWebhookSignature.jwks` (or
7
- * `verifyRequestSignature.jwks`) and the receiver never has to know where the
8
- * sender hosts their keys — brand.json is the single source of truth.
9
- *
10
- * Follows the two documented redirect variants of `brand.json`
11
- * (`authoritative_location` and `house`) up to a caller-configurable hop
12
- * depth. When the selected agent has no `jwks_uri`, falls back to
13
- * `/.well-known/jwks.json` on the origin of the agent's `url` — but only
14
- * when that origin matches the final brand.json origin. The spec's
15
- * well-known fallback exists so publishers hosting brand.json and their
16
- * agents on the same origin can skip the explicit `jwks_uri`; accepting a
17
- * cross-origin fallback would let an attacker pivot the trust anchor to a
18
- * controlled host with a permissive JWKS.
19
- *
20
- * Caching is stacked with the inner {@link HttpsJwksResolver}: brand.json
21
- * honors its own `Cache-Control`/`ETag` (bounded by `maxAgeSeconds`), and
22
- * unknown-kid refreshes cascade — first to the JWKS endpoint, then (if the
23
- * JWKS still doesn't have the kid and the brand.json cooldown has elapsed) to
24
- * brand.json itself, in case the sender rotated `jwks_uri`.
25
- */
1
+ /** Capability-confirmed sender key discovery and SSRF-safe brand.json fetching. */
26
2
  import { type SsrfDnsLookup } from '../net/index.mjs';
27
3
  import type { JwksResolver } from './jwks.mjs';
28
4
  import { type HttpsJwksResolverOptions } from './jwks-https.mjs';
29
5
  import type { AdcpJsonWebKey } from './types.mjs';
6
+ import type { AgentProtocol, FetchCapabilitiesFn } from './agent-resolver/resolve-agent.mjs';
7
+ import type { JwksResolution } from './jwks.mjs';
30
8
  export type BrandAgentType = 'brand' | 'rights' | 'measurement' | 'governance' | 'creative' | 'sales' | 'buying' | 'signals';
31
9
  export type BrandJsonResolverErrorCode = 'invalid_url' | 'invalid_house' | 'redirect_loop' | 'redirect_depth_exceeded' | 'fetch_failed' | 'invalid_body' | 'schema_invalid' | 'agent_not_found' | 'agent_ambiguous' | 'jwks_origin_mismatch';
32
10
  /**
@@ -45,21 +23,20 @@ export declare class BrandJsonResolverError extends Error {
45
23
  });
46
24
  }
47
25
  export interface BrandJsonJwksResolverOptions {
26
+ /** Expected agent URL. Prefer supplying it; legacy configurations infer one onboarding URL, then confirm it through capabilities and canonical matching. */
27
+ agentUrl?: string;
28
+ /** Capabilities transport. Defaults to MCP; A2A integrations must set 'a2a'. */
29
+ protocol?: AgentProtocol;
30
+ /** Enable the 3.x webhook-only domain-derived fallback. Default true. Request verification refuses these keys. */
31
+ legacyWebhookFallback?: boolean;
32
+ fetchCapabilities?: FetchCapabilitiesFn;
48
33
  /** Functional role of the agent whose keys we want to resolve. */
49
34
  agentType: BrandAgentType;
50
35
  /**
51
- * Agent id from `agents[].id`. Required when brand.json declares more than
52
- * one agent of the requested type (otherwise the selector is ambiguous).
53
- * Optional when the type is unique.
36
+ * Agent id from `agents[].id`, used only to narrow canonical URL matches.
54
37
  */
55
38
  agentId?: string;
56
- /**
57
- * Brand id within a house portfolio (`brands[].id`). When omitted on a
58
- * portfolio brand.json, the resolver looks at `house.agents[]`. When set,
59
- * the resolver looks at `brands[brandId].agents[]` first and falls back to
60
- * `house.agents[]` if no agent of the requested type is declared on the
61
- * brand itself.
62
- */
39
+ /** @deprecated Used only to infer an onboarding URL when agentUrl is absent. Canonical verification searches all operator collections. */
63
40
  brandId?: string;
64
41
  /**
65
42
  * Minimum seconds between brand.json refetches. Mirrors the JWKS cooldown
@@ -73,11 +50,7 @@ export interface BrandJsonJwksResolverOptions {
73
50
  * allow longer. Default 3600s (1 hour).
74
51
  */
75
52
  maxAgeSeconds?: number;
76
- /**
77
- * Maximum redirect hops to follow through `authoritative_location` /
78
- * `house` variants. Default 3 — enough for `authoritative_location → house
79
- * → portfolio` without inviting loops.
80
- */
53
+ /** @deprecated Only controls legacy onboarding indirection. Explicit capability URLs allow no redirects; webhook fallback allows one document hop. */
81
54
  maxRedirects?: number;
82
55
  /**
83
56
  * Allow `http://` / private-IP brand.json and JWKS URLs (dev loops only).
@@ -90,52 +63,29 @@ export interface BrandJsonJwksResolverOptions {
90
63
  * address remains subject to SSRF classification and connection pinning.
91
64
  */
92
65
  lookup?: SsrfDnsLookup;
93
- /**
94
- * Forwarded to the inner {@link HttpsJwksResolver} constructor.
95
- * `allowPrivateIp`, `lookup`, and `now` are set from the outer options and
96
- * should not be passed here.
97
- */
66
+ /** Existing settings remain accepted. maxAgeSeconds/minCooldownSeconds configure caching; protocol floors and fail-closed behavior replace other settings. */
98
67
  jwksOptions?: Omit<HttpsJwksResolverOptions, 'allowPrivateIp' | 'lookup' | 'now'>;
99
68
  /** Clock override for deterministic tests. Returns epoch seconds. */
100
69
  now?: () => number;
101
70
  }
102
71
  /**
103
- * JWKS resolver backed by a sender's `brand.json`. Construct one per
104
- * counterparty (or keyed by `brand.json` URL + agent selector) and hand it to
105
- * the webhook/request verifier as the `jwks` dependency.
72
+ * Confirmed operator mapping. The supplied brand.json URL must agree with
73
+ * capabilities on every cache refresh. Prefer ResolvedAgentJwksResolver
74
+ * when the application's onboarding record holds only the seller URL.
106
75
  */
107
76
  export declare class BrandJsonJwksResolver implements JwksResolver {
108
- private readonly url;
109
- private readonly selector;
110
- private readonly minCooldown;
111
- private readonly maxAge;
112
- private readonly maxRedirects;
113
- private readonly allowPrivateIp;
114
- private readonly lookup;
115
- private readonly jwksOptions;
116
- private readonly now;
117
- private snapshot;
118
- private inner;
119
- private inFlight;
77
+ private readonly brandJsonUrl;
78
+ private readonly options;
79
+ private resolver?;
80
+ private onboarding?;
81
+ private lastOnboardingAttempt;
120
82
  constructor(brandJsonUrl: string, options: BrandJsonJwksResolverOptions);
121
- /**
122
- * Resolve a JWK by `kid`. On a cold cache, fetches brand.json first; on an
123
- * expired brand.json snapshot, refreshes respecting the cooldown. Unknown
124
- * kids cascade: first ask the inner HttpsJwksResolver (which will refetch
125
- * its own URL if cooldown has elapsed); if still unknown, refresh
126
- * brand.json in case `jwks_uri` rotated.
127
- */
83
+ private createResolver;
84
+ private confirmedResolver;
128
85
  resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
129
- /**
130
- * The agent URL we resolved `jwks_uri` from. Populated after the first
131
- * successful refresh; useful for verifier result attribution
132
- * (`VerifyWebhookOptions.agentUrlForKeyid`).
133
- */
86
+ resolveWithMetadata(keyid: string): Promise<JwksResolution>;
134
87
  get agentUrl(): string | undefined;
135
- /** Force a refetch of both brand.json and the inner JWKS, bypassing the cooldown. */
136
88
  forceRefresh(): Promise<void>;
137
- private refresh;
138
- private doRefresh;
139
89
  }
140
90
  export interface FetchedBrandJson {
141
91
  status: 'ok' | 'not_modified';
@@ -149,6 +99,8 @@ export interface FetchBrandJsonOptions {
149
99
  startUrl: string;
150
100
  /** ETag sent only to the entry URL for cache revalidation. */
151
101
  currentEtag?: string;
102
+ /** Initial domain-derived fetch may redirect only between host and exact www counterpart. */
103
+ domainDerived?: boolean;
152
104
  /** Maximum JSON-level `authoritative_location` / `house` hops. Default 3, hard maximum 10. */
153
105
  maxRedirects?: number;
154
106
  /** Permit HTTP and private addresses for controlled development environments. */