@adcp/sdk 14.0.0-beta.14 → 14.0.0-beta.15

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 (127) hide show
  1. package/dist/lib/auth/oauth/authorization-required.d.mts +25 -11
  2. package/dist/lib/auth/oauth/authorization-required.d.ts +25 -11
  3. package/dist/lib/auth/oauth/authorization-required.js +18 -4
  4. package/dist/lib/auth/oauth/authorization-required.mjs +17 -4
  5. package/dist/lib/auth/oauth/index.d.mts +1 -1
  6. package/dist/lib/auth/oauth/index.d.ts +1 -1
  7. package/dist/lib/auth/oauth/index.js +2 -0
  8. package/dist/lib/auth/oauth/index.mjs +2 -0
  9. package/dist/lib/auth/oauth/resource-url.d.mts +8 -0
  10. package/dist/lib/auth/oauth/resource-url.d.ts +8 -0
  11. package/dist/lib/auth/oauth/resource-url.js +42 -0
  12. package/dist/lib/auth/oauth/resource-url.mjs +40 -0
  13. package/dist/lib/core/ConversationTypes.d.mts +10 -0
  14. package/dist/lib/core/ConversationTypes.d.ts +10 -0
  15. package/dist/lib/core/SingleAgentClient.d.mts +5 -2
  16. package/dist/lib/core/SingleAgentClient.d.ts +5 -2
  17. package/dist/lib/core/SingleAgentClient.js +133 -28
  18. package/dist/lib/core/SingleAgentClient.mjs +138 -29
  19. package/dist/lib/core/TaskExecutor.d.mts +1 -0
  20. package/dist/lib/core/TaskExecutor.d.ts +1 -0
  21. package/dist/lib/core/TaskExecutor.js +7 -1
  22. package/dist/lib/core/TaskExecutor.mjs +7 -1
  23. package/dist/lib/core/webhook-registration.d.mts +8 -1
  24. package/dist/lib/core/webhook-registration.d.ts +8 -1
  25. package/dist/lib/core/webhook-registration.js +65 -5
  26. package/dist/lib/core/webhook-registration.mjs +61 -4
  27. package/dist/lib/errors/index.d.mts +9 -0
  28. package/dist/lib/errors/index.d.ts +9 -0
  29. package/dist/lib/errors/index.js +33 -0
  30. package/dist/lib/errors/index.mjs +32 -0
  31. package/dist/lib/index.d.mts +3 -2
  32. package/dist/lib/index.d.ts +3 -2
  33. package/dist/lib/index.js +4 -0
  34. package/dist/lib/index.mjs +4 -0
  35. package/dist/lib/media-buy/compatibility.js +14 -2
  36. package/dist/lib/media-buy/compatibility.mjs +14 -2
  37. package/dist/lib/protocols/index.js +23 -7
  38. package/dist/lib/protocols/index.mjs +24 -8
  39. package/dist/lib/protocols/mcp.js +8 -6
  40. package/dist/lib/protocols/mcp.mjs +9 -7
  41. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  42. package/dist/lib/server/decisioning/async-outcome.d.mts +11 -0
  43. package/dist/lib/server/decisioning/async-outcome.d.ts +11 -0
  44. package/dist/lib/server/decisioning/context.d.mts +11 -1
  45. package/dist/lib/server/decisioning/context.d.ts +11 -1
  46. package/dist/lib/server/decisioning/index.d.mts +3 -2
  47. package/dist/lib/server/decisioning/index.d.ts +3 -2
  48. package/dist/lib/server/decisioning/index.js +9 -0
  49. package/dist/lib/server/decisioning/index.mjs +10 -0
  50. package/dist/lib/server/decisioning/runtime/from-platform.d.mts +5 -2
  51. package/dist/lib/server/decisioning/runtime/from-platform.d.ts +5 -2
  52. package/dist/lib/server/decisioning/runtime/from-platform.js +40 -8
  53. package/dist/lib/server/decisioning/runtime/from-platform.mjs +40 -8
  54. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.mts +8 -1
  55. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts +8 -1
  56. package/dist/lib/server/decisioning/runtime/postgres-task-registry.js +10 -1
  57. package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +9 -1
  58. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.d.mts +79 -0
  59. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.d.ts +79 -0
  60. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.js +391 -0
  61. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.mjs +369 -0
  62. package/dist/lib/server/decisioning/runtime/task-registry.d.mts +2 -0
  63. package/dist/lib/server/decisioning/runtime/task-registry.d.ts +2 -0
  64. package/dist/lib/server/decisioning/runtime/task-registry.js +2 -1
  65. package/dist/lib/server/decisioning/runtime/task-registry.mjs +2 -1
  66. package/dist/lib/server/decisioning/runtime/to-context.js +15 -11
  67. package/dist/lib/server/decisioning/runtime/to-context.mjs +15 -11
  68. package/dist/lib/server/index.d.mts +2 -2
  69. package/dist/lib/server/index.d.ts +2 -2
  70. package/dist/lib/server/index.js +4 -0
  71. package/dist/lib/server/index.mjs +4 -0
  72. package/dist/lib/server/webhook-delivery/index.d.mts +2 -2
  73. package/dist/lib/server/webhook-delivery/index.d.ts +2 -2
  74. package/dist/lib/server/webhook-delivery/index.js +4 -0
  75. package/dist/lib/server/webhook-delivery/index.mjs +5 -1
  76. package/dist/lib/server/webhook-delivery/pg.d.mts +3 -1
  77. package/dist/lib/server/webhook-delivery/pg.d.ts +3 -1
  78. package/dist/lib/server/webhook-delivery/pg.js +25 -2
  79. package/dist/lib/server/webhook-delivery/pg.mjs +25 -2
  80. package/dist/lib/server/webhook-delivery/recovery.d.mts +44 -0
  81. package/dist/lib/server/webhook-delivery/recovery.d.ts +44 -0
  82. package/dist/lib/server/webhook-delivery/recovery.js +146 -43
  83. package/dist/lib/server/webhook-delivery/recovery.mjs +144 -43
  84. package/dist/lib/signing/agent-resolver/index.d.mts +1 -1
  85. package/dist/lib/signing/agent-resolver/index.d.ts +1 -1
  86. package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +9 -0
  87. package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +9 -0
  88. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +4 -1
  89. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +4 -1
  90. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +17 -4
  91. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +17 -4
  92. package/dist/lib/signing/jwks.d.mts +7 -0
  93. package/dist/lib/signing/jwks.d.ts +7 -0
  94. package/dist/lib/signing/server.d.mts +2 -2
  95. package/dist/lib/signing/server.d.ts +2 -2
  96. package/dist/lib/signing/webhook-verifier.d.mts +6 -0
  97. package/dist/lib/signing/webhook-verifier.d.ts +6 -0
  98. package/dist/lib/signing/webhook-verifier.js +38 -4
  99. package/dist/lib/signing/webhook-verifier.mjs +38 -4
  100. package/dist/lib/testing/client.d.mts +1 -0
  101. package/dist/lib/testing/client.d.ts +1 -0
  102. package/dist/lib/testing/client.js +6 -2
  103. package/dist/lib/testing/client.mjs +6 -2
  104. package/dist/lib/testing/compliance/comply.d.mts +2 -2
  105. package/dist/lib/testing/compliance/comply.d.ts +2 -2
  106. package/dist/lib/testing/compliance/comply.js +41 -31
  107. package/dist/lib/testing/compliance/comply.mjs +44 -21
  108. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.mts +1 -7
  109. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.ts +1 -7
  110. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.js +6 -42
  111. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.mjs +10 -40
  112. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  113. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  114. package/dist/lib/utils/adcp-version-config.d.mts +2 -0
  115. package/dist/lib/utils/adcp-version-config.d.ts +2 -0
  116. package/dist/lib/utils/adcp-version-config.js +5 -0
  117. package/dist/lib/utils/adcp-version-config.mjs +4 -0
  118. package/dist/lib/version.d.mts +3 -3
  119. package/dist/lib/version.d.ts +3 -3
  120. package/dist/lib/version.js +3 -3
  121. package/dist/lib/version.mjs +3 -3
  122. package/docs/llms.txt +3 -1
  123. package/docs/migration-12-to-14.md +6 -5
  124. package/docs/migration-13-to-14.md +48 -7
  125. package/docs/migration-task-registry-scoping.md +164 -11
  126. package/package.json +1 -1
  127. package/skills/build-decisioning-platform/advanced/REFERENCE.md +1 -1
