@adcp/sdk 16.0.0-beta.9 → 16.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +54 -2
  2. package/bin/adcp-identity.js +3 -2
  3. package/dist/lib/client/core.d.mts +1 -1
  4. package/dist/lib/client/core.d.ts +1 -1
  5. package/dist/lib/core/AgentClient.d.mts +8 -1
  6. package/dist/lib/core/AgentClient.d.ts +8 -1
  7. package/dist/lib/core/AgentClient.js +10 -0
  8. package/dist/lib/core/AgentClient.mjs +10 -0
  9. package/dist/lib/core/AsyncHandler.d.mts +2 -0
  10. package/dist/lib/core/AsyncHandler.d.ts +2 -0
  11. package/dist/lib/core/AsyncHandler.js +1 -1
  12. package/dist/lib/core/AsyncHandler.mjs +1 -1
  13. package/dist/lib/core/ConversationTypes.d.mts +32 -0
  14. package/dist/lib/core/ConversationTypes.d.ts +32 -0
  15. package/dist/lib/core/SingleAgentClient.d.mts +11 -1
  16. package/dist/lib/core/SingleAgentClient.d.ts +11 -1
  17. package/dist/lib/core/SingleAgentClient.js +25 -6
  18. package/dist/lib/core/SingleAgentClient.mjs +26 -7
  19. package/dist/lib/core/TaskExecutor.d.mts +3 -1
  20. package/dist/lib/core/TaskExecutor.d.ts +3 -1
  21. package/dist/lib/core/TaskExecutor.js +127 -34
  22. package/dist/lib/core/TaskExecutor.mjs +127 -34
  23. package/dist/lib/core/task-deadline.d.mts +2 -0
  24. package/dist/lib/core/task-deadline.d.ts +2 -0
  25. package/dist/lib/core/task-deadline.js +20 -2
  26. package/dist/lib/core/task-deadline.mjs +19 -2
  27. package/dist/lib/identity-3.3/revocation.js +6 -7
  28. package/dist/lib/identity-3.3/revocation.mjs +6 -7
  29. package/dist/lib/identity-3.3/schemas.draft.js +1 -1
  30. package/dist/lib/identity-3.3/schemas.draft.mjs +1 -1
  31. package/dist/lib/identity-3.3/trust.d.mts +8 -0
  32. package/dist/lib/identity-3.3/trust.d.ts +8 -0
  33. package/dist/lib/identity-3.3/trust.js +41 -12
  34. package/dist/lib/identity-3.3/trust.mjs +40 -12
  35. package/dist/lib/index.d.mts +1 -1
  36. package/dist/lib/index.d.ts +1 -1
  37. package/dist/lib/server/account-sync-contract.js +4 -1
  38. package/dist/lib/server/account-sync-contract.mjs +4 -1
  39. package/dist/lib/server/create-adcp-server.js +1 -1
  40. package/dist/lib/server/create-adcp-server.mjs +1 -1
  41. package/dist/lib/server/decisioning/runtime/from-platform.js +22 -17
  42. package/dist/lib/server/decisioning/runtime/from-platform.mjs +22 -17
  43. package/dist/lib/signing/crypto.d.mts +1 -1
  44. package/dist/lib/signing/crypto.d.ts +1 -1
  45. package/dist/lib/signing/crypto.js +2 -45
  46. package/dist/lib/signing/crypto.mjs +2 -46
  47. package/dist/lib/signing/web-bot-auth.d.mts +18 -10
  48. package/dist/lib/signing/web-bot-auth.d.ts +18 -10
  49. package/dist/lib/signing/web-bot-auth.js +48 -28
  50. package/dist/lib/signing/web-bot-auth.mjs +49 -29
  51. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  52. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  53. package/dist/lib/v2/projection/catalog.d.mts +10 -0
  54. package/dist/lib/v2/projection/catalog.d.ts +10 -0
  55. package/dist/lib/v2/projection/catalog.js +5 -0
  56. package/dist/lib/v2/projection/catalog.mjs +4 -0
  57. package/dist/lib/v2/projection/creative-delivery.d.mts +15 -9
  58. package/dist/lib/v2/projection/creative-delivery.d.ts +15 -9
  59. package/dist/lib/v2/projection/creative-delivery.js +71 -10
  60. package/dist/lib/v2/projection/creative-delivery.mjs +71 -10
  61. package/dist/lib/v2/projection/v1-to-v2.js +3 -2
  62. package/dist/lib/v2/projection/v1-to-v2.mjs +4 -3
  63. package/dist/lib/version.d.mts +3 -3
  64. package/dist/lib/version.d.ts +3 -3
  65. package/dist/lib/version.js +3 -3
  66. package/dist/lib/version.mjs +3 -3
  67. package/docs/TYPE-SUMMARY.md +12 -2
  68. package/docs/guides/CREATIVE-DELIVERY.md +18 -1
  69. package/docs/guides/REPORTING-STABILITY.md +7 -7
  70. package/docs/llms.txt +3 -3
  71. package/docs/migration-14.x-rc-worksheet.md +4 -4
  72. package/docs/migration-agent-identity-3.3.md +56 -23
  73. package/docs/migration-reporting-storage-package.md +3 -3
  74. package/docs/releases/16.0.0.md +20 -0
  75. package/package.json +1 -1
@@ -8,8 +8,8 @@ export declare const WEB_BOT_AUTH_PROFILE: "web-bot-auth";
8
8
  export declare const WEB_BOT_AUTH_DRAFT_REVISION: "draft-ietf-webbotauth-httpsig-protocol-00";
