@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
@@ -1,38 +1,59 @@
1
1
  import { ssrfSafeFetch } from "../net/index.mjs";
2
+ import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
2
3
  const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
3
4
  const DEFAULT_MAX_AGE_SECONDS = 3600;
4
5
  class HttpsJwksResolver {
5
6
  url;
6
7
  minCooldown;
7
8
  maxAge;
9
+ minCacheAge;
10
+ failClosed;
8
11
  allowPrivateIp;
9
12
  lookup;
10
13
  now;
11
14
  cache;
12
15
  inFlight;
16
+ lastAttemptAt = Number.NEGATIVE_INFINITY;
13
17
  constructor(url, options = {}) {
14
18
  this.url = url;
15
19
  this.minCooldown = options.minCooldownSeconds ?? DEFAULT_MIN_COOLDOWN_SECONDS;
16
20
  this.maxAge = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
21
+ this.minCacheAge = Math.min(options.minCacheAgeSeconds ?? 0, this.maxAge);
22
+ if (!Number.isFinite(this.maxAge) || this.maxAge < 0) {
23
+ throw new TypeError("maxAgeSeconds must be a finite non-negative number");
24
+ }
25
+ if (!Number.isFinite(this.minCooldown) || this.minCooldown < 0) {
26
+ throw new TypeError("minCooldownSeconds must be a finite non-negative number");
27
+ }
28
+ if (!Number.isFinite(options.minCacheAgeSeconds ?? 0) || (options.minCacheAgeSeconds ?? 0) < 0) {
29
+ throw new TypeError("minCacheAgeSeconds must be a finite non-negative number");
30
+ }
31
+ this.failClosed = options.failClosed === true;
17
32
  this.allowPrivateIp = options.allowPrivateIp ?? false;
18
33
  this.lookup = options.lookup;
19
34
  this.now = options.now ?? (() => Math.floor(Date.now() / 1e3));
20
35
  }
21
36
  async resolve(keyid) {
22
37
  if (!this.cache) {
38
+ if (!this.inFlight && this.now() - this.lastAttemptAt < this.minCooldown) {
39
+ throw new Error("JWKS lookup is in its retry cooldown");
40
+ }
23
41
  await this.refresh();
24
42
  } else if (this.cache.keys.has(keyid)) {
25
- if (this.now() <= this.cache.expiresAt) {
43
+ if (this.now() < this.cache.expiresAt) {
26
44
  return this.cache.keys.get(keyid) ?? null;
27
45
  }
28
- if (this.now() - this.cache.fetchedAt >= this.minCooldown) {
29
- await this.refresh().catch(() => {
46
+ if (this.now() - this.lastAttemptAt >= this.minCooldown) {
47
+ await this.refresh().catch((error) => {
48
+ if (this.failClosed) throw error;
30
49
  });
31
50
  }
32
- } else if (this.now() - this.cache.fetchedAt >= this.minCooldown) {
33
- await this.refresh().catch(() => {
51
+ } else if (this.now() - this.lastAttemptAt >= this.minCooldown) {
52
+ await this.refresh().catch((error) => {
53
+ if (this.failClosed) throw error;
34
54
  });
35
55
  }
56
+ if (this.failClosed && this.cache && this.now() >= this.cache.expiresAt) throw new Error("JWKS snapshot expired");
36
57
  return this.cache?.keys.get(keyid) ?? null;
37
58
  }
38
59
  /**
@@ -49,6 +70,7 @@ class HttpsJwksResolver {
49
70
  await this.inFlight;
50
71
  return;
51
72
  }
73
+ this.lastAttemptAt = this.now();
52
74
  this.inFlight = this.doRefresh().finally(() => {
53
75
  this.inFlight = void 0;
54
76
  });
@@ -80,7 +102,7 @@ class HttpsJwksResolver {
80
102
  const text = Buffer.from(res.body).toString("utf8");
81
103
  let parsed;
82
104
  try {
83
- parsed = JSON.parse(text);
105
+ parsed = parseStrictJson(text);
84
106
  } catch {
85
107
  throw new Error(`JWKS fetch ${this.url} returned non-JSON body`);
86
108
  }
@@ -95,6 +117,7 @@ class HttpsJwksResolver {
95
117
  for (const entry of keysField) {
96
118
  if (entry && typeof entry === "object" && typeof entry.kid === "string") {
97
119
  const jwk = entry;
120
+ if (byKid.has(jwk.kid)) throw new Error("JWKS has duplicate key identifiers");
98
121
  byKid.set(jwk.kid, jwk);
99
122
  }
100
123
  }
@@ -107,11 +130,11 @@ class HttpsJwksResolver {
107
130
  }
108
131
  computeExpiry(headers) {
109
132
  const cacheCtl = headers["cache-control"]?.toLowerCase() ?? "";
110
- if (/\bno-store\b|\bno-cache\b/.test(cacheCtl)) return this.now();
133
+ if (/\bno-store\b|\bno-cache\b/.test(cacheCtl)) return this.now() + this.minCacheAge;
111
134
  const match = /max-age\s*=\s*(\d+)/.exec(cacheCtl);
112
135
  if (match) {
113
136
  const serverMax = Number(match[1]);
114
- if (Number.isFinite(serverMax)) return this.now() + Math.min(serverMax, this.maxAge);
137
+ if (Number.isFinite(serverMax)) return this.now() + Math.max(this.minCacheAge, Math.min(serverMax, this.maxAge));
115
138
  }
116
139
  return this.now() + this.maxAge;
117
140
  }
@@ -1,6 +1,14 @@
1
1
  import type { AdcpJsonWebKey } from './types.mjs';
2
2
  export interface JwksResolution {
3
3
  jwk: AdcpJsonWebKey | null;
4
+ /** Canonical agent identity established by capability-bound discovery. */
5
+ agentUrl?: string;
6
+ /** Exact capability-selected operator document for signed-buyer governance. */
7
+ operatorRecord?: {
8
+ url: string;
9
+ document: unknown;
10
+ };
11
+ legacyWebhookFallback?: boolean;
4
12
  /** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
5
13
  operatorAuthorizationValidUntil?: number;
6
14
  }
@@ -1,6 +1,14 @@
1
1
  import type { AdcpJsonWebKey } from './types';
2
2
  export interface JwksResolution {
3
3
  jwk: AdcpJsonWebKey | null;
4
+ /** Canonical agent identity established by capability-bound discovery. */
5
+ agentUrl?: string;
6
+ /** Exact capability-selected operator document for signed-buyer governance. */
7
+ operatorRecord?: {
8
+ url: string;
9
+ document: unknown;
10
+ };
11
+ legacyWebhookFallback?: boolean;
4
12
  /** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
5
13
  operatorAuthorizationValidUntil?: number;
6
14
  }
@@ -49,7 +49,8 @@ function createExpressVerifier(options) {
49
49
  req.verifiedSigner = {
50
50
  keyid: result.keyid,
51
51
  agent_url: result.agent_url,
52
- verified_at: result.verified_at
52
+ verified_at: result.verified_at,
53
+ ...result.operatorRecord !== void 0 && { operatorRecord: result.operatorRecord }
53
54
  };
54
55
  }
55
56
  next();
@@ -26,7 +26,8 @@ function createExpressVerifier(options) {
26
26
  req.verifiedSigner = {
27
27
  keyid: result.keyid,
28
28
  agent_url: result.agent_url,
29
- verified_at: result.verified_at
29
+ verified_at: result.verified_at,
30
+ ...result.operatorRecord !== void 0 && { operatorRecord: result.operatorRecord }
30
31
  };
31
32
  }
32
33
  next();
@@ -0,0 +1,11 @@
1
+ import type { AdcpJsonWebKey } from './types.mjs';
2
+ export interface PublisherSigningKeyPin {
3
+ /** Publisher identity from the buyer's own record, for local diagnostics. */
4
+ publisher: string;
5
+ /** undefined means no pin; an empty pin accepts no keys. */
6
+ signingKeys?: readonly Record<string, unknown>[];
7
+ /** Fetch adagents.json without its usual cache. Reuse a callback bound to this agent/publisher/tenant across deliveries; refreshes share a 30-second cooldown. Return null only for confirmed pin removal; throw on failure. */
8
+ refresh: () => Promise<readonly Record<string, unknown>[] | null>;
9
+ }
10
+ /** Intersect an already discovered JWKS key with every applicable publisher pin. */
11
+ export declare function assertPublisherPins(jwk: AdcpJsonWebKey, publishers: readonly PublisherSigningKeyPin[], now: () => number): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import type { AdcpJsonWebKey } from './types';
2
+ export interface PublisherSigningKeyPin {
3
+ /** Publisher identity from the buyer's own record, for local diagnostics. */
4
+ publisher: string;
5
+ /** undefined means no pin; an empty pin accepts no keys. */
6
+ signingKeys?: readonly Record<string, unknown>[];
7
+ /** Fetch adagents.json without its usual cache. Reuse a callback bound to this agent/publisher/tenant across deliveries; refreshes share a 30-second cooldown. Return null only for confirmed pin removal; throw on failure. */
8
+ refresh: () => Promise<readonly Record<string, unknown>[] | null>;
9
+ }
10
+ /** Intersect an already discovered JWKS key with every applicable publisher pin. */
11
+ export declare function assertPublisherPins(jwk: AdcpJsonWebKey, publishers: readonly PublisherSigningKeyPin[], now: () => number): Promise<void>;
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var publisher_pins_exports = {};
20
+ __export(publisher_pins_exports, {
21
+ assertPublisherPins: () => assertPublisherPins
22
+ });
23
+ module.exports = __toCommonJS(publisher_pins_exports);
24
+ var import_jose = require("jose");
25
+ var import_errors = require('./agent-resolver/errors.js');
26
+ const refreshes = /* @__PURE__ */ new WeakMap();
27
+ async function refreshPin(publisher, now) {
28
+ let states = refreshes.get(publisher.refresh);
29
+ if (!states) refreshes.set(publisher.refresh, states = /* @__PURE__ */ new Map());
30
+ const checkedAt = now();
31
+ const existing = states.get(publisher.publisher);
32
+ if (existing && (existing.inFlight || checkedAt - existing.attemptedAt < 30) && existing.inputPins !== JSON.stringify(publisher.signingKeys)) {
33
+ throw new Error("Publisher pin changed during the refresh cooldown");
34
+ }
35
+ if (existing?.inFlight) return existing.inFlight;
36
+ if (existing && checkedAt - existing.attemptedAt < 30) {
37
+ if (existing.error !== void 0) throw existing.error;
38
+ return existing.snapshot;
39
+ }
40
+ const state = { attemptedAt: checkedAt, inputPins: JSON.stringify(publisher.signingKeys) };
41
+ if (states.size >= 512) {
42
+ const evictable = [...states].find(([, candidate]) => !candidate.inFlight);
43
+ if (!evictable) throw new Error("Publisher refresh capacity exceeded");
44
+ states.delete(evictable[0]);
45
+ }
46
+ states.set(publisher.publisher, state);
47
+ state.inFlight = Promise.resolve().then(async () => {
48
+ try {
49
+ const snapshot = await publisher.refresh();
50
+ if (snapshot === void 0) throw new TypeError("Publisher refresh must return a confirmed pin or null");
51
+ state.snapshot = snapshot === null ? null : Object.freeze(snapshot.map((key) => Object.freeze({ ...key })));
52
+ return state.snapshot;
53
+ } catch (error) {
54
+ state.error = error ?? new Error("Publisher refresh failed");
55
+ throw state.error;
56
+ } finally {
57
+ state.inFlight = void 0;
58
+ }
59
+ });
60
+ return state.inFlight;
61
+ }
62
+ async function thumbprint(key) {
63
+ const members = key.kty === "OKP" ? ["crv", "x"] : key.kty === "EC" ? ["crv", "x", "y"] : key.kty === "RSA" ? ["n", "e"] : void 0;
64
+ if (!members || members.some((name) => typeof key[name] !== "string" || key[name] === "")) return void 0;
65
+ try {
66
+ return await (0, import_jose.calculateJwkThumbprint)(key, "sha256");
67
+ } catch {
68
+ return void 0;
69
+ }
70
+ }
71
+ async function matchingValidity(keyThumbprint, pins) {
72
+ if (pins === void 0) return [Infinity];
73
+ const validity = [];
74
+ for (const pin of pins) {
75
+ if (!pin || typeof pin !== "object" || await thumbprint(pin) !== keyThumbprint) continue;
76
+ if (pin.revoked_at === void 0) validity.push(Infinity);
77
+ else if (typeof pin.revoked_at === "string") {
78
+ const revoked = Date.parse(pin.revoked_at) / 1e3;
79
+ validity.push(Number.isFinite(revoked) ? revoked : -Infinity);
80
+ } else validity.push(-Infinity);
81
+ }
82
+ return validity.length ? [Math.min(...validity)] : [];
83
+ }
84
+ async function matches(keyThumbprint, pins, now) {
85
+ const validity = await matchingValidity(keyThumbprint, pins);
86
+ return validity.some((until) => now() < until);
87
+ }
88
+ async function assertPublisherPins(jwk, publishers, now) {
89
+ const fingerprint = await thumbprint(jwk);
90
+ if (!fingerprint) throw new import_errors.AgentResolverError("request_signature_key_unknown", "Invalid public key", {});
91
+ const applicable = [];
92
+ const supplied = [];
93
+ for (const publisher of publishers) {
94
+ const inputPins = JSON.stringify(publisher.signingKeys);
95
+ supplied.push(inputPins);
96
+ const recent = refreshes.get(publisher.refresh)?.get(publisher.publisher);
97
+ let selected = recent?.snapshot !== void 0 && recent.inputPins === inputPins && now() - recent.attemptedAt < 30 ? recent.snapshot ?? void 0 : publisher.signingKeys;
98
+ if (!await matches(fingerprint, selected, now)) {
99
+ const refreshed = await refreshPin(publisher, now);
100
+ selected = refreshed ?? void 0;
101
+ if (!await matches(fingerprint, selected, now))
102
+ throw new import_errors.AgentResolverError(
103
+ "request_signature_key_unknown",
104
+ "Key is outside the applicable publisher pin",
105
+ {}
106
+ );
107
+ }
108
+ applicable.push(selected);
109
+ }
110
+ const validity = await Promise.all(applicable.map((selected) => matchingValidity(fingerprint, selected)));
111
+ const checkedInputs = publishers.map((publisher) => JSON.stringify(publisher.signingKeys));
112
+ for (let i = 0; i < publishers.length; i++) {
113
+ if (supplied[i] !== checkedInputs[i]) {
114
+ validity.push(await matchingValidity(fingerprint, publishers[i].signingKeys));
115
+ }
116
+ }
117
+ const checkedAt = now();
118
+ if (publishers.some((publisher, i) => JSON.stringify(publisher.signingKeys) !== checkedInputs[i]) || validity.some((bounds) => !bounds.some((until) => checkedAt < until))) {
119
+ throw new import_errors.AgentResolverError("request_signature_key_unknown", "Key is outside the applicable publisher pin", {});
120
+ }
121
+ }
122
+ // Annotate the CommonJS export names for ESM import in node:
123
+ 0 && (module.exports = {
124
+ assertPublisherPins
125
+ });
@@ -0,0 +1,101 @@
1
+ import { calculateJwkThumbprint } from "jose";
2
+ import { AgentResolverError } from "./agent-resolver/errors.mjs";
3
+ const refreshes = /* @__PURE__ */ new WeakMap();
4
+ async function refreshPin(publisher, now) {
5
+ let states = refreshes.get(publisher.refresh);
6
+ if (!states) refreshes.set(publisher.refresh, states = /* @__PURE__ */ new Map());
7
+ const checkedAt = now();
8
+ const existing = states.get(publisher.publisher);
9
+ if (existing && (existing.inFlight || checkedAt - existing.attemptedAt < 30) && existing.inputPins !== JSON.stringify(publisher.signingKeys)) {
10
+ throw new Error("Publisher pin changed during the refresh cooldown");
11
+ }
12
+ if (existing?.inFlight) return existing.inFlight;
13
+ if (existing && checkedAt - existing.attemptedAt < 30) {
14
+ if (existing.error !== void 0) throw existing.error;
15
+ return existing.snapshot;
16
+ }
17
+ const state = { attemptedAt: checkedAt, inputPins: JSON.stringify(publisher.signingKeys) };
18
+ if (states.size >= 512) {
19
+ const evictable = [...states].find(([, candidate]) => !candidate.inFlight);
20
+ if (!evictable) throw new Error("Publisher refresh capacity exceeded");
21
+ states.delete(evictable[0]);
22
+ }
23
+ states.set(publisher.publisher, state);
24
+ state.inFlight = Promise.resolve().then(async () => {
25
+ try {
26
+ const snapshot = await publisher.refresh();
27
+ if (snapshot === void 0) throw new TypeError("Publisher refresh must return a confirmed pin or null");
28
+ state.snapshot = snapshot === null ? null : Object.freeze(snapshot.map((key) => Object.freeze({ ...key })));
29
+ return state.snapshot;
30
+ } catch (error) {
31
+ state.error = error ?? new Error("Publisher refresh failed");
32
+ throw state.error;
33
+ } finally {
34
+ state.inFlight = void 0;
35
+ }
36
+ });
37
+ return state.inFlight;
38
+ }
39
+ async function thumbprint(key) {
40
+ const members = key.kty === "OKP" ? ["crv", "x"] : key.kty === "EC" ? ["crv", "x", "y"] : key.kty === "RSA" ? ["n", "e"] : void 0;
41
+ if (!members || members.some((name) => typeof key[name] !== "string" || key[name] === "")) return void 0;
42
+ try {
43
+ return await calculateJwkThumbprint(key, "sha256");
44
+ } catch {
45
+ return void 0;
46
+ }
47
+ }
48
+ async function matchingValidity(keyThumbprint, pins) {
49
+ if (pins === void 0) return [Infinity];
50
+ const validity = [];
51
+ for (const pin of pins) {
52
+ if (!pin || typeof pin !== "object" || await thumbprint(pin) !== keyThumbprint) continue;
53
+ if (pin.revoked_at === void 0) validity.push(Infinity);
54
+ else if (typeof pin.revoked_at === "string") {
55
+ const revoked = Date.parse(pin.revoked_at) / 1e3;
56
+ validity.push(Number.isFinite(revoked) ? revoked : -Infinity);
57
+ } else validity.push(-Infinity);
58
+ }
59
+ return validity.length ? [Math.min(...validity)] : [];
60
+ }
61
+ async function matches(keyThumbprint, pins, now) {
62
+ const validity = await matchingValidity(keyThumbprint, pins);
63
+ return validity.some((until) => now() < until);
64
+ }
65
+ async function assertPublisherPins(jwk, publishers, now) {
66
+ const fingerprint = await thumbprint(jwk);
67
+ if (!fingerprint) throw new AgentResolverError("request_signature_key_unknown", "Invalid public key", {});
68
+ const applicable = [];
69
+ const supplied = [];
70
+ for (const publisher of publishers) {
71
+ const inputPins = JSON.stringify(publisher.signingKeys);
72
+ supplied.push(inputPins);
73
+ const recent = refreshes.get(publisher.refresh)?.get(publisher.publisher);
74
+ let selected = recent?.snapshot !== void 0 && recent.inputPins === inputPins && now() - recent.attemptedAt < 30 ? recent.snapshot ?? void 0 : publisher.signingKeys;
75
+ if (!await matches(fingerprint, selected, now)) {
76
+ const refreshed = await refreshPin(publisher, now);
77
+ selected = refreshed ?? void 0;
78
+ if (!await matches(fingerprint, selected, now))
79
+ throw new AgentResolverError(
80
+ "request_signature_key_unknown",
81
+ "Key is outside the applicable publisher pin",
82
+ {}
83
+ );
84
+ }
85
+ applicable.push(selected);
86
+ }
87
+ const validity = await Promise.all(applicable.map((selected) => matchingValidity(fingerprint, selected)));
88
+ const checkedInputs = publishers.map((publisher) => JSON.stringify(publisher.signingKeys));
89
+ for (let i = 0; i < publishers.length; i++) {
90
+ if (supplied[i] !== checkedInputs[i]) {
91
+ validity.push(await matchingValidity(fingerprint, publishers[i].signingKeys));
92
+ }
93
+ }
94
+ const checkedAt = now();
95
+ if (publishers.some((publisher, i) => JSON.stringify(publisher.signingKeys) !== checkedInputs[i]) || validity.some((bounds) => !bounds.some((until) => checkedAt < until))) {
96
+ throw new AgentResolverError("request_signature_key_unknown", "Key is outside the applicable publisher pin", {});
97
+ }
98
+ }
99
+ export {
100
+ assertPublisherPins
101
+ };
@@ -30,3 +30,4 @@ export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions
30
30
  export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer.mjs';
31
31
  export { signResponseAsync } from './signer-async.mjs';
32
32
  export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, type DelegatedOperatorAuthorizationContext, type AgentResolverErrorCode, type AgentResolverErrorDetail, type AgentEntry, type AgentJwksResult, type CapabilitiesWithBrandJsonUrl, type CreateAgentJwksSetOptions, type FetchCapabilitiesFn, type GetAgentJwksOptions, type IdentityKeyOriginPurpose, type IdentityKeyOrigins, type IdentityPosture, type ResolveAgentOptions, type ResolvedAgentJwksResolverOptions, type TraceStep, } from './agent-resolver/index.mjs';
33
+ export type { PublisherSigningKeyPin } from './publisher-pins.mjs';
@@ -30,3 +30,4 @@ export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions
30
30
  export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer';
31
31
  export { signResponseAsync } from './signer-async';
32
32
  export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, type DelegatedOperatorAuthorizationContext, type AgentResolverErrorCode, type AgentResolverErrorDetail, type AgentEntry, type AgentJwksResult, type CapabilitiesWithBrandJsonUrl, type CreateAgentJwksSetOptions, type FetchCapabilitiesFn, type GetAgentJwksOptions, type IdentityKeyOriginPurpose, type IdentityKeyOrigins, type IdentityPosture, type ResolveAgentOptions, type ResolvedAgentJwksResolverOptions, type TraceStep, } from './agent-resolver';
33
+ export type { PublisherSigningKeyPin } from './publisher-pins';
@@ -60,6 +60,11 @@ export interface RevocationSnapshot {
60
60
  export interface VerifiedSigner {
61
61
  keyid: string;
62
62
  agent_url?: string;
63
+ /** Exact operator record selected during verified agent discovery. */
64
+ operatorRecord?: {
65
+ url: string;
66
+ document: unknown;
67
+ };
63
68
  verified_at: number;
64
69
  }
65
70
  /**
@@ -60,6 +60,11 @@ export interface RevocationSnapshot {
60
60
  export interface VerifiedSigner {
61
61
  keyid: string;
62
62
  agent_url?: string;
63
+ /** Exact operator record selected during verified agent discovery. */
64
+ operatorRecord?: {
65
+ url: string;
66
+ document: unknown;
67
+ };
63
68
  verified_at: number;
64
69
  }
65
70
  /**
@@ -32,7 +32,8 @@ var import_crypto = require('./crypto.js');
32
32
  var import_webhook_auth_detection = require('./webhook-auth-detection.js');
33
33
  var import_types = require('./types.js');
34
34
  async function verifyRequestSignature(request, options) {
35
- const now = options.now ? options.now() : Math.floor(Date.now() / 1e3);
35
+ const currentTime = options.now ?? (() => Math.floor(Date.now() / 1e3));
36
+ const now = currentTime();
36
37
  const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? (0, import_content_digest.requestSigningEncodingForVersion)(options.adcpVersion) : void 0;
37
38
  const sigInputHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature-Input");
38
39
  const sigHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature");
@@ -119,14 +120,38 @@ async function verifyRequestSignature(request, options) {
119
120
  const effectiveCapability = pinnedBinaryEncoding === "rfc8941-base64" ? { ...options.capability, covers_content_digest: "required" } : options.capability;
120
121
  validateCoveredComponents(parsedInput.components, effectiveCapability, request);
121
122
  let jwk;
123
+ let resolvedAgentUrl;
124
+ let authorizationValidUntil;
125
+ let operatorRecord;
122
126
  try {
123
- jwk = await options.jwks.resolve(parsedInput.params.keyid);
127
+ if (options.jwks.resolveWithMetadata) {
128
+ const resolution = await options.jwks.resolveWithMetadata(parsedInput.params.keyid);
129
+ if (resolution.legacyWebhookFallback)
130
+ throw new import_errors2.AgentResolverError(
131
+ "request_signature_brand_json_url_missing",
132
+ "Legacy discovery is limited to webhook verification",
133
+ {}
134
+ );
135
+ jwk = resolution.jwk;
136
+ resolvedAgentUrl = resolution.agentUrl;
137
+ operatorRecord = resolution.operatorRecord;
138
+ authorizationValidUntil = resolution.operatorAuthorizationValidUntil;
139
+ } else {
140
+ jwk = await options.jwks.resolve(parsedInput.params.keyid);
141
+ }
124
142
  } catch (err) {
125
143
  if (err instanceof import_errors2.AgentResolverError) {
126
- throw new import_errors.RequestSignatureError(err.code, 7, err.message, err.detail);
144
+ throw new import_errors.RequestSignatureError(err.code, 7, err.message, err.detail, err.recovery);
127
145
  }
128
146
  throw err;
129
147
  }
148
+ if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
149
+ throw new import_errors.RequestSignatureError(
150
+ "request_signature_brand_origin_mismatch",
151
+ 7,
152
+ "Delegated operator authorization expired before verification"
153
+ );
154
+ }
130
155
  if (!jwk) {
131
156
  throw new import_errors.RequestSignatureError(
132
157
  "request_signature_key_unknown",
@@ -222,6 +247,13 @@ async function verifyRequestSignature(request, options) {
222
247
  throw err;
223
248
  }
224
249
  }
250
+ if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
251
+ throw new import_errors.RequestSignatureError(
252
+ "request_signature_brand_origin_mismatch",
253
+ 7,
254
+ "Delegated operator authorization expired during verification"
255
+ );
256
+ }
225
257
  const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS;
226
258
  const ttl = Math.max(remaining, import_types.MAX_SIGNATURE_WINDOW_SECONDS + import_types.CLOCK_SKEW_TOLERANCE_SECONDS);
227
259
  const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
@@ -239,8 +271,21 @@ async function verifyRequestSignature(request, options) {
239
271
  `Per-keyid replay cache cap exceeded for keyid=${jwk.kid}`
240
272
  );
241
273
  }
242
- const agent_url = options.agentUrlForKeyid?.(jwk.kid);
243
- return { status: "verified", keyid: jwk.kid, agent_url, verified_at: now };
274
+ if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
275
+ throw new import_errors.RequestSignatureError(
276
+ "request_signature_brand_origin_mismatch",
277
+ 7,
278
+ "Delegated operator authorization expired before acceptance"
279
+ );
280
+ }
281
+ const agent_url = resolvedAgentUrl ?? options.agentUrlForKeyid?.(jwk.kid);
282
+ return {
283
+ status: "verified",
284
+ keyid: jwk.kid,
285
+ agent_url,
286
+ verified_at: now,
287
+ ...operatorRecord !== void 0 && { operatorRecord }
288
+ };
244
289
  }
245
290
  function signatureEncodingCandidates(pinned, digestPolicy) {
246
291
  if (!pinned) return ["legacy-base64url", "rfc8941-base64"];
@@ -25,7 +25,8 @@ import {
25
25
  REQUEST_SIGNING_TAG
26
26
  } from "./types.mjs";
27
27
  async function verifyRequestSignature(request, options) {
28
- const now = options.now ? options.now() : Math.floor(Date.now() / 1e3);
28
+ const currentTime = options.now ?? (() => Math.floor(Date.now() / 1e3));
29
+ const now = currentTime();
29
30
  const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? requestSigningEncodingForVersion(options.adcpVersion) : void 0;
30
31
  const sigInputHeader = getHeaderValue(request.headers, "Signature-Input");
31
32
  const sigHeader = getHeaderValue(request.headers, "Signature");
@@ -112,14 +113,38 @@ async function verifyRequestSignature(request, options) {
112
113
  const effectiveCapability = pinnedBinaryEncoding === "rfc8941-base64" ? { ...options.capability, covers_content_digest: "required" } : options.capability;
113
114
  validateCoveredComponents(parsedInput.components, effectiveCapability, request);
114
115
  let jwk;
116
+ let resolvedAgentUrl;
117
+ let authorizationValidUntil;
118
+ let operatorRecord;
115
119
  try {
116
- jwk = await options.jwks.resolve(parsedInput.params.keyid);
120
+ if (options.jwks.resolveWithMetadata) {
121
+ const resolution = await options.jwks.resolveWithMetadata(parsedInput.params.keyid);
122
+ if (resolution.legacyWebhookFallback)
123
+ throw new AgentResolverError(
124
+ "request_signature_brand_json_url_missing",
125
+ "Legacy discovery is limited to webhook verification",
126
+ {}
127
+ );
128
+ jwk = resolution.jwk;
129
+ resolvedAgentUrl = resolution.agentUrl;
130
+ operatorRecord = resolution.operatorRecord;
131
+ authorizationValidUntil = resolution.operatorAuthorizationValidUntil;
132
+ } else {
133
+ jwk = await options.jwks.resolve(parsedInput.params.keyid);
134
+ }
117
135
  } catch (err) {
118
136
  if (err instanceof AgentResolverError) {
119
- throw new RequestSignatureError(err.code, 7, err.message, err.detail);
137
+ throw new RequestSignatureError(err.code, 7, err.message, err.detail, err.recovery);
120
138
  }
121
139
  throw err;
122
140
  }
141
+ if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
142
+ throw new RequestSignatureError(
143
+ "request_signature_brand_origin_mismatch",
144
+ 7,
145
+ "Delegated operator authorization expired before verification"
146
+ );
147
+ }
123
148
  if (!jwk) {
124
149
  throw new RequestSignatureError(
125
150
  "request_signature_key_unknown",
@@ -215,6 +240,13 @@ async function verifyRequestSignature(request, options) {
215
240
  throw err;
216
241
  }
217
242
  }
243
+ if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
244
+ throw new RequestSignatureError(
245
+ "request_signature_brand_origin_mismatch",
246
+ 7,
247
+ "Delegated operator authorization expired during verification"
248
+ );
249
+ }
218
250
  const remaining = parsedInput.params.expires - now + CLOCK_SKEW_TOLERANCE_SECONDS;
219
251
  const ttl = Math.max(remaining, MAX_SIGNATURE_WINDOW_SECONDS + CLOCK_SKEW_TOLERANCE_SECONDS);
220
252
  const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
@@ -232,8 +264,21 @@ async function verifyRequestSignature(request, options) {
232
264
  `Per-keyid replay cache cap exceeded for keyid=${jwk.kid}`
233
265
  );
234
266
  }
235
- const agent_url = options.agentUrlForKeyid?.(jwk.kid);
236
- return { status: "verified", keyid: jwk.kid, agent_url, verified_at: now };
267
+ if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
268
+ throw new RequestSignatureError(
269
+ "request_signature_brand_origin_mismatch",
270
+ 7,
271
+ "Delegated operator authorization expired before acceptance"
272
+ );
273
+ }
274
+ const agent_url = resolvedAgentUrl ?? options.agentUrlForKeyid?.(jwk.kid);
275
+ return {
276
+ status: "verified",
277
+ keyid: jwk.kid,
278
+ agent_url,
279
+ verified_at: now,
280
+ ...operatorRecord !== void 0 && { operatorRecord }
281
+ };
237
282
  }
238
283
  function signatureEncodingCandidates(pinned, digestPolicy) {
239
284
  if (!pinned) return ["legacy-base64url", "rfc8941-base64"];
@@ -14,8 +14,8 @@
14
14
  * `content-type`, and `content-digest` — `content-digest` is unconditional
15
15
  * for webhooks (vs policy-driven on requests) because every webhook
16
16
  * carries a JSON body.
17
- * - JWKS resolved via the publisher's `brand.json` `agents[]` `jwks_uri`,
18
- * not the request-path capability document.
17
+ * - JWKS resolved via the capabilities-selected operator brand.json;
18
+ * publisher pins narrow its keys for applicable inventory.
19
19
  *
20
20
  * Checklist steps below mirror the 14-step shape in
21
21
  * `docs/building/implementation/security.mdx#verifier-checklist-for-webhooks`
@@ -23,6 +23,7 @@
23
23
  * match the security doc.
24
24
  */
25
25
  import type { JwksResolver } from './jwks.mjs';
26
+ import { type PublisherSigningKeyPin } from './publisher-pins.mjs';
26
27
  import { type ReplayStore } from './replay.mjs';
27
28
  import { type RevocationStore } from './revocation.mjs';
28
29
  export declare const WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
@@ -42,6 +43,10 @@ export interface WebhookRequestLike {
42
43
  export declare const WEBHOOK_MANDATORY_COMPONENTS: ReadonlyArray<string>;
43
44
  export interface VerifyWebhookOptions {
44
45
  jwks: JwksResolver;
46
+ /** Applicable publishers from the verifier's own media-buy record. */
47
+ publisherPins?: readonly PublisherSigningKeyPin[];
48
+ /** Log the underlying request_signature_* discovery cause locally. */
49
+ onKeyResolutionError?: (error: unknown) => void;
45
50
  replayStore: ReplayStore;
46
51
  revocationStore: RevocationStore;
47
52
  /** Now in seconds since epoch. Defaults to `Date.now() / 1000`. */