@@ -7,6 +7,12 @@ import {
7
7
  assertWebhookDisposition,
8
8
  isWebhookDeliveryTerminalError
9
9
  } from "./common.mjs";
10
+ class WebhookAuthenticationProtectionError extends Error {
11
+ name = "WebhookAuthenticationProtectionError";
12
+ }
13
+ class WebhookAuthenticationResolutionError extends Error {
14
+ name = "WebhookAuthenticationResolutionError";
15
+ }
10
16
  function memoryWebhookDeliveryRecoveryBackend(options = {}) {
11
17
  const records = /* @__PURE__ */ new Map();
12
18
  const now = options.now ?? Date.now;
@@ -116,13 +122,25 @@ function createWebhookDeliveryRecovery(options) {
116
122
  return {
117
123
  durability: "durable",
118
124
  probe: () => options.backend.probe(),
125
+ async prepare(key, snapshot, prepareOptions) {
126
+ assertDeliveryKey(key);
127
+ const stored = await protectSnapshot(
128
+ snapshot,
129
+ key,
130
+ options.authenticationAdapter,
131
+ prepareOptions?.protectPayloadToken === true
132
+ );
133
+ assertStoredSnapshotSize(stored, maxSnapshotBytes);
134
+ return {
135
+ snapshot: stored,
136
+ snapshotFingerprint: snapshotFingerprint(stored),
137
+ storageFingerprint: canonicalJsonSha256(stored)
138
+ };
139
+ },
119
140
  async checkpoint(key, snapshot) {
120
141
  assertDeliveryKey(key);
121
- const stored = await protectSnapshot(snapshot, key, options.authenticationAdapter);
122
- const storedBytes = Buffer.byteLength(JSON.stringify(stored));
123
- if (storedBytes > maxSnapshotBytes) {
124
- throw new TypeError(`Webhook recovery snapshot exceeds maxSnapshotBytes (${maxSnapshotBytes})`);
125
- }
142
+ const stored = await protectSnapshot(snapshot, key, options.authenticationAdapter, false);
143
+ assertStoredSnapshotSize(stored, maxSnapshotBytes);
126
144
  const fingerprint = snapshotFingerprint(stored);
127
145
  const storageFingerprint = canonicalJsonSha256(stored);
128
146
  const ownerToken = randomUUID();
@@ -294,59 +312,101 @@ function assertPollOutcome(outcome) {
294
312
  throw new TypeError("Webhook recovery deliver() returned an invalid disposition");
295
313
  }
296
314
  }
297
- async function protectSnapshot(snapshot, key, adapter) {
315
+ async function protectSnapshot(snapshot, key, adapter, protectPayloadToken) {
316
+ const payload = structuredClone(snapshot.payload);
317
+ const cleartextPayloadToken = protectPayloadToken && typeof payload.token === "string" ? payload.token : void 0;
318
+ if (cleartextPayloadToken !== void 0) delete payload.token;
298
319
  const base = {
299
320
  url: snapshot.url,
300
- payload: structuredClone(snapshot.payload),
321
+ payload,
301
322
  retries: { ...snapshot.retries }
302
323
  };
303
324
  canonicalJsonSha256(base);
304
- if (snapshot.authentication === null) return { ...base, authentication: { kind: "none" } };
305
- if (!adapter) {
325
+ if ((snapshot.authentication !== null || cleartextPayloadToken !== void 0) && !adapter) {
306
326
  throw new TypeError(
307
- "Durable webhook recovery requires authenticationAdapter when bearer or HMAC authentication is present"
327
+ "Durable webhook recovery requires authenticationAdapter when bearer/HMAC authentication or a payload token is present"
308
328
  );
309
329
  }
310
- const context = authenticationContext(key, base);
311
- const protectedAuth = await adapter.protect(structuredClone(snapshot.authentication), context);
312
- if (!protectedAuth || typeof protectedAuth.fingerprint !== "string" || protectedAuth.fingerprint.length < 16) {
313
- throw new TypeError(
314
- "WebhookAuthenticationAdapter.protect() must return a stable fingerprint of at least 16 characters"
315
- );
316
- }
317
- canonicalJsonSha256(protectedAuth.protectedValue);
318
- return {
330
+ const stored = {
319
331
  ...base,
320
- authentication: {
321
- kind: "protected",
322
- protectedValue: structuredClone(protectedAuth.protectedValue),
323
- fingerprint: protectedAuth.fingerprint
324
- }
332
+ authentication: { kind: "none" }
325
333
  };
334
+ if (snapshot.authentication !== null) {
335
+ const protectedAuth = await protectAuthentication(
336
+ adapter,
337
+ structuredClone(snapshot.authentication),
338
+ authenticationContext(key, base)
339
+ );
340
+ stored.authentication = { kind: "protected", ...protectedAuth };
341
+ }
342
+ if (cleartextPayloadToken !== void 0) {
343
+ const protectedToken = await protectAuthentication(
344
+ adapter,
345
+ { type: "bearer", token: cleartextPayloadToken },
346
+ authenticationContext(key, base, "payload_token")
347
+ );
348
+ stored.payloadToken = { kind: "protected", ...protectedToken };
349
+ }
350
+ return stored;
326
351
  }
327
352
  async function resolveSnapshot(snapshot, key, adapter) {
328
- let authentication = null;
329
- if (snapshot.authentication.kind === "protected") {
330
- if (!adapter) throw new Error("Cannot recover protected webhook authentication without authenticationAdapter");
331
- authentication = await adapter.resolve(
332
- structuredClone(snapshot.authentication.protectedValue),
333
- authenticationContext(key, snapshot)
334
- );
335
- if (authentication === null)
336
- throw new Error("WebhookAuthenticationAdapter.resolve() returned null for protected state");
353
+ try {
354
+ let authentication = null;
355
+ if (snapshot.authentication.kind === "protected") {
356
+ if (!adapter) throw new Error("Cannot recover protected webhook authentication without authenticationAdapter");
357
+ authentication = await adapter.resolve(
358
+ structuredClone(snapshot.authentication.protectedValue),
359
+ authenticationContext(key, snapshot)
360
+ );
361
+ if (authentication === null)
362
+ throw new Error("WebhookAuthenticationAdapter.resolve() returned null for protected state");
363
+ }
364
+ const payload = structuredClone(snapshot.payload);
365
+ if (snapshot.payloadToken) {
366
+ if (!adapter) throw new Error("Cannot recover protected webhook payload token without authenticationAdapter");
367
+ const resolved = await adapter.resolve(
368
+ structuredClone(snapshot.payloadToken.protectedValue),
369
+ authenticationContext(key, snapshot, "payload_token")
370
+ );
371
+ if (resolved?.type !== "bearer" || typeof resolved.token !== "string") {
372
+ throw new Error("WebhookAuthenticationAdapter.resolve() returned an invalid protected payload token");
373
+ }
374
+ payload.token = resolved.token;
375
+ }
376
+ return {
377
+ url: snapshot.url,
378
+ payload,
379
+ authentication,
380
+ retries: { ...snapshot.retries }
381
+ };
382
+ } catch (cause) {
383
+ if (cause instanceof WebhookAuthenticationResolutionError) throw cause;
384
+ throw new WebhookAuthenticationResolutionError("Webhook authentication resolution failed", { cause });
337
385
  }
338
- return {
339
- url: snapshot.url,
340
- payload: structuredClone(snapshot.payload),
341
- authentication,
342
- retries: { ...snapshot.retries }
343
- };
344
386
  }
345
- function authenticationContext(key, snapshot) {
346
- return {
387
+ function authenticationContext(key, snapshot, purpose) {
388
+ const base = {
347
389
  key: { ...key },
348
- url: snapshot.url,
390
+ url: snapshot.url
391
+ };
392
+ if (purpose === void 0) {
393
+ return {
394
+ ...base,
395
+ // This is the exact pre-payload-token context hash. Do not change it:
396
+ // applications may bind KMS AAD to this value for pending snapshots.
397
+ snapshotContextFingerprint: canonicalJsonSha256({
398
+ key,
399
+ url: snapshot.url,
400
+ payload: snapshot.payload,
401
+ retries: snapshot.retries
402
+ })
403
+ };
404
+ }
405
+ return {
406
+ ...base,
407
+ purpose,
349
408
  snapshotContextFingerprint: canonicalJsonSha256({
409
+ purpose,
350
410
  key,
351
411
  url: snapshot.url,
352
412
  payload: snapshot.payload,
@@ -402,15 +462,56 @@ function snapshotFingerprint(snapshot) {
402
462
  url: snapshot.url,
403
463
  payload: snapshot.payload,
404
464
  retries: snapshot.retries,
405
- authentication: snapshot.authentication.kind === "none" ? { kind: "none" } : { kind: "protected", fingerprint: snapshot.authentication.fingerprint }
465
+ authentication: snapshot.authentication.kind === "none" ? { kind: "none" } : { kind: "protected", fingerprint: snapshot.authentication.fingerprint },
466
+ ...snapshot.payloadToken && { payloadToken: { fingerprint: snapshot.payloadToken.fingerprint } }
406
467
  });
407
468
  }
469
+ async function protectAuthentication(adapter, authentication, context) {
470
+ let protectedAuth;
471
+ try {
472
+ protectedAuth = await adapter.protect(authentication, context);
473
+ } catch (cause) {
474
+ throw new WebhookAuthenticationProtectionError("Webhook authentication protection failed", { cause });
475
+ }
476
+ if (!protectedAuth || typeof protectedAuth.fingerprint !== "string" || protectedAuth.fingerprint.length < 16) {
477
+ throw new TypeError(
478
+ "WebhookAuthenticationAdapter.protect() must return a stable fingerprint of at least 16 characters"
479
+ );
480
+ }
481
+ canonicalJsonSha256(protectedAuth.protectedValue);
482
+ const cleartext = authentication.type === "bearer" ? authentication.token : authentication.secret;
483
+ if (protectedValueContainsSecret(protectedAuth.protectedValue, cleartext) || protectedAuth.fingerprint.includes(cleartext)) {
484
+ throw new TypeError("WebhookAuthenticationAdapter.protect() returned durable state containing cleartext");
485
+ }
486
+ return {
487
+ protectedValue: structuredClone(protectedAuth.protectedValue),
488
+ fingerprint: protectedAuth.fingerprint
489
+ };
490
+ }
491
+ function protectedValueContainsSecret(value, cleartext) {
492
+ if (typeof value === "string") return value.includes(cleartext);
493
+ if (Array.isArray(value)) return value.some((item) => protectedValueContainsSecret(item, cleartext));
494
+ if (value && typeof value === "object") {
495
+ return Object.entries(value).some(
496
+ ([key, item]) => key.includes(cleartext) || protectedValueContainsSecret(item, cleartext)
497
+ );
498
+ }
499
+ return false;
500
+ }
501
+ function assertStoredSnapshotSize(snapshot, maxSnapshotBytes) {
502
+ const storedBytes = Buffer.byteLength(JSON.stringify(snapshot));
503
+ if (storedBytes > maxSnapshotBytes) {
504
+ throw new TypeError(`Webhook recovery snapshot exceeds maxSnapshotBytes (${maxSnapshotBytes})`);
505
+ }
506
+ }
408
507
  function assertLeaseMs(value) {
409
508
  if (!Number.isSafeInteger(value) || value < 1e3 || value > 36e5) {
410
509
  throw new TypeError("leaseMs must be an integer from 1000 through 3600000");
411
510
  }
412
511
  }
413
512
  export {
513
+ WebhookAuthenticationProtectionError,
514
+ WebhookAuthenticationResolutionError,
414
515
  createWebhookDeliveryRecovery,
415
516
  memoryWebhookDeliveryRecoveryBackend,
416
517
  pollWebhookDeliveryRecovery
@@ -17,7 +17,7 @@
17
17
  */
18
18
  export { resolveAgent } from './resolve-agent.mjs';
19
19
  export { ResolvedAgentJwksResolver } from './resolved-agent-jwks.mjs';
20
- export type { AgentResolution, AgentProtocol, AuthorizedOperatorScope, FetchCapabilitiesFn, ResolveAgentOptions, TraceStep, } from './resolve-agent.mjs';
20
+ export type { AgentResolution, AgentProtocol, AuthorizedOperatorScope, DelegatedOperatorAuthorizationContext, FetchCapabilitiesFn, ResolveAgentOptions, TraceStep, } from './resolve-agent.mjs';
21
21
  export type { ResolvedAgentJwksResolverOptions } from './resolved-agent-jwks.mjs';
22
22
  export { getAgentJwks, createAgentJwksSet } from './jwks-set.mjs';
23
23
  export type { AgentJwksResult, GetAgentJwksOptions, CreateAgentJwksSetOptions } from './jwks-set.mjs';
@@ -17,7 +17,7 @@
17
17
  */
18
18
  export { resolveAgent } from './resolve-agent';
19
19
  export { ResolvedAgentJwksResolver } from './resolved-agent-jwks';
20
- export type { AgentResolution, AgentProtocol, AuthorizedOperatorScope, FetchCapabilitiesFn, ResolveAgentOptions, TraceStep, } from './resolve-agent';
20
+ export type { AgentResolution, AgentProtocol, AuthorizedOperatorScope, DelegatedOperatorAuthorizationContext, FetchCapabilitiesFn, ResolveAgentOptions, TraceStep, } from './resolve-agent';
21
21
  export type { ResolvedAgentJwksResolverOptions } from './resolved-agent-jwks';
22
22
  export { getAgentJwks, createAgentJwksSet } from './jwks-set';
23
23
  export type { AgentJwksResult, GetAgentJwksOptions, CreateAgentJwksSetOptions } from './jwks-set';
@@ -33,6 +33,15 @@ import { type AgentResolverErrorDetail } from './errors.mjs';
33
33
  import { type AgentEntry } from './select-agent.mjs';
34
34
  export type AgentProtocol = 'mcp' | 'a2a';
35
35
  export type AuthorizedOperatorScope = 'media_buying' | 'creative_generation' | 'rights_clearance' | 'governance' | 'measurement' | 'agent_operations';
36
+ /**
37
+ * Trusted local context selecting one delegated-operator authorization tuple.
38
+ * This is receiver policy, never counterparty-supplied wire data.
39
+ */
40
+ export interface DelegatedOperatorAuthorizationContext {
41
+ brand?: string;
42
+ scope?: AuthorizedOperatorScope;
43
+ country?: string;
44
+ }
36
45
  export interface FetchCapabilitiesFn {
37
46
  (agentUrl: string): Promise<unknown>;
38
47
  }
@@ -33,6 +33,15 @@ import { type AgentResolverErrorDetail } from './errors';
33
33
  import { type AgentEntry } from './select-agent';
34
34
  export type AgentProtocol = 'mcp' | 'a2a';
35
35
  export type AuthorizedOperatorScope = 'media_buying' | 'creative_generation' | 'rights_clearance' | 'governance' | 'measurement' | 'agent_operations';
36
+ /**
37
+ * Trusted local context selecting one delegated-operator authorization tuple.
38
+ * This is receiver policy, never counterparty-supplied wire data.
39
+ */
40
+ export interface DelegatedOperatorAuthorizationContext {
41
+ brand?: string;
42
+ scope?: AuthorizedOperatorScope;
43
+ country?: string;
44
+ }
36
45
  export interface FetchCapabilitiesFn {
37
46
  (agentUrl: string): Promise<unknown>;
38
47
  }
@@ -1,5 +1,5 @@
1
1
  import type { AdcpJsonWebKey } from '../types.mjs';
2
- import type { JwksResolver } from '../jwks.mjs';
2
+ import type { JwksResolution, JwksResolver } from '../jwks.mjs';
3
3
  import { type AgentProtocol, type AgentResolution, type ResolveAgentOptions } from './resolve-agent.mjs';
4
4
  export interface ResolvedAgentJwksResolverOptions extends Pick<ResolveAgentOptions, 'fetchCapabilities' | 'allowPrivateIp' | 'bodyCaps' | 'timeoutMs' | 'now' | 'requiredOperatorBrand' | 'requiredOperatorScope' | 'requiredOperatorCountry'> {
5
5
  /** Positive cache lifetime in seconds. Defaults to 300. */
@@ -20,9 +20,12 @@ export declare class ResolvedAgentJwksResolver implements JwksResolver {
20
20
  private readonly resolveAgentFn;
21
21
  private keys;
22
22
  private expiresAt;
23
+ private operatorAuthorizationValidUntil?;
23
24
  private lastUnknownKidRefresh;
24
25
  private inFlight?;
25
26
  constructor(agentUrl: string, protocol: AgentProtocol, options?: ResolvedAgentJwksResolverOptions);
26
27
  resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
28
+ resolveWithMetadata(keyid: string): Promise<JwksResolution>;
29
+ private resolution;
27
30
  private refresh;
28
31
  }
@@ -1,5 +1,5 @@
1
1
  import type { AdcpJsonWebKey } from '../types';
2
- import type { JwksResolver } from '../jwks';
2
+ import type { JwksResolution, JwksResolver } from '../jwks';
3
3
  import { type AgentProtocol, type AgentResolution, type ResolveAgentOptions } from './resolve-agent';
4
4
  export interface ResolvedAgentJwksResolverOptions extends Pick<ResolveAgentOptions, 'fetchCapabilities' | 'allowPrivateIp' | 'bodyCaps' | 'timeoutMs' | 'now' | 'requiredOperatorBrand' | 'requiredOperatorScope' | 'requiredOperatorCountry'> {
5
5
  /** Positive cache lifetime in seconds. Defaults to 300. */
@@ -20,9 +20,12 @@ export declare class ResolvedAgentJwksResolver implements JwksResolver {
20
20
  private readonly resolveAgentFn;
21
21
  private keys;
22
22
  private expiresAt;
23
+ private operatorAuthorizationValidUntil?;
23
24
  private lastUnknownKidRefresh;
24
25
  private inFlight?;
25
26
  constructor(agentUrl: string, protocol: AgentProtocol, options?: ResolvedAgentJwksResolverOptions);
26
27
  resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
28
+ resolveWithMetadata(keyid: string): Promise<JwksResolution>;
29
+ private resolution;
27
30
  private refresh;
28
31
  }
@@ -48,9 +48,13 @@ class ResolvedAgentJwksResolver {
48
48
  resolveAgentFn;
49
49
  keys = /* @__PURE__ */ new Map();
50
50
  expiresAt = 0;
51
+ operatorAuthorizationValidUntil;
51
52
  lastUnknownKidRefresh = Number.NEGATIVE_INFINITY;
52
53
  inFlight;
53
54
  async resolve(keyid) {
55
+ return (await this.resolveWithMetadata(keyid)).jwk;
56
+ }
57
+ async resolveWithMetadata(keyid) {
54
58
  const now = this.now();
55
59
  let refreshed = false;
56
60
  if (now >= this.expiresAt) {
@@ -58,15 +62,23 @@ class ResolvedAgentJwksResolver {
58
62
  refreshed = true;
59
63
  }
60
64
  const cached = this.keys.get(keyid);
61
- if (cached) return cached;
65
+ if (cached) return this.resolution(cached);
62
66
  if (refreshed) {
63
67
  this.lastUnknownKidRefresh = now;
64
- return null;
68
+ return this.resolution(null);
65
69
  }
66
- if (now - this.lastUnknownKidRefresh < this.unknownKidCooldownSeconds) return null;
70
+ if (now - this.lastUnknownKidRefresh < this.unknownKidCooldownSeconds) return this.resolution(null);
67
71
  await this.refresh();
68
72
  this.lastUnknownKidRefresh = now;
69
- return this.keys.get(keyid) ?? null;
73
+ return this.resolution(this.keys.get(keyid) ?? null);
74
+ }
75
+ resolution(jwk) {
76
+ return {
77
+ jwk,
78
+ ...this.operatorAuthorizationValidUntil !== void 0 && {
79
+ operatorAuthorizationValidUntil: this.operatorAuthorizationValidUntil
80
+ }
81
+ };
70
82
  }
71
83
  async refresh() {
72
84
  if (this.inFlight) return this.inFlight;
@@ -104,6 +116,7 @@ class ResolvedAgentJwksResolver {
104
116
  }
105
117
  }
106
118
  this.keys = next;
119
+ this.operatorAuthorizationValidUntil = resolution.operatorAuthorizationValidUntil;
107
120
  this.expiresAt = Math.min(
108
121
  refreshedAt + this.cacheTtlSeconds,
109
122
  resolution.operatorAuthorizationValidUntil ?? Number.POSITIVE_INFINITY
@@ -25,9 +25,13 @@ class ResolvedAgentJwksResolver {
25
25
  resolveAgentFn;
26
26
  keys = /* @__PURE__ */ new Map();
27
27
  expiresAt = 0;
28
+ operatorAuthorizationValidUntil;
28
29
  lastUnknownKidRefresh = Number.NEGATIVE_INFINITY;
29
30
  inFlight;
30
31
  async resolve(keyid) {
32
+ return (await this.resolveWithMetadata(keyid)).jwk;
33
+ }
34
+ async resolveWithMetadata(keyid) {
31
35
  const now = this.now();
32
36
  let refreshed = false;
33
37
  if (now >= this.expiresAt) {
@@ -35,15 +39,23 @@ class ResolvedAgentJwksResolver {
35
39
  refreshed = true;
36
40
  }
37
41
  const cached = this.keys.get(keyid);
38
- if (cached) return cached;
42
+ if (cached) return this.resolution(cached);
39
43
  if (refreshed) {
40
44
  this.lastUnknownKidRefresh = now;
41
- return null;
45
+ return this.resolution(null);
42
46
  }
43
- if (now - this.lastUnknownKidRefresh < this.unknownKidCooldownSeconds) return null;
47
+ if (now - this.lastUnknownKidRefresh < this.unknownKidCooldownSeconds) return this.resolution(null);
44
48
  await this.refresh();
45
49
  this.lastUnknownKidRefresh = now;
46
- return this.keys.get(keyid) ?? null;
50
+ return this.resolution(this.keys.get(keyid) ?? null);
51
+ }
52
+ resolution(jwk) {
53
+ return {
54
+ jwk,
55
+ ...this.operatorAuthorizationValidUntil !== void 0 && {
56
+ operatorAuthorizationValidUntil: this.operatorAuthorizationValidUntil
57
+ }
58
+ };
47
59
  }
48
60
  async refresh() {
49
61
  if (this.inFlight) return this.inFlight;
@@ -81,6 +93,7 @@ class ResolvedAgentJwksResolver {
81
93
  }
82
94
  }
83
95
  this.keys = next;
96
+ this.operatorAuthorizationValidUntil = resolution.operatorAuthorizationValidUntil;
84
97
  this.expiresAt = Math.min(
85
98
  refreshedAt + this.cacheTtlSeconds,
86
99
  resolution.operatorAuthorizationValidUntil ?? Number.POSITIVE_INFINITY
@@ -1,6 +1,13 @@
1
1
  import type { AdcpJsonWebKey } from './types.mjs';
2
+ export interface JwksResolution {
3
+ jwk: AdcpJsonWebKey | null;
4
+ /** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
5
+ operatorAuthorizationValidUntil?: number;
6
+ }
2
7
  export interface JwksResolver {
3
8
  resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
9
+ /** Optional metadata-aware lookup used by verifiers that must enforce delegation expiry at acceptance time. */
10
+ resolveWithMetadata?(keyid: string): Promise<JwksResolution>;
4
11
  }
5
12
  export declare class StaticJwksResolver implements JwksResolver {
6
13
  private readonly byKid;
@@ -1,6 +1,13 @@
1
1
  import type { AdcpJsonWebKey } from './types';
2
+ export interface JwksResolution {
3
+ jwk: AdcpJsonWebKey | null;
4
+ /** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
5
+ operatorAuthorizationValidUntil?: number;
6
+ }
2
7
  export interface JwksResolver {
3
8
  resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
9
+ /** Optional metadata-aware lookup used by verifiers that must enforce delegation expiry at acceptance time. */
10
+ resolveWithMetadata?(keyid: string): Promise<JwksResolution>;
4
11
  }
5
12
  export declare class StaticJwksResolver implements JwksResolver {
6
13
  private readonly byKid;
@@ -13,7 +13,7 @@ export { computeContentDigest, contentDigestMatches, parseContentDigest } from '
13
13
  export { requestContextFromExpress, requestContextFromFetch, requestContextFromLambda, type ExpressRequestLike, type FetchRequestLike, type LambdaRequestEvent, type RequestContextFromExpressOptions, type RequestContextFromLambdaOptions, } from './request-context.mjs';
14
14
  export { jwkToPublicKey, verifySignature } from './crypto.mjs';
15
15
  export { RequestSignatureError, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors.mjs';
16
- export { StaticJwksResolver, type JwksResolver } from './jwks.mjs';
16
+ export { StaticJwksResolver, type JwksResolution, type JwksResolver } from './jwks.mjs';
17
17
  export { HttpsJwksResolver, type HttpsJwksResolverOptions } from './jwks-https.mjs';
18
18
  export { BrandJsonJwksResolver, BrandJsonResolverError, type BrandAgentType, type BrandJsonJwksResolverOptions, type BrandJsonResolverErrorCode, } from './brand-jwks.mjs';
19
19
  export { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser.mjs';
@@ -28,4 +28,4 @@ export { createWebhookVerifier, isWebhookLoopbackHost, verifyWebhookSignature, W
28
28
  export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions } from './middleware.mjs';
29
29
  export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer.mjs';
30
30
  export { signResponseAsync } from './signer-async.mjs';
31
- export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, 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';
31
+ 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';
@@ -13,7 +13,7 @@ export { computeContentDigest, contentDigestMatches, parseContentDigest } from '
13
13
  export { requestContextFromExpress, requestContextFromFetch, requestContextFromLambda, type ExpressRequestLike, type FetchRequestLike, type LambdaRequestEvent, type RequestContextFromExpressOptions, type RequestContextFromLambdaOptions, } from './request-context';
14
14
  export { jwkToPublicKey, verifySignature } from './crypto';
15
15
  export { RequestSignatureError, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors';
16
- export { StaticJwksResolver, type JwksResolver } from './jwks';
16
+ export { StaticJwksResolver, type JwksResolution, type JwksResolver } from './jwks';
17
17
  export { HttpsJwksResolver, type HttpsJwksResolverOptions } from './jwks-https';
18
18
  export { BrandJsonJwksResolver, BrandJsonResolverError, type BrandAgentType, type BrandJsonJwksResolverOptions, type BrandJsonResolverErrorCode, } from './brand-jwks';
19
19
  export { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser';
@@ -28,4 +28,4 @@ export { createWebhookVerifier, isWebhookLoopbackHost, verifyWebhookSignature, W
28
28
  export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions } from './middleware';
29
29
  export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer';
30
30
  export { signResponseAsync } from './signer-async';
31
- export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, 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';
31
+ 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';
@@ -53,6 +53,12 @@ export interface VerifyWebhookOptions {
53
53
  requiredTag?: string;
54
54
  /** Optional reverse-lookup (kid → publisher URL) for result attribution. */
55
55
  agentUrlForKeyid?: (keyid: string) => string | undefined;
56
+ /**
57
+ * Trusted local discriminator appended to replay accounting. Receivers use
58
+ * this to isolate registrations authorized under different delegated-
59
+ * operator tuples even when they share a callback URL and signing key.
60
+ */
61
+ replayScopeBinding?: string;
56
62
  }
57
63
  export interface VerifyWebhookResult {
58
64
  status: 'verified';
@@ -53,6 +53,12 @@ export interface VerifyWebhookOptions {
53
53
  requiredTag?: string;
54
54
  /** Optional reverse-lookup (kid → publisher URL) for result attribution. */
55
55
  agentUrlForKeyid?: (keyid: string) => string | undefined;
56
+ /**
57
+ * Trusted local discriminator appended to replay accounting. Receivers use
58
+ * this to isolate registrations authorized under different delegated-
59
+ * operator tuples even when they share a callback URL and signing key.
60
+ */
61
+ replayScopeBinding?: string;
56
62
  }
57
63
  export interface VerifyWebhookResult {
58
64
  status: 'verified';
@@ -42,7 +42,8 @@ const WEBHOOK_MANDATORY_COMPONENTS = [
42
42
  "content-digest"
43
43
  ];
44
44
  async function verifyWebhookSignature(request, options) {
45
- const now = options.now ? options.now() : Math.floor(Date.now() / 1e3);
45
+ const currentTime = options.now ?? (() => Date.now() / 1e3);
46
+ const now = currentTime();
46
47
  const requiredTag = options.requiredTag ?? WEBHOOK_SIGNING_TAG;
47
48
  const sigInputHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature-Input");
48
49
  const sigHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature");
@@ -83,7 +84,8 @@ async function verifyWebhookSignature(request, options) {
83
84
  validateWindow(parsedInput.params.created, parsedInput.params.expires, now);
84
85
  validateCoveredComponents(parsedInput.components);
85
86
  const canonicalWebhookTarget = validateTargetUri(request.url);
86
- const jwk = await options.jwks.resolve(parsedInput.params.keyid);
87
+ const keyResolution = options.jwks.resolveWithMetadata ? await options.jwks.resolveWithMetadata(parsedInput.params.keyid) : { jwk: await options.jwks.resolve(parsedInput.params.keyid) };
88
+ const jwk = keyResolution.jwk;
87
89
  if (!jwk) {
88
90
  throw new import_errors.WebhookSignatureError(
89
91
  "webhook_signature_key_unknown",
@@ -98,6 +100,9 @@ async function verifyWebhookSignature(request, options) {
98
100
  `JWKS resolver returned a JWK whose kid "${jwk.kid}" does not match requested keyid "${parsedInput.params.keyid}".`
99
101
  );
100
102
  }
103
+ if (keyResolution.operatorAuthorizationValidUntil !== void 0) {
104
+ assertDelegatedOperatorAuthorizationActive(keyResolution.operatorAuthorizationValidUntil, currentTime());
105
+ }
101
106
  if (jwk.adcp_use === void 0 || !jwk.key_ops?.includes("verify") || jwk.adcp_use !== "request-signing" && jwk.adcp_use !== "webhook-signing") {
102
107
  throw new import_errors.WebhookSignatureError(
103
108
  "webhook_signature_key_purpose_invalid",
@@ -115,7 +120,7 @@ async function verifyWebhookSignature(request, options) {
115
120
  }
116
121
  throw err;
117
122
  }
118
- const replayScope = canonicalWebhookTarget;
123
+ const replayScope = replayScopeForWebhook(canonicalWebhookTarget, options.replayScopeBinding);
119
124
  if (await options.replayStore.has(jwk.kid, replayScope, parsedInput.params.nonce, now)) {
120
125
  throw new import_errors.WebhookSignatureError(
121
126
  "webhook_signature_replayed",
@@ -161,6 +166,9 @@ async function verifyWebhookSignature(request, options) {
161
166
  "Content-Digest header does not match recomputed body hash."
162
167
  );
163
168
  }
169
+ if (keyResolution.operatorAuthorizationValidUntil !== void 0) {
170
+ assertDelegatedOperatorAuthorizationActive(keyResolution.operatorAuthorizationValidUntil, currentTime());
171
+ }
164
172
  const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS;
165
173
  const ttl = Math.max(remaining, import_types.MAX_SIGNATURE_WINDOW_SECONDS + import_types.CLOCK_SKEW_TOLERANCE_SECONDS);
166
174
  const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
@@ -178,8 +186,34 @@ async function verifyWebhookSignature(request, options) {
178
186
  `Per-keyid replay cache cap exceeded on commit for keyid=${jwk.kid}.`
179
187
  );
180
188
  }
189
+ if (keyResolution.operatorAuthorizationValidUntil !== void 0) {
190
+ assertDelegatedOperatorAuthorizationActive(keyResolution.operatorAuthorizationValidUntil, currentTime());
191
+ }
181
192
  const agent_url = options.agentUrlForKeyid?.(jwk.kid);
182
- return { status: "verified", keyid: jwk.kid, ...agent_url !== void 0 && { agent_url }, verified_at: now };
193
+ return {
194
+ status: "verified",
195
+ keyid: jwk.kid,
196
+ ...agent_url !== void 0 && { agent_url },
197
+ // Keep the established whole-second credential timestamp while using the
198
+ // fractional clock above for exact authorization-expiry enforcement.
199
+ verified_at: Math.floor(now)
200
+ };
201
+ }
202
+ function assertDelegatedOperatorAuthorizationActive(validUntil, now) {
203
+ if (validUntil !== void 0 && (!Number.isFinite(validUntil) || now >= validUntil)) {
204
+ throw new import_errors.WebhookSignatureError(
205
+ "webhook_signature_key_unknown",
206
+ 7,
207
+ "The signing key is no longer authorized for this webhook registration."
208
+ );
209
+ }
210
+ }
211
+ function replayScopeForWebhook(canonicalTarget, binding) {
212
+ if (binding === void 0) return canonicalTarget;
213
+ if (binding.length === 0 || binding.length > 1024 || binding.includes("\0")) {
214
+ throw new TypeError("replayScopeBinding must be a non-empty string of at most 1024 characters without NUL.");
215
+ }
216
+ return JSON.stringify([canonicalTarget, binding]);
183
217
  }
184
218
  function requireParams(parsed) {
185
219
  const required = ["created", "expires", "nonce", "keyid", "alg", "tag"];