9
9
  export declare const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
10
10
  export declare const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
11
- /** The profile's signature algorithms (wba-profile.mdx "Profile rules"). ES256 is RFC 9421 profile only. */
12
- export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519", "rsa-pss-sha512"];
11
+ /** The profile's signature algorithm: Ed25519 only. ES256 is RFC 9421 profile only. */
12
+ export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519"];
13
13
  /**
14
14
  * The profile's `Accept-Signature` member (RFC 9421 §5.1): the label, the
15
15
  * components the profile requires, and its parameters. A replay challenge
@@ -73,7 +73,12 @@ export declare function jwkThumbprint(jwk: Partial<Pick<AdcpJsonWebKey, 'kty' |
73
73
  * time rather than on the first request.
74
74
  */
75
75
  export declare function assertWebBotAuthSigningKey(key: WebBotSigningKey): void;
76
- /** Reject authority normalization differentials on received identifiers. */
76
+ /**
77
+ * A received identity origin, which must be exactly the canonical https
78
+ * origin string (WBA §5.2.1): lowercase host, no default port, and no path,
79
+ * trailing `/`, query, fragment, or userinfo. The string is compared as
80
+ * received, never normalized first.
81
+ */
77
82
  export declare function receivedAgentOrigin(value: string): string;
78
83
  export declare function getWebBotAuthLegacyOriginGate(resolver: Function | undefined): ((origin: string) => Promise<void>) | undefined;
