@adcp/sdk 14.0.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 (234) 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 +35 -2
  10. package/dist/lib/core/SingleAgentClient.d.ts +35 -2
  11. package/dist/lib/core/SingleAgentClient.js +377 -36
  12. package/dist/lib/core/SingleAgentClient.mjs +387 -38
  13. package/dist/lib/core/TaskExecutor.d.mts +3 -1
  14. package/dist/lib/core/TaskExecutor.d.ts +3 -1
  15. package/dist/lib/core/TaskExecutor.js +17 -12
  16. package/dist/lib/core/TaskExecutor.mjs +17 -12
  17. package/dist/lib/core/account-key.d.mts +3 -0
  18. package/dist/lib/core/account-key.d.ts +3 -0
  19. package/dist/lib/core/account-key.js +41 -0
  20. package/dist/lib/core/account-key.mjs +17 -0
  21. package/dist/lib/core/account-resolution.d.mts +2 -0
  22. package/dist/lib/core/account-resolution.d.ts +2 -0
  23. package/dist/lib/core/buyer-account-registry.d.mts +93 -0
  24. package/dist/lib/core/buyer-account-registry.d.ts +93 -0
  25. package/dist/lib/core/buyer-account-registry.js +602 -0
  26. package/dist/lib/core/buyer-account-registry.mjs +578 -0
  27. package/dist/lib/core/product-cache.d.mts +18 -0
  28. package/dist/lib/core/product-cache.d.ts +18 -0
  29. package/dist/lib/core/product-cache.js +137 -0
  30. package/dist/lib/core/product-cache.mjs +112 -0
  31. package/dist/lib/errors/index.d.mts +40 -1
  32. package/dist/lib/errors/index.d.ts +40 -1
  33. package/dist/lib/errors/index.js +69 -3
  34. package/dist/lib/errors/index.mjs +64 -3
  35. package/dist/lib/governance/authorization.d.mts +17 -1
  36. package/dist/lib/governance/authorization.d.ts +17 -1
  37. package/dist/lib/governance/authorization.js +55 -7
  38. package/dist/lib/governance/authorization.mjs +59 -7
  39. package/dist/lib/governance/index.d.mts +2 -2
  40. package/dist/lib/governance/index.d.ts +2 -2
  41. package/dist/lib/governance/index.js +2 -0
  42. package/dist/lib/governance/index.mjs +3 -1
  43. package/dist/lib/index.d.mts +7 -4
  44. package/dist/lib/index.d.ts +7 -4
  45. package/dist/lib/index.js +29 -0
  46. package/dist/lib/index.mjs +31 -1
  47. package/dist/lib/net/agent-transport-fetch.d.mts +4 -0
  48. package/dist/lib/net/agent-transport-fetch.d.ts +4 -0
  49. package/dist/lib/net/agent-transport-fetch.js +18 -5
  50. package/dist/lib/net/agent-transport-fetch.mjs +17 -5
  51. package/dist/lib/protocols/a2a.js +9 -1
  52. package/dist/lib/protocols/a2a.mjs +9 -1
  53. package/dist/lib/protocols/index.js +9 -2
  54. package/dist/lib/protocols/index.mjs +9 -2
  55. package/dist/lib/protocols/mcp-modern.js +2 -1
  56. package/dist/lib/protocols/mcp-modern.mjs +2 -1
  57. package/dist/lib/protocols/mcp.js +5 -2
  58. package/dist/lib/protocols/mcp.mjs +5 -2
  59. package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
  60. package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
  61. package/dist/lib/protocols/rawResponseCapture.js +41 -29
  62. package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
  63. package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
  64. package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
  65. package/dist/lib/protocols/signedRequestRejection.js +209 -0
  66. package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
  67. package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
  68. package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
  69. package/dist/lib/protocols/transportDiagnostics.js +2 -0
  70. package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
  71. package/dist/lib/registry/types.generated.d.mts +112 -45
  72. package/dist/lib/registry/types.generated.d.ts +112 -45
  73. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  74. package/dist/lib/server/account-provisioning.d.mts +2 -0
  75. package/dist/lib/server/account-provisioning.d.ts +2 -0
  76. package/dist/lib/server/account-provisioning.js +30 -0
  77. package/dist/lib/server/account-provisioning.mjs +6 -0
  78. package/dist/lib/server/account-reference-warnings.d.mts +12 -0
  79. package/dist/lib/server/account-reference-warnings.d.ts +12 -0
  80. package/dist/lib/server/account-reference-warnings.js +48 -0
  81. package/dist/lib/server/account-reference-warnings.mjs +23 -0
  82. package/dist/lib/server/auth-signature.js +1 -0
  83. package/dist/lib/server/auth-signature.mjs +1 -0
  84. package/dist/lib/server/create-adcp-server.d.mts +34 -0
  85. package/dist/lib/server/create-adcp-server.d.ts +34 -0
  86. package/dist/lib/server/create-adcp-server.js +225 -14
  87. package/dist/lib/server/create-adcp-server.mjs +225 -14
  88. package/dist/lib/server/decisioning/account.d.mts +2 -0
  89. package/dist/lib/server/decisioning/account.d.ts +2 -0
  90. package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
  91. package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
  92. package/dist/lib/server/index.d.mts +2 -2
  93. package/dist/lib/server/index.d.ts +2 -2
  94. package/dist/lib/server/index.js +2 -0
  95. package/dist/lib/server/index.mjs +3 -1
  96. package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
  97. package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
  98. package/dist/lib/signing/agent-resolver/consistency.js +0 -1
  99. package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
  100. package/dist/lib/signing/agent-resolver/errors.d.mts +3 -1
  101. package/dist/lib/signing/agent-resolver/errors.d.ts +3 -1
  102. package/dist/lib/signing/agent-resolver/errors.js +6 -0
  103. package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
  104. package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +11 -0
  105. package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +11 -0
  106. package/dist/lib/signing/agent-resolver/fetch-helpers.js +37 -2
  107. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +40 -3
  108. package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
  109. package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
  110. package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
  111. package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
  112. package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
  113. package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
  114. package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
  115. package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
  116. package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
  117. package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
  118. package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
  119. package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
  120. package/dist/lib/signing/agent-resolver/resolve-agent.js +148 -137
  121. package/dist/lib/signing/agent-resolver/resolve-agent.mjs +157 -139
  122. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
  123. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
  124. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
  125. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
  126. package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
  127. package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
  128. package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
  129. package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
  130. package/dist/lib/signing/brand-jwks.d.mts +29 -75
  131. package/dist/lib/signing/brand-jwks.d.ts +29 -75
  132. package/dist/lib/signing/brand-jwks.js +120 -182
  133. package/dist/lib/signing/brand-jwks.mjs +120 -182
  134. package/dist/lib/signing/errors.d.mts +7 -3
  135. package/dist/lib/signing/errors.d.ts +7 -3
  136. package/dist/lib/signing/errors.js +7 -2
  137. package/dist/lib/signing/errors.mjs +7 -2
  138. package/dist/lib/signing/jwks-https.d.mts +7 -0
  139. package/dist/lib/signing/jwks-https.d.ts +7 -0
  140. package/dist/lib/signing/jwks-https.js +31 -8
  141. package/dist/lib/signing/jwks-https.mjs +31 -8
  142. package/dist/lib/signing/jwks.d.mts +8 -0
  143. package/dist/lib/signing/jwks.d.ts +8 -0
  144. package/dist/lib/signing/middleware.js +2 -1
  145. package/dist/lib/signing/middleware.mjs +2 -1
  146. package/dist/lib/signing/publisher-pins.d.mts +11 -0
  147. package/dist/lib/signing/publisher-pins.d.ts +11 -0
  148. package/dist/lib/signing/publisher-pins.js +125 -0
  149. package/dist/lib/signing/publisher-pins.mjs +101 -0
  150. package/dist/lib/signing/server.d.mts +1 -0
  151. package/dist/lib/signing/server.d.ts +1 -0
  152. package/dist/lib/signing/types.d.mts +5 -0
  153. package/dist/lib/signing/types.d.ts +5 -0
  154. package/dist/lib/signing/verifier.js +50 -5
  155. package/dist/lib/signing/verifier.mjs +50 -5
  156. package/dist/lib/signing/webhook-verifier.d.mts +7 -2
  157. package/dist/lib/signing/webhook-verifier.d.ts +7 -2
  158. package/dist/lib/signing/webhook-verifier.js +42 -2
  159. package/dist/lib/signing/webhook-verifier.mjs +42 -2
  160. package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
  161. package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
  162. package/dist/lib/testing/storyboard/account-policy.js +35 -0
  163. package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
  164. package/dist/lib/testing/storyboard/context.js +6 -0
  165. package/dist/lib/testing/storyboard/context.mjs +6 -0
  166. package/dist/lib/testing/storyboard/request-builder.js +12 -2
  167. package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
  168. package/dist/lib/testing/storyboard/runner.js +3 -2
  169. package/dist/lib/testing/storyboard/runner.mjs +3 -2
  170. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  171. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  172. package/dist/lib/types/accept-proposal.d.ts +19 -1
  173. package/dist/lib/types/buy-products.d.ts +19 -1
  174. package/dist/lib/types/check-governance.d.ts +19 -1
  175. package/dist/lib/types/comply-test-controller.d.ts +19 -1
  176. package/dist/lib/types/control-media-buy.d.ts +19 -1
  177. package/dist/lib/types/core.generated.d.mts +14 -1
  178. package/dist/lib/types/core.generated.d.ts +14 -1
  179. package/dist/lib/types/create-media-buy.d.ts +14 -1
  180. package/dist/lib/types/get-media-buys.d.ts +14 -1
  181. package/dist/lib/types/get-products.d.ts +19 -1
  182. package/dist/lib/types/list-products.d.ts +19 -1
  183. package/dist/lib/types/refine-proposals.d.ts +19 -1
  184. package/dist/lib/types/request-proposals.d.ts +19 -1
  185. package/dist/lib/types/schemas.generated.d.ts +12 -3
  186. package/dist/lib/types/schemas.generated.js +4 -1
  187. package/dist/lib/types/schemas.generated.mjs +4 -1
  188. package/dist/lib/types/tools.generated.d.mts +14 -1
  189. package/dist/lib/types/tools.generated.d.ts +14 -1
  190. package/dist/lib/types/update-media-buy.d.ts +14 -1
  191. package/dist/lib/version.d.mts +3 -3
  192. package/dist/lib/version.d.ts +3 -3
  193. package/dist/lib/version.js +3 -3
  194. package/dist/lib/version.mjs +3 -3
  195. package/dist/lib/webhooks/index.d.mts +24 -0
  196. package/dist/lib/webhooks/index.d.ts +24 -0
  197. package/dist/lib/webhooks/index.js +50 -24
  198. package/dist/lib/webhooks/index.mjs +49 -24
  199. package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
  200. package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
  201. package/dist/lib/wholesale-feed-sync/index.js +7 -0
  202. package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
  203. package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
  204. package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
  205. package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
  206. package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
  207. package/dist/lib/wholesale-feed-sync/sync.d.mts +13 -29
  208. package/dist/lib/wholesale-feed-sync/sync.d.ts +13 -29
  209. package/dist/lib/wholesale-feed-sync/sync.js +208 -281
  210. package/dist/lib/wholesale-feed-sync/sync.mjs +213 -281
  211. package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
  212. package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
  213. package/docs/README.md +6 -0
  214. package/docs/TYPE-SUMMARY.md +2 -2
  215. package/docs/guides/BUILD-AN-AGENT.md +2 -2
  216. package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
  217. package/docs/guides/BUYER-STORAGE.md +3 -0
  218. package/docs/guides/FIRST-CALL-TO-A-SELLER.md +106 -0
  219. package/docs/guides/SIGNING-GUIDE.md +16 -7
  220. package/docs/guides/account-resolution.md +132 -10
  221. package/docs/llms.txt +3 -2
  222. package/docs/migration-14.0-to-14.1.md +85 -0
  223. package/docs/migration-14.x-rc-worksheet.md +4 -4
  224. package/docs/migration-4.x-to-5.x.md +1 -0
  225. package/docs/migration-agent-resolution-3.3.md +125 -0
  226. package/docs/recipes/verifying-inbound-webhooks.md +60 -15
  227. package/package.json +3 -2
  228. package/skills/adcp-brand.previous/SKILL.md +0 -200
  229. package/skills/adcp-creative.previous/SKILL.md +0 -305
  230. package/skills/adcp-governance.previous/SKILL.md +0 -566
  231. package/skills/adcp-measurement.previous/SKILL.md +0 -136
  232. package/skills/adcp-media-buy.previous/SKILL.md +0 -556
  233. package/skills/adcp-si.previous/SKILL.md +0 -206
  234. package/skills/adcp-signals.previous/SKILL.md +0 -204
@@ -23,10 +23,17 @@ class ResolvedAgentJwksResolver {
23
23
  cacheTtlSeconds;
24
24
  unknownKidCooldownSeconds;
25
25
  resolveAgentFn;
26
+ canonicalAgentUrl;
27
+ legacyWebhookFallback;
28
+ operatorRecord;
29
+ get resolvedAgentUrl() {
30
+ return this.canonicalAgentUrl;
31
+ }
26
32
  keys = /* @__PURE__ */ new Map();
27
33
  expiresAt = 0;
28
34
  operatorAuthorizationValidUntil;
29
35
  lastUnknownKidRefresh = Number.NEGATIVE_INFINITY;
36
+ lastDiscoveryAttemptAt = Number.NEGATIVE_INFINITY;
30
37
  inFlight;
31
38
  async resolve(keyid) {
32
39
  return (await this.resolveWithMetadata(keyid)).jwk;
@@ -35,6 +42,15 @@ class ResolvedAgentJwksResolver {
35
42
  const now = this.now();
36
43
  let refreshed = false;
37
44
  if (now >= this.expiresAt) {
45
+ if (!this.inFlight && now - this.lastDiscoveryAttemptAt < 30) {
46
+ const expiredAuthorization = this.operatorAuthorizationValidUntil !== void 0 && now >= this.operatorAuthorizationValidUntil;
47
+ throw new AgentResolverError(
48
+ expiredAuthorization ? "request_signature_brand_origin_mismatch" : "request_signature_brand_json_unreachable",
49
+ "Expired operator mapping cannot be used during the discovery cooldown",
50
+ { agent_url: this.agentUrl },
51
+ ["agent_url"]
52
+ );
53
+ }
38
54
  await this.refresh();
39
55
  refreshed = true;
40
56
  }
@@ -44,14 +60,22 @@ class ResolvedAgentJwksResolver {
44
60
  this.lastUnknownKidRefresh = now;
45
61
  return this.resolution(null);
46
62
  }
47
- if (now - this.lastUnknownKidRefresh < this.unknownKidCooldownSeconds) return this.resolution(null);
63
+ if (now - Math.max(this.lastUnknownKidRefresh, this.lastDiscoveryAttemptAt) < Math.max(30, this.unknownKidCooldownSeconds))
64
+ return this.resolution(null);
48
65
  await this.refresh();
49
66
  this.lastUnknownKidRefresh = now;
50
67
  return this.resolution(this.keys.get(keyid) ?? null);
51
68
  }
69
+ async forceRefresh() {
70
+ this.expiresAt = 0;
71
+ await this.refresh();
72
+ }
52
73
  resolution(jwk) {
53
74
  return {
54
75
  jwk,
76
+ ...this.operatorRecord !== void 0 && { operatorRecord: this.operatorRecord },
77
+ ...this.legacyWebhookFallback && { legacyWebhookFallback: true },
78
+ ...this.resolvedAgentUrl !== void 0 && { agentUrl: this.resolvedAgentUrl },
55
79
  ...this.operatorAuthorizationValidUntil !== void 0 && {
56
80
  operatorAuthorizationValidUntil: this.operatorAuthorizationValidUntil
57
81
  }
@@ -59,23 +83,11 @@ class ResolvedAgentJwksResolver {
59
83
  }
60
84
  async refresh() {
61
85
  if (this.inFlight) return this.inFlight;
86
+ this.lastDiscoveryAttemptAt = this.now();
62
87
  this.inFlight = (async () => {
63
88
  const resolution = await this.resolveAgentFn(this.agentUrl, {
64
- protocol: this.protocol,
65
- ...this.options.fetchCapabilities && { fetchCapabilities: this.options.fetchCapabilities },
66
- ...this.options.allowPrivateIp !== void 0 && { allowPrivateIp: this.options.allowPrivateIp },
67
- ...this.options.bodyCaps && { bodyCaps: this.options.bodyCaps },
68
- ...this.options.timeoutMs !== void 0 && { timeoutMs: this.options.timeoutMs },
69
- ...this.options.now && { now: this.options.now },
70
- ...this.options.requiredOperatorBrand !== void 0 && {
71
- requiredOperatorBrand: this.options.requiredOperatorBrand
72
- },
73
- ...this.options.requiredOperatorScope !== void 0 && {
74
- requiredOperatorScope: this.options.requiredOperatorScope
75
- },
76
- ...this.options.requiredOperatorCountry !== void 0 && {
77
- requiredOperatorCountry: this.options.requiredOperatorCountry
78
- }
89
+ ...this.options,
90
+ protocol: this.protocol
79
91
  });
80
92
  const refreshedAt = this.now();
81
93
  if (resolution.operatorAuthorizationValidUntil !== void 0 && refreshedAt >= resolution.operatorAuthorizationValidUntil) {
@@ -89,13 +101,28 @@ class ResolvedAgentJwksResolver {
89
101
  const next = /* @__PURE__ */ new Map();
90
102
  for (const candidate of resolution.jwks.keys) {
91
103
  if (candidate && typeof candidate === "object" && typeof candidate.kid === "string") {
104
+ if (next.has(candidate.kid))
105
+ throw new AgentResolverError(
106
+ "request_signature_key_unknown",
107
+ "JWKS has duplicate key identifiers",
108
+ { agent_url: this.agentUrl },
109
+ ["agent_url"]
110
+ );
92
111
  next.set(candidate.kid, candidate);
93
112
  }
94
113
  }
95
114
  this.keys = next;
115
+ this.canonicalAgentUrl = resolution.agentUrl;
116
+ this.operatorRecord = resolution.brandJson === void 0 ? void 0 : { url: resolution.brandJsonUrl, document: resolution.brandJson };
117
+ this.legacyWebhookFallback = resolution.legacyWebhookFallback;
96
118
  this.operatorAuthorizationValidUntil = resolution.operatorAuthorizationValidUntil;
97
119
  this.expiresAt = Math.min(
98
- refreshedAt + this.cacheTtlSeconds,
120
+ refreshedAt + Math.min(
121
+ this.cacheTtlSeconds,
122
+ 1800,
123
+ Math.max(30, cacheLifetime(resolution.brandJsonCacheControl)),
124
+ Math.max(60, cacheLifetime(resolution.jwksCacheControl))
125
+ ),
99
126
  resolution.operatorAuthorizationValidUntil ?? Number.POSITIVE_INFINITY
100
127
  );
101
128
  })().finally(() => {
@@ -104,6 +131,12 @@ class ResolvedAgentJwksResolver {
104
131
  return this.inFlight;
105
132
  }
106
133
  }
134
+ function cacheLifetime(cacheControl) {
135
+ if (!cacheControl) return Infinity;
136
+ if (/\bno-cache\b|\bno-store\b/i.test(cacheControl)) return 0;
137
+ const match = /(?:^|,)\s*max-age\s*=\s*"?(\d+)/i.exec(cacheControl);
138
+ return match ? Number(match[1]) : Infinity;
139
+ }
107
140
  export {
108
141
  ResolvedAgentJwksResolver
109
142
  };
@@ -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
  };