79
84
  export interface WebBotAuthAuthorizationFailure {
@@ -136,8 +141,9 @@ export interface VerifyWebBotAuthOptions {
136
141
  /**
137
142
  * Verify a Web Bot Auth request (wba-profile.mdx "Verification"). Resolves to
138
143
  * exactly one verified signer: the profile takes one `web-bot-auth` signature
139
- * and one `Signature-Agent` member per request. The array shape is kept for
140
- * principal resolvers written against earlier drafts.
144
+ * and one `Signature-Agent` member per request, and does not read signatures
145
+ * with other tags. The array shape is kept for principal resolvers written
146
+ * against earlier drafts.
141
147
  */
142
148
  export declare function verifyWebBotAuth(request: RequestLike, options: VerifyWebBotAuthOptions): Promise<VerifiedWebBotSigner[]>;
143
149
  export interface VerifiedSignatureDirectory {
@@ -151,11 +157,13 @@ export interface VerifiedSignatureDirectory {
151
157
  }
152
158
  /**
153
159
  * Verify a key directory response (wba-profile.mdx "Key directories", WBA
154
- * Appendix B.1). The response must be signed once per key, each signature
155
- * covering `"@authority";req` and `content-digest` with `created`, `expires`,
156
- * the key's thumbprint as `keyid` and `tag="http-message-signatures-directory"`,
157
- * and verifying under the key the directory itself provides. An unsigned
158
- * directory is a discovery failure.
160
+ * Appendix B.1). The response must be signed once per usable key, each
161
+ * signature covering `"@authority";req` and `content-digest` with `created`,
162
+ * `expires`, the key's thumbprint as `keyid` and
163
+ * `tag="http-message-signatures-directory"`, and verifying under the key the
164
+ * directory itself provides. Keys the profile cannot use, and their
165
+ * signatures, are ignored. An unsigned directory, or one without a usable
166
+ * key, is a discovery failure.
159
167
  */
160
168
  export declare function verifyKeyDirectory(response: ResponseLike, now?: number, options?: {
161
169
  allowTestKeys?: boolean;
@@ -70,8 +70,9 @@ const WEB_BOT_AUTH_PROFILE = "web-bot-auth";
70
70
  const WEB_BOT_AUTH_DRAFT_REVISION = "draft-ietf-webbotauth-httpsig-protocol-00";
71
71
  const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
72
72
  const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
73
- const WEB_BOT_AUTH_ALGORITHMS = Object.freeze(["ed25519", "rsa-pss-sha512"]);
73
+ const WEB_BOT_AUTH_ALGORITHMS = Object.freeze(["ed25519"]);
74
74
  const DIRECTORY_TAG = "http-message-signatures-directory";
75
+ const PRIVATE_JWK_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth", "k"]);
75
76
  const CLOCK_SKEW_SECONDS = 60;
76
77
  const MAX_LIFETIME_SECONDS = 300;
77
78
  const REQUIRED_PARAMETERS = ["created", "expires", "nonce", "keyid", "alg", "tag"];
@@ -182,26 +183,37 @@ function jwkThumbprint(jwk) {
182
183
  else return denied("unsupported_key");
183
184
  return (0, import_crypto.createHash)("sha256").update(JSON.stringify(fields)).digest("base64url");
184
185
  }
185
- function algorithm(jwk) {
186
- if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
187
- if (jwk.kty === "RSA" && typeof jwk.n === "string" && jwk.e === "AQAB") {
188
- const bytes = Buffer.from(jwk.n, "base64url").length;
189
- if (bytes >= 256 && bytes <= 512) return "rsa-pss-sha512";
186
+ function keyAlgorithm(jwk) {
187
+ return jwk.kty === "OKP" && jwk.crv === "Ed25519" ? "ed25519" : void 0;
188
+ }
189
+ function importable(jwk) {
190
+ try {
191
+ (0, import_crypto2.jwkToPublicKey)(jwk);
192
+ return true;
193
+ } catch {
194
+ return false;
190
195
  }
191
- return denied("unsupported_key");
196
+ }
197
+ function algorithm(jwk) {
198
+ return keyAlgorithm(jwk) ?? denied("unsupported_key");
192
199
  }
193
200
  function signingAlgorithm(jwk) {
194
201
  try {
195
202
  return algorithm(jwk);
196
203
  } catch {
197
- throw new TypeError(
198
- "Web Bot Auth signs with Ed25519 or RSA-PSS-SHA512 (2048-4096 bit, e=65537) keys only; ES256 remains on the AdCP RFC 9421 profile"
199
- );
204
+ throw new TypeError("Web Bot Auth signs with Ed25519 keys only; ES256 remains on the AdCP RFC 9421 profile");
200
205
  }
201
206
  }
202
207
  function assertWebBotAuthSigningKey(key) {
203
208
  signingAlgorithm("privateKey" in key ? key.privateKey : key.publicKey);
204
209
  }
210
+ function signingOrigin(value) {
211
+ try {
212
+ return receivedAgentOrigin((0, import_canonicalize.canonicalAgentOrigin)(value));
213
+ } catch {
214
+ throw new TypeError("Web Bot Auth signs as an https origin only (WBA section 5.2.1)");
215
+ }
216
+ }
205
217
  function inputs(headers, limit) {
206
218
  const members = dictionary((0, import_canonicalize2.getHeaderValue)(headers, "signature-input"), "Signature-Input");
207
219
  if (members.size > limit) bad("signature_input");
@@ -214,6 +226,7 @@ function inputs(headers, limit) {
214
226
  });
215
227
  }
216
228
  function receivedAgentOrigin(value) {
229
+ if (!value.startsWith("https://")) return denied("agent_origin");
217
230
  try {
218
231
  (0, import_select_agent.canonicalAgentUrl)(value);
219
232
  const origin = (0, import_canonicalize.canonicalAgentOrigin)(value);
@@ -436,8 +449,10 @@ async function verifyWebBotAuth(request, options) {
436
449
  if (!(member2[0] instanceof ArrayBuffer) || member2[1].size !== 0) bad("signature");
437
450
  for (const member2 of agents?.values() ?? []) if (typeof member2[0] !== "string") bad("signature_agent");
438
451
  if (!inputMembers || !signatureMembers) return denied("headers_missing");
439
- if (inputMembers.size !== 1 || signatureMembers.size !== 1) denied("signature_count");
440
- label = inputMembers.keys().next().value;
452
+ const tagged = [...inputMembers].filter(([, member2]) => member2[1].get("tag") === WEB_BOT_AUTH_PROFILE);
453
+ if (tagged.length === 0) denied("profile");
454
+ if (tagged.length > 1) denied("signature_count");
455
+ label = tagged[0][0];
441
456
  if (!signatureMembers.has(label)) denied("signature_label");
442
457
  try {
443
458
  (0, import_parser.parseSignature)(signatureHeader, label, "rfc8941-base64");
@@ -450,7 +465,6 @@ async function verifyWebBotAuth(request, options) {
450
465
  } catch {
451
466
  return denied("parameters");
452
467
  }
453
- if (input.params.tag !== WEB_BOT_AUTH_PROFILE) denied("profile");
454
468
  if (!agents || agents.size !== 1) denied(agents ? "agent_count" : "agent_missing");
455
469
  const agentMember = `signature-agent;key="${label}"`;
456
470
  const agentComponents = input.components.filter((c) => c.startsWith("signature-agent;"));
@@ -458,7 +472,7 @@ async function verifyWebBotAuth(request, options) {
458
472
  const member = agents.get(label);
459
473
  if (!member) denied("agent_not_covered");
460
474
  const type = member[1].get("type");
461
- if (type !== void 0 && type !== "directory") denied("agent_type");
475
+ if (type !== void 0 && String(type) !== "directory") denied("agent_type");
462
476
  const origin = receivedAgentOrigin(member[0]);
463
477
  const { created, expires, nonce, keyid, alg } = input.params;
464
478
  if ([created, expires, nonce, keyid, alg].some((value) => value === void 0)) denied("parameters_missing");
@@ -582,25 +596,26 @@ async function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}
582
596
  let expires = Infinity;
583
597
  for (const raw of document.keys) {
584
598
  if (!raw || typeof raw !== "object") denied("directory_key");
599
+ if (Object.keys(raw).some((k) => PRIVATE_JWK_MEMBERS.has(k.toLowerCase()) || k.startsWith("_private")))
600
+ denied("directory_key");
601
+ if (!keyAlgorithm(raw) || !importable(raw)) continue;
585
602
  const thumbprint = jwkThumbprint(raw);
586
603
  const key = { ...raw, kid: raw.kid ?? thumbprint };
587
- if (key.kid !== thumbprint || kids.has(key.kid) || key.d !== void 0 || Object.keys(key).some((k) => k.startsWith("_private")) || key.adcp_use !== void 0 || !testKeysAllowed(options.allowTestKeys) && TEST_KIDS.has(key.kid))
604
+ if (key.kid !== thumbprint || kids.has(key.kid) || key.adcp_use !== void 0 || !testKeysAllowed(options.allowTestKeys) && TEST_KIDS.has(key.kid))
588
605
  denied("directory_key");
589
- algorithm(key);
590
606
  kids.add(key.kid);
591
- keys.push(key);
592
607
  const proof = signatures.find((input) => input.params.keyid === key.kid && input.params.tag === DIRECTORY_TAG);
593
608
  if (!proof || !proof.components.includes("@authority;req") || !proof.components.includes("content-digest") || !Number.isSafeInteger(proof.params.created) || !Number.isSafeInteger(proof.params.expires) || proof.params.expires <= now || proof.params.expires <= proof.params.created)
594
- denied("directory_proof");
609
+ continue;
595
610
  const proofComponents = [(0, import_http_message_sig.component)("@authority", { req: true }), (0, import_http_message_sig.component)("content-digest")];
596
611
  let covered;
597
612
  try {
598
613
  covered = proof.components.map(componentDescriptor);
599
614
  } catch {
600
- return denied("directory_proof");
615
+ continue;
601
616
  }
602
617
  const requestHeaders = response.request.headers ?? {};
603
- await verifyWithLibrary(
618
+ const proven = await verifyWithLibrary(
604
619
  {
605
620
  kind: "response",
606
621
  status: response.status,
@@ -626,9 +641,18 @@ async function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}
626
641
  // The library rejects a proof whose `created` is in the future (beyond skew).
627
642
  { requiredComponents: proofComponents, requiredParameters: ["created", "expires", "keyid", "tag"], now },
628
643
  async () => keyVerifier(key)
644
+ ).then(
645
+ () => true,
646
+ (error) => {
647
+ if (error instanceof WebBotAuthError) return false;
648
+ throw error;
649
+ }
629
650
  );
651
+ if (!proven) continue;
652
+ keys.push(key);
630
653
  expires = Math.min(expires, proof.params.expires);
631
654
  }
655
+ if (!keys.length) denied("directory_keys");
632
656
  return { keys, expires, signed: true, proof: response };
633
657
  } catch (error) {
634
658
  if (error instanceof WebBotAuthError) {
@@ -758,7 +782,7 @@ async function signWebBotAuth(request, key, options) {
758
782
  throw new TypeError(
759
783
  "Web Bot Auth relays were removed from the profile (adcp#7894): an intermediary signs as itself with one signature"
760
784
  );
761
- const origin = (0, import_canonicalize.canonicalAgentOrigin)(options.origin);
785
+ const origin = signingOrigin(options.origin);
762
786
  const label = options.label ?? "sig1";
763
787
  if (!/^[a-z][a-z0-9_.*-]*$/.test(label)) throw new TypeError("Invalid signature label");
764
788
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
@@ -813,7 +837,7 @@ async function signWebBotAuth(request, key, options) {
813
837
  return headers;
814
838
  }
815
839
  async function createSignedSignatureDirectory(originUrl, signingKeys, options = {}) {
816
- const origin = (0, import_canonicalize.canonicalAgentOrigin)(originUrl);
840
+ const origin = signingOrigin(originUrl);
817
841
  const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
818
842
  const lifetime = options.lifetimeSeconds ?? 86400;
819
843
  if (!Number.isFinite(now) || !Number.isSafeInteger(lifetime) || lifetime < 1 || !Number.isSafeInteger(now + lifetime))
@@ -822,12 +846,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
822
846
  const keys = signingKeys.map((key) => {
823
847
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
824
848
  signingAlgorithm(jwk);
825
- return {
826
- kty: jwk.kty,
827
- ...jwk.kty === "RSA" ? { n: jwk.n, e: jwk.e } : { crv: jwk.crv, x: jwk.x },
828
- kid: jwkThumbprint(jwk),
829
- use: "sig"
830
- };
849
+ return { kty: jwk.kty, crv: jwk.crv, x: jwk.x, kid: jwkThumbprint(jwk), use: "sig" };
831
850
  });
832
851
  if (new Set(keys.map((k) => k.kid)).size !== keys.length) throw new TypeError("Duplicate directory key");
833
852
  const body = JSON.stringify({ keys });
@@ -873,6 +892,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
873
892
  }
874
893
  function createWebBotAuthFetch(upstream, key, options) {
875
894
  assertWebBotAuthSigningKey(key);
895
+ signingOrigin(options.origin);
876
896
  return async (input, init) => {
877
897
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
878
898
  if (options.shouldSign && !options.shouldSign(url, init)) return upstream(input, init);
@@ -16,7 +16,7 @@ import { canonicalAgentUrl } from "./agent-resolver/select-agent.mjs";
16
16
  import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
17
17
  import { getHeaderValue } from "./canonicalize.mjs";
18
18
  import { computeContentDigest, contentDigestMatches, contentDigestUsesEncoding } from "./content-digest.mjs";
19
- import { signWithPrivateJwkAsync, verifySignatureAsync } from "./crypto.mjs";
19
+ import { jwkToPublicKey, signWithPrivateJwkAsync, verifySignatureAsync } from "./crypto.mjs";
20
20
  import {
21
21
  parseSignature,
22
22
  parseSignatureInputMember,
@@ -26,8 +26,9 @@ const WEB_BOT_AUTH_PROFILE = "web-bot-auth";
26
26
  const WEB_BOT_AUTH_DRAFT_REVISION = "draft-ietf-webbotauth-httpsig-protocol-00";
27
27
  const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
28
28
  const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
29
- const WEB_BOT_AUTH_ALGORITHMS = Object.freeze(["ed25519", "rsa-pss-sha512"]);
29
+ const WEB_BOT_AUTH_ALGORITHMS = Object.freeze(["ed25519"]);
30
30
  const DIRECTORY_TAG = "http-message-signatures-directory";
31
+ const PRIVATE_JWK_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth", "k"]);
31
32
  const CLOCK_SKEW_SECONDS = 60;
32
33
  const MAX_LIFETIME_SECONDS = 300;
33
34
  const REQUIRED_PARAMETERS = ["created", "expires", "nonce", "keyid", "alg", "tag"];
@@ -138,26 +139,37 @@ function jwkThumbprint(jwk) {
138
139
  else return denied("unsupported_key");
139
140
  return createHash("sha256").update(JSON.stringify(fields)).digest("base64url");
140
141
  }
141
- function algorithm(jwk) {
142
- if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
143
- if (jwk.kty === "RSA" && typeof jwk.n === "string" && jwk.e === "AQAB") {
144
- const bytes = Buffer.from(jwk.n, "base64url").length;
145
- if (bytes >= 256 && bytes <= 512) return "rsa-pss-sha512";
142
+ function keyAlgorithm(jwk) {
143
+ return jwk.kty === "OKP" && jwk.crv === "Ed25519" ? "ed25519" : void 0;
144
+ }
145
+ function importable(jwk) {
146
+ try {
147
+ jwkToPublicKey(jwk);
148
+ return true;
149
+ } catch {
150
+ return false;
146
151
  }
147
- return denied("unsupported_key");
152
+ }
153
+ function algorithm(jwk) {
154
+ return keyAlgorithm(jwk) ?? denied("unsupported_key");
148
155
  }
149
156
  function signingAlgorithm(jwk) {
150
157
  try {
151
158
  return algorithm(jwk);
152
159
  } catch {
153
- throw new TypeError(
154
- "Web Bot Auth signs with Ed25519 or RSA-PSS-SHA512 (2048-4096 bit, e=65537) keys only; ES256 remains on the AdCP RFC 9421 profile"
155
- );
160
+ throw new TypeError("Web Bot Auth signs with Ed25519 keys only; ES256 remains on the AdCP RFC 9421 profile");
156
161
  }
157
162
  }
158
163
  function assertWebBotAuthSigningKey(key) {
159
164
  signingAlgorithm("privateKey" in key ? key.privateKey : key.publicKey);
160
165
  }
166
+ function signingOrigin(value) {
167
+ try {
168
+ return receivedAgentOrigin(canonicalAgentOrigin(value));
169
+ } catch {
170
+ throw new TypeError("Web Bot Auth signs as an https origin only (WBA section 5.2.1)");
171
+ }
172
+ }
161
173
  function inputs(headers, limit) {
162
174
  const members = dictionary(getHeaderValue(headers, "signature-input"), "Signature-Input");
163
175
  if (members.size > limit) bad("signature_input");
@@ -170,6 +182,7 @@ function inputs(headers, limit) {
170
182
  });
171
183
  }
172
184
  function receivedAgentOrigin(value) {
185
+ if (!value.startsWith("https://")) return denied("agent_origin");
173
186
  try {
174
187
  canonicalAgentUrl(value);
175
188
  const origin = canonicalAgentOrigin(value);
@@ -392,8 +405,10 @@ async function verifyWebBotAuth(request, options) {
392
405
  if (!(member2[0] instanceof ArrayBuffer) || member2[1].size !== 0) bad("signature");
393
406
  for (const member2 of agents?.values() ?? []) if (typeof member2[0] !== "string") bad("signature_agent");
394
407
  if (!inputMembers || !signatureMembers) return denied("headers_missing");
395
- if (inputMembers.size !== 1 || signatureMembers.size !== 1) denied("signature_count");
396
- label = inputMembers.keys().next().value;
408
+ const tagged = [...inputMembers].filter(([, member2]) => member2[1].get("tag") === WEB_BOT_AUTH_PROFILE);
409
+ if (tagged.length === 0) denied("profile");
410
+ if (tagged.length > 1) denied("signature_count");
411
+ label = tagged[0][0];
397
412
  if (!signatureMembers.has(label)) denied("signature_label");
398
413
  try {
399
414
  parseSignature(signatureHeader, label, "rfc8941-base64");
@@ -406,7 +421,6 @@ async function verifyWebBotAuth(request, options) {
406
421
  } catch {
407
422
  return denied("parameters");
408
423
  }
409
- if (input.params.tag !== WEB_BOT_AUTH_PROFILE) denied("profile");
410
424
  if (!agents || agents.size !== 1) denied(agents ? "agent_count" : "agent_missing");
411
425
  const agentMember = `signature-agent;key="${label}"`;
412
426
  const agentComponents = input.components.filter((c) => c.startsWith("signature-agent;"));
@@ -414,7 +428,7 @@ async function verifyWebBotAuth(request, options) {
414
428
  const member = agents.get(label);
415
429
  if (!member) denied("agent_not_covered");
416
430
  const type = member[1].get("type");
417
- if (type !== void 0 && type !== "directory") denied("agent_type");
431
+ if (type !== void 0 && String(type) !== "directory") denied("agent_type");
418
432
  const origin = receivedAgentOrigin(member[0]);
419
433
  const { created, expires, nonce, keyid, alg } = input.params;
420
434
  if ([created, expires, nonce, keyid, alg].some((value) => value === void 0)) denied("parameters_missing");
@@ -538,25 +552,26 @@ async function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}
538
552
  let expires = Infinity;
539
553
  for (const raw of document.keys) {
540
554
  if (!raw || typeof raw !== "object") denied("directory_key");
555
+ if (Object.keys(raw).some((k) => PRIVATE_JWK_MEMBERS.has(k.toLowerCase()) || k.startsWith("_private")))
556
+ denied("directory_key");
557
+ if (!keyAlgorithm(raw) || !importable(raw)) continue;
541
558
  const thumbprint = jwkThumbprint(raw);
542
559
  const key = { ...raw, kid: raw.kid ?? thumbprint };
543
- if (key.kid !== thumbprint || kids.has(key.kid) || key.d !== void 0 || Object.keys(key).some((k) => k.startsWith("_private")) || key.adcp_use !== void 0 || !testKeysAllowed(options.allowTestKeys) && TEST_KIDS.has(key.kid))
560
+ if (key.kid !== thumbprint || kids.has(key.kid) || key.adcp_use !== void 0 || !testKeysAllowed(options.allowTestKeys) && TEST_KIDS.has(key.kid))
544
561
  denied("directory_key");
545
- algorithm(key);
546
562
  kids.add(key.kid);
547
- keys.push(key);
548
563
  const proof = signatures.find((input) => input.params.keyid === key.kid && input.params.tag === DIRECTORY_TAG);
549
564
  if (!proof || !proof.components.includes("@authority;req") || !proof.components.includes("content-digest") || !Number.isSafeInteger(proof.params.created) || !Number.isSafeInteger(proof.params.expires) || proof.params.expires <= now || proof.params.expires <= proof.params.created)
550
- denied("directory_proof");
565
+ continue;
551
566
  const proofComponents = [component("@authority", { req: true }), component("content-digest")];
552
567
  let covered;
553
568
  try {
554
569
  covered = proof.components.map(componentDescriptor);
555
570
  } catch {
556
- return denied("directory_proof");
571
+ continue;
557
572
  }
558
573
  const requestHeaders = response.request.headers ?? {};
559
- await verifyWithLibrary(
574
+ const proven = await verifyWithLibrary(
560
575
  {
561
576
  kind: "response",
562
577
  status: response.status,
@@ -582,9 +597,18 @@ async function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}
582
597
  // The library rejects a proof whose `created` is in the future (beyond skew).
583
598
  { requiredComponents: proofComponents, requiredParameters: ["created", "expires", "keyid", "tag"], now },
584
599
  async () => keyVerifier(key)
600
+ ).then(
601
+ () => true,
602
+ (error) => {
603
+ if (error instanceof WebBotAuthError) return false;
604
+ throw error;
605
+ }
585
606
  );
607
+ if (!proven) continue;
608
+ keys.push(key);
586
609
  expires = Math.min(expires, proof.params.expires);
587
610
  }
611
+ if (!keys.length) denied("directory_keys");
588
612
  return { keys, expires, signed: true, proof: response };
589
613
  } catch (error) {
590
614
  if (error instanceof WebBotAuthError) {
@@ -714,7 +738,7 @@ async function signWebBotAuth(request, key, options) {
714
738
  throw new TypeError(
715
739
  "Web Bot Auth relays were removed from the profile (adcp#7894): an intermediary signs as itself with one signature"
716
740
  );
717
- const origin = canonicalAgentOrigin(options.origin);
741
+ const origin = signingOrigin(options.origin);
718
742
  const label = options.label ?? "sig1";
719
743
  if (!/^[a-z][a-z0-9_.*-]*$/.test(label)) throw new TypeError("Invalid signature label");
720
744
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
@@ -769,7 +793,7 @@ async function signWebBotAuth(request, key, options) {
769
793
  return headers;
770
794
  }
771
795
  async function createSignedSignatureDirectory(originUrl, signingKeys, options = {}) {
772
- const origin = canonicalAgentOrigin(originUrl);
796
+ const origin = signingOrigin(originUrl);
773
797
  const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
774
798
  const lifetime = options.lifetimeSeconds ?? 86400;
775
799
  if (!Number.isFinite(now) || !Number.isSafeInteger(lifetime) || lifetime < 1 || !Number.isSafeInteger(now + lifetime))
@@ -778,12 +802,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
778
802
  const keys = signingKeys.map((key) => {
779
803
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
780
804
  signingAlgorithm(jwk);
781
- return {
782
- kty: jwk.kty,
783
- ...jwk.kty === "RSA" ? { n: jwk.n, e: jwk.e } : { crv: jwk.crv, x: jwk.x },
784
- kid: jwkThumbprint(jwk),
785
- use: "sig"
786
- };
805
+ return { kty: jwk.kty, crv: jwk.crv, x: jwk.x, kid: jwkThumbprint(jwk), use: "sig" };
787
806
  });
788
807
  if (new Set(keys.map((k) => k.kid)).size !== keys.length) throw new TypeError("Duplicate directory key");
789
808
  const body = JSON.stringify({ keys });
@@ -829,6 +848,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
829
848
  }
830
849
  function createWebBotAuthFetch(upstream, key, options) {
831
850
  assertWebBotAuthSigningKey(key);
851
+ signingOrigin(options.origin);
832
852
  return async (input, init) => {
833
853
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
834
854
  if (options.shouldSign && !options.shouldSign(url, init)) return upstream(input, init);
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "16.0.0-beta.9";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "16.1.0";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "16.0.0-beta.9";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "16.1.0";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -70,6 +70,10 @@ export interface V1FormatDefinition {
70
70
  required?: boolean;
71
71
  asset_type?: string;
72
72
  requirements?: Record<string, unknown>;
73
+ /** Reference-catalog extension used to match pixel-tracker slots. */
74
+ event?: string;
75
+ /** Caller-defined tracker extension; required to match a custom event. */
76
+ custom_event_name?: string;
73
77
  }>;
74
78
  /**
75
79
  * v2 canonical projection. Always object-shaped per
@@ -87,6 +91,12 @@ interface CatalogIndex {
87
91
  byUniqueId: Map<string, V1FormatDefinition | null>;
88
92
  entries: V1FormatDefinition[];
89
93
  }
94
+ /**
95
+ * True when `agentUrl` names the AAO reference format agent (including its
96
+ * folded historical hosts). Ids under any other agent URL belong to that
97
+ * agent, so AAO naming conventions say nothing about what they mean.
98
+ */
99
+ export declare function isAaoAgentUrl(agentUrl: string): boolean;
90
100
  /**
91
101
  * Load the catalog from a known path. Resolution order:
92
102
  *
@@ -70,6 +70,10 @@ export interface V1FormatDefinition {
70
70
  required?: boolean;
71
71
  asset_type?: string;
72
72
  requirements?: Record<string, unknown>;
73
+ /** Reference-catalog extension used to match pixel-tracker slots. */
74
+ event?: string;
75
+ /** Caller-defined tracker extension; required to match a custom event. */
76
+ custom_event_name?: string;
73
77
  }>;
74
78
  /**
75
79
  * v2 canonical projection. Always object-shaped per
@@ -87,6 +91,12 @@ interface CatalogIndex {
87
91
  byUniqueId: Map<string, V1FormatDefinition | null>;
88
92
  entries: V1FormatDefinition[];
89
93
  }
94
+ /**
95
+ * True when `agentUrl` names the AAO reference format agent (including its
96
+ * folded historical hosts). Ids under any other agent URL belong to that
97
+ * agent, so AAO naming conventions say nothing about what they mean.
98
+ */
99
+ export declare function isAaoAgentUrl(agentUrl: string): boolean;
90
100
  /**
91
101
  * Load the catalog from a known path. Resolution order:
92
102
  *
@@ -30,6 +30,7 @@ var catalog_exports = {};
30
30
  __export(catalog_exports, {
31
31
  _resetCatalogCache: () => _resetCatalogCache,
32
32
  findCatalogEntryByCanonicalAndSize: () => findCatalogEntryByCanonicalAndSize,
33
+ isAaoAgentUrl: () => isAaoAgentUrl,
33
34
  loadCatalog: () => loadCatalog,
34
35
  lookupUniqueV1FormatById: () => lookupUniqueV1FormatById,
35
36
  lookupV1Format: () => lookupV1Format,
@@ -52,6 +53,9 @@ function normalizeAgentUrl(u) {
52
53
  if (canonical === null) return void 0;
53
54
  return AAO_LEGACY_AGENT_URL_ALIASES.has(canonical) ? import_constants.AAO_CANONICAL_AGENT_URL : canonical;
54
55
  }
56
+ function isAaoAgentUrl(agentUrl) {
57
+ return normalizeAgentUrl(agentUrl) === import_constants.AAO_CANONICAL_AGENT_URL;
58
+ }
55
59
  function indexKey(agentUrl, id) {
56
60
  const normalized = normalizeAgentUrl(agentUrl);
57
61
  return normalized === void 0 ? void 0 : `${normalized}::${id}`;
@@ -136,6 +140,7 @@ function findCatalogEntryByCanonicalAndSize(canonical, width, height, agentUrl,
136
140
  0 && (module.exports = {
137
141
  _resetCatalogCache,
138
142
  findCatalogEntryByCanonicalAndSize,
143
+ isAaoAgentUrl,
139
144
  loadCatalog,
140
145
  lookupUniqueV1FormatById,
141
146
  lookupV1Format,
@@ -19,6 +19,9 @@ function normalizeAgentUrl(u) {
19
19
  if (canonical === null) return void 0;
20
20
  return AAO_LEGACY_AGENT_URL_ALIASES.has(canonical) ? AAO_CANONICAL_AGENT_URL : canonical;
21
21
  }
22
+ function isAaoAgentUrl(agentUrl) {
23
+ return normalizeAgentUrl(agentUrl) === AAO_CANONICAL_AGENT_URL;
24
+ }
22
25
  function indexKey(agentUrl, id) {
23
26
  const normalized = normalizeAgentUrl(agentUrl);
24
27
  return normalized === void 0 ? void 0 : `${normalized}::${id}`;
@@ -102,6 +105,7 @@ function findCatalogEntryByCanonicalAndSize(canonical, width, height, agentUrl,
102
105
  export {
103
106
  _resetCatalogCache,
104
107
  findCatalogEntryByCanonicalAndSize,
108
+ isAaoAgentUrl,
105
109
  loadCatalog,
106
110
  lookupUniqueV1FormatById,
107
111
  lookupV1Format,
@@ -4,6 +4,7 @@ import type { CanonicalFormatLegacyResolver } from './v2-to-v1.mjs';
4
4
  import type { CanonicalFormatKind, ProjectionDiagnostic, V1FormatId } from './types.mjs';
5
5
  import type { CanonicalFormatDeclaration } from './legacy-metadata.mjs';
6
6
  import type { UndeclaredFormatOptionSelector } from './legacy-routes.mjs';
7
+ import { type V1FormatDefinition } from './catalog.mjs';
7
8
  type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, Extract<keyof T, K>> : never;
8
9
  /**
9
10
  * Primary SDK creative shape. Spell out the named fields rather than using
@@ -276,15 +277,20 @@ export declare class CreativeFormatCapabilityError extends Error {
276
277
  readonly code = "ADCP_CREATIVE_FORMAT_CAPABILITY_CONTRADICTION";
277
278
  constructor(message: string);
278
279
  }
279
- export declare function projectCreativeForDelivery<T extends CreativeAsset>(creative: T, selectorContainer: CreativeFormatSelectorContainer, wireMode?: 'canonical', operation?: string, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): CanonicalProjectedCreative<T>;
280
- export declare function projectCreativeForDelivery<T extends CreativeAsset>(creative: T, selectorContainer: CreativeFormatSelectorContainer, wireMode: 'legacy' | 'unknown', operation?: string, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): LegacyProjectedCreative<T>;
281
- export declare function projectCreativeForDelivery<T extends CreativeAsset>(creative: T, selectorContainer: CreativeFormatSelectorContainer, wireMode: CreativeFormatWireMode, operation?: string, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): CanonicalProjectedCreative<T> | LegacyProjectedCreative<T>;
282
- export declare function projectMediaBuyCreativesForDelivery<T>(request: T, wireMode: 'canonical', operation?: 'create_media_buy' | 'update_media_buy', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): ProjectedMediaBuyCreativeRequest<T, 'canonical'>;
283
- export declare function projectMediaBuyCreativesForDelivery<T>(request: T, wireMode: 'legacy' | 'unknown', operation?: 'create_media_buy' | 'update_media_buy', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): ProjectedMediaBuyCreativeRequest<T, 'legacy'>;
284
- export declare function projectMediaBuyCreativesForDelivery<T>(request: T, wireMode: CreativeFormatWireMode, operation?: 'create_media_buy' | 'update_media_buy', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): ProjectedMediaBuyCreativeRequest<T, 'canonical'> | ProjectedMediaBuyCreativeRequest<T, 'legacy'>;
285
- export declare function projectSyncCreativesForDelivery<T>(request: T, selectorContainers: ReadonlyArray<CreativeFormatSelectorContainer>, wireMode: 'canonical', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): ProjectedSyncCreativeRequest<T, 'canonical'>;
286
- export declare function projectSyncCreativesForDelivery<T>(request: T, selectorContainers: ReadonlyArray<CreativeFormatSelectorContainer>, wireMode: 'legacy' | 'unknown', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): ProjectedSyncCreativeRequest<T, 'legacy'>;
287
- export declare function projectSyncCreativesForDelivery<T>(request: T, selectorContainers: ReadonlyArray<CreativeFormatSelectorContainer>, wireMode: CreativeFormatWireMode, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver): ProjectedSyncCreativeRequest<T, 'canonical'> | ProjectedSyncCreativeRequest<T, 'legacy'>;
280
+ /**
281
+ * Project identity and asset keys for the selected wire mode. Optional
282
+ * `legacyFormatDefinitions` override bundled definitions by exact format owner
283
+ * and ID; ambiguous definitions leave asset keys unchanged.
284
+ */
285
+ export declare function projectCreativeForDelivery<T extends CreativeAsset>(creative: T, selectorContainer: CreativeFormatSelectorContainer, wireMode?: 'canonical', operation?: string, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): CanonicalProjectedCreative<T>;
286
+ export declare function projectCreativeForDelivery<T extends CreativeAsset>(creative: T, selectorContainer: CreativeFormatSelectorContainer, wireMode: 'legacy' | 'unknown', operation?: string, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): LegacyProjectedCreative<T>;
287
+ export declare function projectCreativeForDelivery<T extends CreativeAsset>(creative: T, selectorContainer: CreativeFormatSelectorContainer, wireMode: CreativeFormatWireMode, operation?: string, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): CanonicalProjectedCreative<T> | LegacyProjectedCreative<T>;
288
+ export declare function projectMediaBuyCreativesForDelivery<T>(request: T, wireMode: 'canonical', operation?: 'create_media_buy' | 'update_media_buy', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): ProjectedMediaBuyCreativeRequest<T, 'canonical'>;
289
+ export declare function projectMediaBuyCreativesForDelivery<T>(request: T, wireMode: 'legacy' | 'unknown', operation?: 'create_media_buy' | 'update_media_buy', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): ProjectedMediaBuyCreativeRequest<T, 'legacy'>;
290
+ export declare function projectMediaBuyCreativesForDelivery<T>(request: T, wireMode: CreativeFormatWireMode, operation?: 'create_media_buy' | 'update_media_buy', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): ProjectedMediaBuyCreativeRequest<T, 'canonical'> | ProjectedMediaBuyCreativeRequest<T, 'legacy'>;
291
+ export declare function projectSyncCreativesForDelivery<T>(request: T, selectorContainers: ReadonlyArray<CreativeFormatSelectorContainer>, wireMode: 'canonical', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): ProjectedSyncCreativeRequest<T, 'canonical'>;
292
+ export declare function projectSyncCreativesForDelivery<T>(request: T, selectorContainers: ReadonlyArray<CreativeFormatSelectorContainer>, wireMode: 'legacy' | 'unknown', legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): ProjectedSyncCreativeRequest<T, 'legacy'>;
293
+ export declare function projectSyncCreativesForDelivery<T>(request: T, selectorContainers: ReadonlyArray<CreativeFormatSelectorContainer>, wireMode: CreativeFormatWireMode, legacyFormatConverter?: LegacyFormatConverter, canonicalFormatLegacyResolver?: CanonicalFormatLegacyResolver, legacyFormatDefinitions?: readonly V1FormatDefinition[]): ProjectedSyncCreativeRequest<T, 'canonical'> | ProjectedSyncCreativeRequest<T, 'legacy'>;
288
294
  export type { UndeclaredFormatOptionSelector } from './legacy-routes.mjs';
289
295
  /**
290
296
  * Looks up a format option the SDK derived from a seller's legacy `format_ids`.