@adcp/sdk 16.0.0-beta.6 → 16.0.0-beta.7

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/dist/lib/core/TaskExecutor.d.mts +1 -0
  2. package/dist/lib/core/TaskExecutor.d.ts +1 -0
  3. package/dist/lib/core/TaskExecutor.js +48 -34
  4. package/dist/lib/core/TaskExecutor.mjs +49 -35
  5. package/dist/lib/identity-3.3/revocation.js +5 -4
  6. package/dist/lib/identity-3.3/revocation.mjs +5 -4
  7. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  8. package/dist/lib/server/auth-signature.js +23 -2
  9. package/dist/lib/server/auth-signature.mjs +26 -2
  10. package/dist/lib/server/auth.d.mts +6 -0
  11. package/dist/lib/server/auth.d.ts +6 -0
  12. package/dist/lib/server/auth.js +6 -0
  13. package/dist/lib/server/auth.mjs +6 -0
  14. package/dist/lib/server/create-adcp-server.js +19 -3
  15. package/dist/lib/server/create-adcp-server.mjs +23 -4
  16. package/dist/lib/server/decisioning/runtime/from-platform.js +6 -2
  17. package/dist/lib/server/decisioning/runtime/from-platform.mjs +6 -2
  18. package/dist/lib/server/serve.js +2 -1
  19. package/dist/lib/server/serve.mjs +2 -1
  20. package/dist/lib/server/webhook-emitter.js +1 -0
  21. package/dist/lib/server/webhook-emitter.mjs +2 -1
  22. package/dist/lib/signing/crypto.d.mts +1 -1
  23. package/dist/lib/signing/crypto.d.ts +1 -1
  24. package/dist/lib/signing/crypto.js +37 -2
  25. package/dist/lib/signing/crypto.mjs +37 -2
  26. package/dist/lib/signing/parser.d.mts +6 -0
  27. package/dist/lib/signing/parser.d.ts +6 -0
  28. package/dist/lib/signing/parser.js +5 -1
  29. package/dist/lib/signing/parser.mjs +4 -1
  30. package/dist/lib/signing/web-bot-auth.d.mts +61 -22
  31. package/dist/lib/signing/web-bot-auth.d.ts +61 -22
  32. package/dist/lib/signing/web-bot-auth.js +286 -266
  33. package/dist/lib/signing/web-bot-auth.mjs +288 -277
  34. package/dist/lib/testing/client.js +3 -5
  35. package/dist/lib/testing/client.mjs +4 -6
  36. package/dist/lib/testing/compliance/comply.js +7 -2
  37. package/dist/lib/testing/compliance/comply.mjs +8 -3
  38. package/dist/lib/testing/index.d.mts +1 -1
  39. package/dist/lib/testing/index.d.ts +1 -1
  40. package/dist/lib/testing/index.js +4 -2
  41. package/dist/lib/testing/index.mjs +8 -2
  42. package/dist/lib/testing/storyboard/runner.js +12 -12
  43. package/dist/lib/testing/storyboard/runner.mjs +14 -13
  44. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  45. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  46. package/dist/lib/testing/storyboard/validations.js +23 -1
  47. package/dist/lib/testing/storyboard/validations.mjs +28 -2
  48. package/dist/lib/testing/types.d.mts +39 -0
  49. package/dist/lib/testing/types.d.ts +39 -0
  50. package/dist/lib/v2/projection/augment-response.js +6 -3
  51. package/dist/lib/v2/projection/augment-response.mjs +8 -5
  52. package/dist/lib/v2/projection/creative-delivery.js +29 -17
  53. package/dist/lib/v2/projection/creative-delivery.mjs +31 -18
  54. package/dist/lib/v2/projection/v1-to-v2.d.mts +7 -0
  55. package/dist/lib/v2/projection/v1-to-v2.d.ts +7 -0
  56. package/dist/lib/v2/projection/v1-to-v2.js +70 -2
  57. package/dist/lib/v2/projection/v1-to-v2.mjs +72 -1
  58. package/dist/lib/validation/index.d.mts +1 -1
  59. package/dist/lib/validation/index.d.ts +1 -1
  60. package/dist/lib/validation/index.js +4 -2
  61. package/dist/lib/validation/index.mjs +4 -2
  62. package/dist/lib/validation/schema-loader.d.mts +26 -0
  63. package/dist/lib/validation/schema-loader.d.ts +26 -0
  64. package/dist/lib/validation/schema-loader.js +98 -14
  65. package/dist/lib/validation/schema-loader.mjs +95 -13
  66. package/dist/lib/version.d.mts +3 -3
  67. package/dist/lib/version.d.ts +3 -3
  68. package/dist/lib/version.js +3 -3
  69. package/dist/lib/version.mjs +3 -3
  70. package/docs/TYPE-SUMMARY.md +2 -2
  71. package/docs/guides/VALIDATE-YOUR-AGENT.md +34 -0
  72. package/docs/llms.txt +2 -2
  73. package/docs/migration-14.x-rc-worksheet.md +4 -4
  74. package/docs/migration-agent-identity-3.3.md +60 -23
  75. package/package.json +1 -1
@@ -441,6 +441,7 @@ export declare class TaskExecutor {
441
441
  * AJV runs.
442
442
  */
443
443
  private validateResponseSchema;
444
+ private validateResponseSchemaAgainst;
444
445
  private responseAdcpVersionForValidation;
445
446
  private effectiveResponseAdcpVersion;
446
447
  /**
@@ -441,6 +441,7 @@ export declare class TaskExecutor {
441
441
  * AJV runs.
442
442
  */
443
443
  private validateResponseSchema;
444
+ private validateResponseSchemaAgainst;
444
445
  private responseAdcpVersionForValidation;
445
446
  private effectiveResponseAdcpVersion;
446
447
  /**
@@ -4459,41 +4459,22 @@ class TaskExecutor {
4459
4459
  const mode = this.responseValidationMode;
4460
4460
  const logViolations = this.config.logSchemaViolations !== false;
4461
4461
  try {
4462
- let validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? import_version.ADCP_VERSION;
4463
- if (taskName === "get_adcp_capabilities" && validationVersion !== "v2.5") {
4464
- validationVersion = (0, import_schema_loader.resolveServedValidationVersion)(response, validationVersion);
4465
- }
4466
- const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4467
- const outcome = (this.config.experimentalIdentity33?.() || this.config.identity33SchemaExperiment && normalizedResponse && typeof normalizedResponse === "object" && (0, import_adcp_version_config.isAdcpVersionAtLeast)(normalizedResponse.adcp_version, "3.3")) && mode !== "off" ? validateIdentity33Payload(taskName, "response", normalizedResponse, validationVersion) : (0, import_client_hooks.validateIncomingResponse)(taskName, normalizedResponse, mode, void 0, validationVersion);
4468
- if (outcome.valid) return { valid: true, errors: [] };
4469
- const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4470
- if (logViolations) {
4471
- debugLogs.push({
4472
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4473
- type: mode === "strict" ? "error" : "warning",
4474
- message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${(0, import_schema_validator.formatIssues)(
4475
- outcome.issues
4476
- )}`,
4477
- errors: errorStrings,
4478
- issues: outcome.issues,
4479
- schemaVariant: outcome.variant,
4480
- schemaVersion: validationVersion,
4481
- mode
4482
- });
4483
- }
4484
- if (mode === "warn") {
4485
- if (logViolations) {
4486
- console.warn(
4487
- `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4488
- errorStrings
4489
- );
4490
- }
4491
- return { valid: true, errors: [] };
4492
- }
4493
- if (logViolations) {
4494
- console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4462
+ const pinnedValidationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? import_version.ADCP_VERSION;
4463
+ if (taskName === "get_adcp_capabilities" && pinnedValidationVersion !== "v2.5") {
4464
+ return (0, import_schema_loader.withServedValidationVersion)(
4465
+ response,
4466
+ pinnedValidationVersion,
4467
+ (validationVersion) => this.validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion)
4468
+ );
4495
4469
  }
4496
- return { valid: false, errors: errorStrings };
4470
+ return this.validateResponseSchemaAgainst(
4471
+ response,
4472
+ taskName,
4473
+ debugLogs,
4474
+ mode,
4475
+ logViolations,
4476
+ pinnedValidationVersion
4477
+ );
4497
4478
  } catch (error) {
4498
4479
  const errorMessage = error instanceof Error ? error.message : "unknown error";
4499
4480
  console.error("Error during schema validation:", errorMessage);
@@ -4503,6 +4484,39 @@ class TaskExecutor {
4503
4484
  };
4504
4485
  }
4505
4486
  }
4487
+ validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion) {
4488
+ const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4489
+ const outcome = (this.config.experimentalIdentity33?.() || this.config.identity33SchemaExperiment && normalizedResponse && typeof normalizedResponse === "object" && (0, import_adcp_version_config.isAdcpVersionAtLeast)(normalizedResponse.adcp_version, "3.3")) && mode !== "off" ? validateIdentity33Payload(taskName, "response", normalizedResponse, validationVersion) : (0, import_client_hooks.validateIncomingResponse)(taskName, normalizedResponse, mode, void 0, validationVersion);
4490
+ if (outcome.valid) return { valid: true, errors: [] };
4491
+ const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4492
+ if (logViolations) {
4493
+ debugLogs.push({
4494
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4495
+ type: mode === "strict" ? "error" : "warning",
4496
+ message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${(0, import_schema_validator.formatIssues)(
4497
+ outcome.issues
4498
+ )}`,
4499
+ errors: errorStrings,
4500
+ issues: outcome.issues,
4501
+ schemaVariant: outcome.variant,
4502
+ schemaVersion: validationVersion,
4503
+ mode
4504
+ });
4505
+ }
4506
+ if (mode === "warn") {
4507
+ if (logViolations) {
4508
+ console.warn(
4509
+ `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4510
+ errorStrings
4511
+ );
4512
+ }
4513
+ return { valid: true, errors: [] };
4514
+ }
4515
+ if (logViolations) {
4516
+ console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4517
+ }
4518
+ return { valid: false, errors: errorStrings };
4519
+ }
4506
4520
  responseAdcpVersionForValidation() {
4507
4521
  if (this.config.versionEnvelope === "major-only") return "3.0";
4508
4522
  return void 0;
@@ -26,7 +26,7 @@ import {
26
26
  resolveValidationModes
27
27
  } from "../validation/client-hooks.mjs";
28
28
  import { formatIssues } from "../validation/schema-validator.mjs";
29
- import { resolveServedValidationVersion } from "../validation/schema-loader.mjs";
29
+ import { withServedValidationVersion } from "../validation/schema-loader.mjs";
30
30
  import { ADCP_VERSION } from "../version.mjs";
31
31
  import { unwrapProtocolResponse, isAdcpOperationSuccess } from "../utils/response-unwrapper.mjs";
32
32
  import { extractAdcpErrorInfo, extractCorrelationId } from "../utils/error-extraction.mjs";
@@ -4437,41 +4437,22 @@ class TaskExecutor {
4437
4437
  const mode = this.responseValidationMode;
4438
4438
  const logViolations = this.config.logSchemaViolations !== false;
4439
4439
  try {
4440
- let validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? ADCP_VERSION;
4441
- if (taskName === "get_adcp_capabilities" && validationVersion !== "v2.5") {
4442
- validationVersion = resolveServedValidationVersion(response, validationVersion);
4443
- }
4444
- const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4445
- const outcome = (this.config.experimentalIdentity33?.() || this.config.identity33SchemaExperiment && normalizedResponse && typeof normalizedResponse === "object" && isAdcpVersionAtLeast(normalizedResponse.adcp_version, "3.3")) && mode !== "off" ? validateIdentity33Payload(taskName, "response", normalizedResponse, validationVersion) : validateIncomingResponse(taskName, normalizedResponse, mode, void 0, validationVersion);
4446
- if (outcome.valid) return { valid: true, errors: [] };
4447
- const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4448
- if (logViolations) {
4449
- debugLogs.push({
4450
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4451
- type: mode === "strict" ? "error" : "warning",
4452
- message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${formatIssues(
4453
- outcome.issues
4454
- )}`,
4455
- errors: errorStrings,
4456
- issues: outcome.issues,
4457
- schemaVariant: outcome.variant,
4458
- schemaVersion: validationVersion,
4459
- mode
4460
- });
4461
- }
4462
- if (mode === "warn") {
4463
- if (logViolations) {
4464
- console.warn(
4465
- `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4466
- errorStrings
4467
- );
4468
- }
4469
- return { valid: true, errors: [] };
4470
- }
4471
- if (logViolations) {
4472
- console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4440
+ const pinnedValidationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? ADCP_VERSION;
4441
+ if (taskName === "get_adcp_capabilities" && pinnedValidationVersion !== "v2.5") {
4442
+ return withServedValidationVersion(
4443
+ response,
4444
+ pinnedValidationVersion,
4445
+ (validationVersion) => this.validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion)
4446
+ );
4473
4447
  }
4474
- return { valid: false, errors: errorStrings };
4448
+ return this.validateResponseSchemaAgainst(
4449
+ response,
4450
+ taskName,
4451
+ debugLogs,
4452
+ mode,
4453
+ logViolations,
4454
+ pinnedValidationVersion
4455
+ );
4475
4456
  } catch (error) {
4476
4457
  const errorMessage = error instanceof Error ? error.message : "unknown error";
4477
4458
  console.error("Error during schema validation:", errorMessage);
@@ -4481,6 +4462,39 @@ class TaskExecutor {
4481
4462
  };
4482
4463
  }
4483
4464
  }
4465
+ validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion) {
4466
+ const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4467
+ const outcome = (this.config.experimentalIdentity33?.() || this.config.identity33SchemaExperiment && normalizedResponse && typeof normalizedResponse === "object" && isAdcpVersionAtLeast(normalizedResponse.adcp_version, "3.3")) && mode !== "off" ? validateIdentity33Payload(taskName, "response", normalizedResponse, validationVersion) : validateIncomingResponse(taskName, normalizedResponse, mode, void 0, validationVersion);
4468
+ if (outcome.valid) return { valid: true, errors: [] };
4469
+ const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4470
+ if (logViolations) {
4471
+ debugLogs.push({
4472
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4473
+ type: mode === "strict" ? "error" : "warning",
4474
+ message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${formatIssues(
4475
+ outcome.issues
4476
+ )}`,
4477
+ errors: errorStrings,
4478
+ issues: outcome.issues,
4479
+ schemaVariant: outcome.variant,
4480
+ schemaVersion: validationVersion,
4481
+ mode
4482
+ });
4483
+ }
4484
+ if (mode === "warn") {
4485
+ if (logViolations) {
4486
+ console.warn(
4487
+ `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4488
+ errorStrings
4489
+ );
4490
+ }
4491
+ return { valid: true, errors: [] };
4492
+ }
4493
+ if (logViolations) {
4494
+ console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4495
+ }
4496
+ return { valid: false, errors: errorStrings };
4497
+ }
4484
4498
  responseAdcpVersionForValidation() {
4485
4499
  if (this.config.versionEnvelope === "major-only") return "3.0";
4486
4500
  return void 0;
@@ -225,7 +225,8 @@ class OriginRevocationResolver {
225
225
  for (const token of tokens) {
226
226
  try {
227
227
  const header = (0, import_jose.decodeProtectedHeader)(token);
228
- if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string" || typeof header.alg !== "string" || !["EdDSA", "ES256"].includes(header.alg))
228
+ if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string" || // Directory keys are Ed25519 or RSA-PSS under the profile (adcp#7894).
229
+ header.alg !== "EdDSA" && header.alg !== "PS512")
229
230
  continue;
230
231
  await this.options.directory.resolve(header.kid, origin);
231
232
  const checked = this.options.directory.getVerifiedDirectory(origin);
@@ -234,7 +235,7 @@ class OriginRevocationResolver {
234
235
  const jwk = checked.keys.find((key) => key.kid === header.kid);
235
236
  if (!jwk) continue;
236
237
  const result = await (0, import_jose.compactVerify)(token, await (0, import_jose.importJWK)(jwk, header.alg), {
237
- algorithms: ["EdDSA", "ES256"]
238
+ algorithms: [header.alg]
238
239
  });
239
240
  verifiedPayload = result.payload;
240
241
  break;
@@ -295,8 +296,8 @@ async function createSignedOriginRevocationList(snapshot, privateKey) {
295
296
  const kid = (0, import_web_bot_auth.jwkThumbprint)(privateKey);
296
297
  if (privateKey.kid && privateKey.kid !== kid)
297
298
  throw new TypeError("Revocation signing kid must be the JWK thumbprint");
298
- const alg = privateKey.crv === "Ed25519" ? "EdDSA" : privateKey.crv === "P-256" ? "ES256" : void 0;
299
- if (!alg || !privateKey.d) throw new TypeError("Revocations require a private Ed25519 or P-256 key");
299
+ const alg = privateKey.crv === "Ed25519" ? "EdDSA" : privateKey.kty === "RSA" ? "PS512" : void 0;
300
+ if (!alg || !privateKey.d) throw new TypeError("Revocations require a private Ed25519 or RSA key");
300
301
  return new import_jose.CompactSign(Buffer.from(JSON.stringify(snapshot))).setProtectedHeader({ alg, kid, typ: "adcp-gov-revocation+jws" }).sign(await (0, import_jose.importJWK)(privateKey, alg));
301
302
  }
302
303
  // Annotate the CommonJS export names for ESM import in node:
@@ -204,7 +204,8 @@ class OriginRevocationResolver {
204
204
  for (const token of tokens) {
205
205
  try {
206
206
  const header = decodeProtectedHeader(token);
207
- if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string" || typeof header.alg !== "string" || !["EdDSA", "ES256"].includes(header.alg))
207
+ if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string" || // Directory keys are Ed25519 or RSA-PSS under the profile (adcp#7894).
208
+ header.alg !== "EdDSA" && header.alg !== "PS512")
208
209
  continue;
209
210
  await this.options.directory.resolve(header.kid, origin);
210
211
  const checked = this.options.directory.getVerifiedDirectory(origin);
@@ -213,7 +214,7 @@ class OriginRevocationResolver {
213
214
  const jwk = checked.keys.find((key) => key.kid === header.kid);
214
215
  if (!jwk) continue;
215
216
  const result = await compactVerify(token, await importJWK(jwk, header.alg), {
216
- algorithms: ["EdDSA", "ES256"]
217
+ algorithms: [header.alg]
217
218
  });
218
219
  verifiedPayload = result.payload;
219
220
  break;
@@ -274,8 +275,8 @@ async function createSignedOriginRevocationList(snapshot, privateKey) {
274
275
  const kid = jwkThumbprint(privateKey);
275
276
  if (privateKey.kid && privateKey.kid !== kid)
276
277
  throw new TypeError("Revocation signing kid must be the JWK thumbprint");
277
- const alg = privateKey.crv === "Ed25519" ? "EdDSA" : privateKey.crv === "P-256" ? "ES256" : void 0;
278
- if (!alg || !privateKey.d) throw new TypeError("Revocations require a private Ed25519 or P-256 key");
278
+ const alg = privateKey.crv === "Ed25519" ? "EdDSA" : privateKey.kty === "RSA" ? "PS512" : void 0;
279
+ if (!alg || !privateKey.d) throw new TypeError("Revocations require a private Ed25519 or RSA key");
279
280
  return new CompactSign(Buffer.from(JSON.stringify(snapshot))).setProtectedHeader({ alg, kid, typ: "adcp-gov-revocation+jws" }).sign(await importJWK(privateKey, alg));
280
281
  }
281
282
  export {
@@ -4,5 +4,5 @@
4
4
  "source_sha": "4e553ad955f83b49c7d221ab5c3ff78237ad02e3",
5
5
  "source_tarball_sha256": "580656d6466ef9f0d1119985e6726c2efea718dc671e2ad30957fcb2fd54af0f",
6
6
  "upstream_adcp_version": "2.5.3",
7
- "synced_at": "2026-10-16T23:30:06.203Z"
7
+ "synced_at": "2026-10-17T14:29:51.574Z"
8
8
  }
@@ -149,8 +149,29 @@ function verifySignatureAsAuthenticator(options) {
149
149
  return principal;
150
150
  };
151
151
  const tagged = (0, import_auth.tagAuthenticatorNeedsRawBody)(authenticator);
152
+ if (options.webBotAuth) webBotAuthProfiles.set(tagged, options.capability.profiles ?? []);
152
153
  return options.webBotAuth ? (0, import_auth.tagAuthenticatorPresenceGated)(tagged) : tagged;
153
154
  }
155
+ const webBotAuthProfiles = /* @__PURE__ */ new WeakMap();
156
+ function unsignedRequiredError(err, signatureAuth, body) {
157
+ const profiles = webBotAuthProfiles.get(signatureAuth);
158
+ if (!profiles?.includes("web-bot-auth") || !(err instanceof import_errors.RequestSignatureError) || err.code !== "request_signature_required")
159
+ return asSignatureAuthError(err);
160
+ const hasBody = body !== void 0 && body !== "";
161
+ if ((0, import_web_bot_auth2.advertisesOnlyWebBotAuth)(profiles)) {
162
+ const refused = (0, import_web_bot_auth2.unsignedWebBotAuthError)(hasBody);
163
+ return new import_auth.AuthError(refused.message, {
164
+ cause: refused,
165
+ status: refused.status,
166
+ headers: refused.headers,
167
+ body: refused.body
168
+ });
169
+ }
170
+ return new import_auth.AuthError(`Signature rejected (${err.code}).`, {
171
+ cause: err,
172
+ headers: { "Accept-Signature": (0, import_web_bot_auth2.webBotAuthAcceptSignature)("sig1", hasBody) }
173
+ });
174
+ }
154
175
  function principalForVerifiedSigner(signer, makePrincipal) {
155
176
  if (!SAFE_KEYID.test(signer.keyid)) {
156
177
  throw new import_auth.AuthError("Signature key id contains unsupported characters.", {
@@ -207,7 +228,7 @@ function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {})
207
228
  try {
208
229
  (0, import_verifier.assertUnsignedWebhookAuthenticationAbsent)(body);
209
230
  } catch (err) {
210
- throw asSignatureAuthError(err);
231
+ throw unsignedRequiredError(err, signatureAuth, body);
211
232
  }
212
233
  let fallbackResult = null;
213
234
  let fallbackError;
@@ -223,7 +244,7 @@ function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {})
223
244
  (0, import_verifier.assertUnsignedOperationNotRequired)(operation, unsignedCapability);
224
245
  (0, import_verifier.assertUnsignedProtocolMethodNotRequired)(body, unsignedCapability);
225
246
  } catch (err) {
226
- throw asSignatureAuthError(err);
247
+ throw unsignedRequiredError(err, signatureAuth, body);
227
248
  }
228
249
  if (fallbackThrew) throw fallbackError;
229
250
  return null;
@@ -1,8 +1,11 @@
1
1
  import { getWebBotAuthLegacyOriginGate } from "../signing/web-bot-auth.mjs";
2
2
  import { RequestSignatureError } from "../signing/errors.mjs";
3
3
  import {
4
+ advertisesOnlyWebBotAuth,
4
5
  selectSignatureProfile,
6
+ unsignedWebBotAuthError,
5
7
  verifyWebBotAuth,
8
+ webBotAuthAcceptSignature,
6
9
  isWebBotAuthRevocationChecked,
7
10
  webBotAuthUnavailableResponse,
8
11
  WebBotAuthUnavailableError,
@@ -146,8 +149,29 @@ function verifySignatureAsAuthenticator(options) {
146
149
  return principal;
147
150
  };
148
151
  const tagged = tagAuthenticatorNeedsRawBody(authenticator);
152
+ if (options.webBotAuth) webBotAuthProfiles.set(tagged, options.capability.profiles ?? []);
149
153
  return options.webBotAuth ? tagAuthenticatorPresenceGated(tagged) : tagged;
150
154
  }
155
+ const webBotAuthProfiles = /* @__PURE__ */ new WeakMap();
156
+ function unsignedRequiredError(err, signatureAuth, body) {
157
+ const profiles = webBotAuthProfiles.get(signatureAuth);
158
+ if (!profiles?.includes("web-bot-auth") || !(err instanceof RequestSignatureError) || err.code !== "request_signature_required")
159
+ return asSignatureAuthError(err);
160
+ const hasBody = body !== void 0 && body !== "";
161
+ if (advertisesOnlyWebBotAuth(profiles)) {
162
+ const refused = unsignedWebBotAuthError(hasBody);
163
+ return new AuthError(refused.message, {
164
+ cause: refused,
165
+ status: refused.status,
166
+ headers: refused.headers,
167
+ body: refused.body
168
+ });
169
+ }
170
+ return new AuthError(`Signature rejected (${err.code}).`, {
171
+ cause: err,
172
+ headers: { "Accept-Signature": webBotAuthAcceptSignature("sig1", hasBody) }
173
+ });
174
+ }
151
175
  function principalForVerifiedSigner(signer, makePrincipal) {
152
176
  if (!SAFE_KEYID.test(signer.keyid)) {
153
177
  throw new AuthError("Signature key id contains unsupported characters.", {
@@ -204,7 +228,7 @@ function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {})
204
228
  try {
205
229
  assertUnsignedWebhookAuthenticationAbsent(body);
206
230
  } catch (err) {
207
- throw asSignatureAuthError(err);
231
+ throw unsignedRequiredError(err, signatureAuth, body);
208
232
  }
209
233
  let fallbackResult = null;
210
234
  let fallbackError;
@@ -220,7 +244,7 @@ function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {})
220
244
  assertUnsignedOperationNotRequired(operation, unsignedCapability);
221
245
  assertUnsignedProtocolMethodNotRequired(body, unsignedCapability);
222
246
  } catch (err) {
223
- throw asSignatureAuthError(err);
247
+ throw unsignedRequiredError(err, signatureAuth, body);
224
248
  }
225
249
  if (fallbackThrew) throw fallbackError;
226
250
  return null;
@@ -346,6 +346,12 @@ export interface RespondUnauthorizedOptions {
346
346
  * directly.
347
347
  */
348
348
  signatureError?: RequestSignatureErrorCode;
349
+ /**
350
+ * Additional response headers, such as the Web Bot Auth `Accept-Signature`
351
+ * challenge added to an AdCP RFC 9421 `request_signature_required` answer.
352
+ * Cannot replace `WWW-Authenticate` or `Content-Type`.
353
+ */
354
+ headers?: Record<string, string>;
349
355
  }
350
356
  /**
351
357
  * Send an RFC 6750-compliant 401/403 response with the `WWW-Authenticate`
@@ -346,6 +346,12 @@ export interface RespondUnauthorizedOptions {
346
346
  * directly.
347
347
  */
348
348
  signatureError?: RequestSignatureErrorCode;
349
+ /**
350
+ * Additional response headers, such as the Web Bot Auth `Accept-Signature`
351
+ * challenge added to an AdCP RFC 9421 `request_signature_required` answer.
352
+ * Cannot replace `WWW-Authenticate` or `Content-Type`.
353
+ */
354
+ headers?: Record<string, string>;
349
355
  }
350
356
  /**
351
357
  * Send an RFC 6750-compliant 401/403 response with the `WWW-Authenticate`
@@ -247,7 +247,13 @@ function respondUnauthorized(_req, res, options = {}) {
247
247
  if (options.errorDescription) parts.push(`error_description="${escapeQuotes(options.errorDescription)}"`);
248
248
  if (options.resourceMetadata) parts.push(`resource_metadata="${escapeQuotes(options.resourceMetadata)}"`);
249
249
  const scheme = useSignatureChallenge ? "Signature" : "Bearer";
250
+ const extra = Object.fromEntries(
251
+ Object.entries(options.headers ?? {}).filter(
252
+ ([name]) => !["content-type", "www-authenticate"].includes(name.toLowerCase())
253
+ )
254
+ );
250
255
  res.writeHead(options.status ?? 401, {
256
+ ...extra,
251
257
  "Content-Type": "application/json",
252
258
  "WWW-Authenticate": `${scheme} ${parts.join(", ")}`
253
259
  });
@@ -208,7 +208,13 @@ function respondUnauthorized(_req, res, options = {}) {
208
208
  if (options.errorDescription) parts.push(`error_description="${escapeQuotes(options.errorDescription)}"`);
209
209
  if (options.resourceMetadata) parts.push(`resource_metadata="${escapeQuotes(options.resourceMetadata)}"`);
210
210
  const scheme = useSignatureChallenge ? "Signature" : "Bearer";
211
+ const extra = Object.fromEntries(
212
+ Object.entries(options.headers ?? {}).filter(
213
+ ([name]) => !["content-type", "www-authenticate"].includes(name.toLowerCase())
214
+ )
215
+ );
211
216
  res.writeHead(options.status ?? 401, {
217
+ ...extra,
212
218
  "Content-Type": "application/json",
213
219
  "WWW-Authenticate": `${scheme} ${parts.join(", ")}`
214
220
  });
@@ -1438,6 +1438,8 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
1438
1438
  "Web Bot Auth requires origin revocation: use createIdentity33PrincipalResolver({ revocation }) or supply isRevoked"
1439
1439
  );
1440
1440
  const requiredFor = effectiveRequestSigning.required_for ?? [];
1441
+ const advertisedProfiles = effectiveRequestSigning.profiles;
1442
+ const webBotAuthAdvertised = !!signedRequests.webBotAuth && !!advertisedProfiles?.includes("web-bot-auth");
1441
1443
  const verifier = (0, import_middleware.createExpressVerifier)({
1442
1444
  capability: {
1443
1445
  supported: true,
@@ -1543,6 +1545,19 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
1543
1545
  res.setHeader(k, v);
1544
1546
  return {
1545
1547
  json(body) {
1548
+ if (webBotAuthAdvertised && code === 401 && body?.error === "request_signature_required") {
1549
+ const hasBody = !!req.rawBody;
1550
+ if ((0, import_web_bot_auth2.advertisesOnlyWebBotAuth)(advertisedProfiles)) {
1551
+ const refused = (0, import_web_bot_auth2.unsignedWebBotAuthError)(hasBody);
1552
+ res.removeHeader("WWW-Authenticate");
1553
+ res.statusCode = refused.status;
1554
+ for (const [name, value] of Object.entries(refused.headers)) res.setHeader(name, value);
1555
+ res.setHeader("Content-Type", "application/json");
1556
+ res.end(JSON.stringify(refused.body));
1557
+ return;
1558
+ }
1559
+ res.setHeader("Accept-Signature", (0, import_web_bot_auth2.webBotAuthAcceptSignature)("sig1", hasBody));
1560
+ }
1546
1561
  res.setHeader("Content-Type", "application/json");
1547
1562
  res.end(JSON.stringify(body));
1548
1563
  }
@@ -2127,6 +2142,7 @@ function createAdcpServer(config) {
2127
2142
  throw new Error("Select Web Bot Auth on each trusted webhook registration, not as a server-wide default");
2128
2143
  if (config.webhooks?.webBotAuth && !config.identity33)
2129
2144
  throw new Error("Web Bot Auth webhook signing requires the identity33 experiment");
2145
+ if (config.webhooks?.webBotAuth?.key) (0, import_web_bot_auth.assertWebBotAuthSigningKey)(config.webhooks.webBotAuth.key);
2130
2146
  const toolsWithErrorArm = (0, import_error_arm_tools.getToolsWithErrorArm)(adcpVersion);
2131
2147
  const isProduction = process.env.NODE_ENV === "production";
2132
2148
  const requestValidationMode = validationConfig?.requests ?? (isProduction ? "off" : "strict");
@@ -4558,9 +4574,9 @@ function createAdcpServer(config) {
4558
4574
  algorithms: [
4559
4575
  .../* @__PURE__ */ new Set([
4560
4576
  algorithm,
4561
- ...webhooks.webBotAuth ? [
4562
- ("privateKey" in webhooks.webBotAuth.key ? webhooks.webBotAuth.key.privateKey.crv : webhooks.webBotAuth.key.publicKey.crv) === "P-256" ? "ecdsa-p256-sha256" : "ed25519"
4563
- ] : []
4577
+ // `algorithms` lists AdCP webhook-profile values; a Web Bot Auth key
4578
+ // is Ed25519 or RSA-PSS (adcp#7894), and only Ed25519 has a value here.
4579
+ ...webhooks.webBotAuth && ("privateKey" in webhooks.webBotAuth.key ? webhooks.webBotAuth.key.privateKey.crv : webhooks.webBotAuth.key.publicKey.crv) === "Ed25519" ? ["ed25519"] : []
4564
4580
  ])
4565
4581
  ],
4566
4582
  ...webhooks.webBotAuth && { profiles: ["adcp/webhook-signing/v1", "web-bot-auth"] },
@@ -1,4 +1,4 @@
1
- import { getWebBotAuthLegacyOriginGate } from "../signing/web-bot-auth.mjs";
1
+ import { assertWebBotAuthSigningKey, getWebBotAuthLegacyOriginGate } from "../signing/web-bot-auth.mjs";
2
2
  import {
3
3
  isIdentity33SchemaScope,
4
4
  withIdentity33SchemaScope,
@@ -12,6 +12,9 @@ import {
12
12
  webBotAuthToolFailure,
13
13
  selectSignatureProfile,
14
14
  verifyWebBotAuth,
15
+ advertisesOnlyWebBotAuth,
16
+ unsignedWebBotAuthError,
17
+ webBotAuthAcceptSignature,
15
18
  isWebBotAuthRevocationChecked,
16
19
  webBotAuthUnavailableResponse,
17
20
  WebBotAuthUnavailableError,
@@ -1526,6 +1529,8 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
1526
1529
  "Web Bot Auth requires origin revocation: use createIdentity33PrincipalResolver({ revocation }) or supply isRevoked"
1527
1530
  );
1528
1531
  const requiredFor = effectiveRequestSigning.required_for ?? [];
1532
+ const advertisedProfiles = effectiveRequestSigning.profiles;
1533
+ const webBotAuthAdvertised = !!signedRequests.webBotAuth && !!advertisedProfiles?.includes("web-bot-auth");
1529
1534
  const verifier = createExpressVerifier({
1530
1535
  capability: {
1531
1536
  supported: true,
@@ -1631,6 +1636,19 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
1631
1636
  res.setHeader(k, v);
1632
1637
  return {
1633
1638
  json(body) {
1639
+ if (webBotAuthAdvertised && code === 401 && body?.error === "request_signature_required") {
1640
+ const hasBody = !!req.rawBody;
1641
+ if (advertisesOnlyWebBotAuth(advertisedProfiles)) {
1642
+ const refused = unsignedWebBotAuthError(hasBody);
1643
+ res.removeHeader("WWW-Authenticate");
1644
+ res.statusCode = refused.status;
1645
+ for (const [name, value] of Object.entries(refused.headers)) res.setHeader(name, value);
1646
+ res.setHeader("Content-Type", "application/json");
1647
+ res.end(JSON.stringify(refused.body));
1648
+ return;
1649
+ }
1650
+ res.setHeader("Accept-Signature", webBotAuthAcceptSignature("sig1", hasBody));
1651
+ }
1634
1652
  res.setHeader("Content-Type", "application/json");
1635
1653
  res.end(JSON.stringify(body));
1636
1654
  }
@@ -2215,6 +2233,7 @@ function createAdcpServer(config) {
2215
2233
  throw new Error("Select Web Bot Auth on each trusted webhook registration, not as a server-wide default");
2216
2234
  if (config.webhooks?.webBotAuth && !config.identity33)
2217
2235
  throw new Error("Web Bot Auth webhook signing requires the identity33 experiment");
2236
+ if (config.webhooks?.webBotAuth?.key) assertWebBotAuthSigningKey(config.webhooks.webBotAuth.key);
2218
2237
  const toolsWithErrorArm = getToolsWithErrorArm(adcpVersion);
2219
2238
  const isProduction = process.env.NODE_ENV === "production";
2220
2239
  const requestValidationMode = validationConfig?.requests ?? (isProduction ? "off" : "strict");
@@ -4646,9 +4665,9 @@ function createAdcpServer(config) {
4646
4665
  algorithms: [
4647
4666
  .../* @__PURE__ */ new Set([
4648
4667
  algorithm,
4649
- ...webhooks.webBotAuth ? [
4650
- ("privateKey" in webhooks.webBotAuth.key ? webhooks.webBotAuth.key.privateKey.crv : webhooks.webBotAuth.key.publicKey.crv) === "P-256" ? "ecdsa-p256-sha256" : "ed25519"
4651
- ] : []
4668
+ // `algorithms` lists AdCP webhook-profile values; a Web Bot Auth key
4669
+ // is Ed25519 or RSA-PSS (adcp#7894), and only Ed25519 has a value here.
4670
+ ...webhooks.webBotAuth && ("privateKey" in webhooks.webBotAuth.key ? webhooks.webBotAuth.key.privateKey.crv : webhooks.webBotAuth.key.publicKey.crv) === "Ed25519" ? ["ed25519"] : []
4652
4671
  ])
4653
4672
  ],
4654
4673
  ...webhooks.webBotAuth && { profiles: ["adcp/webhook-signing/v1", "web-bot-auth"] },
@@ -311,7 +311,10 @@ function projectResponseCreativeIdentities(value, operation, legacyFormatConvert
311
311
  let projected = dataRecord;
312
312
  if ((0, import_types2.isProjectionProductInput)(projected)) {
313
313
  if (wireMode === "canonical") {
314
- const canonical = (0, import_augment_response.toCanonicalOnlyResponse)({ products: [projected] }, { legacyFormatConverter });
314
+ const canonical = (0, import_augment_response.toCanonicalOnlyResponse)(
315
+ { products: [projected] },
316
+ { legacyFormatConverter, _inferDualEmissionLegacyRefs: false }
317
+ );
315
318
  if (canonical.diagnostics.length > 0 || canonical.response.products.length !== 1) {
316
319
  throw new import_creative_delivery.CreativeFormatProjectionError(
317
320
  operation,
@@ -550,7 +553,8 @@ function asCanonicalProductResponse(response, legacyFormatConverter) {
550
553
  assertSafeGetProductsResponse(response);
551
554
  validateDualProductFormatDeclarations(response, legacyFormatConverter);
552
555
  const projected = (0, import_augment_response.toCanonicalOnlyResponse)(response, {
553
- legacyFormatConverter
556
+ legacyFormatConverter,
557
+ _inferDualEmissionLegacyRefs: false
554
558
  });
555
559
  const blockingDiagnostics = projected.diagnostics.filter(
556
560
  (diagnostic) => diagnostic.code !== "LEGACY_FORMAT_ID_DROPPED_UNMAPPED"
@@ -343,7 +343,10 @@ function projectResponseCreativeIdentities(value, operation, legacyFormatConvert
343
343
  let projected = dataRecord;
344
344
  if (isProjectionProductInput(projected)) {
345
345
  if (wireMode === "canonical") {
346
- const canonical = toCanonicalOnlyResponse({ products: [projected] }, { legacyFormatConverter });
346
+ const canonical = toCanonicalOnlyResponse(
347
+ { products: [projected] },
348
+ { legacyFormatConverter, _inferDualEmissionLegacyRefs: false }
349
+ );
347
350
  if (canonical.diagnostics.length > 0 || canonical.response.products.length !== 1) {
348
351
  throw new CreativeFormatProjectionError(
349
352
  operation,
@@ -582,7 +585,8 @@ function asCanonicalProductResponse(response, legacyFormatConverter) {
582
585
  assertSafeGetProductsResponse(response);
583
586
  validateDualProductFormatDeclarations(response, legacyFormatConverter);
584
587
  const projected = toCanonicalOnlyResponse(response, {
585
- legacyFormatConverter
588
+ legacyFormatConverter,
589
+ _inferDualEmissionLegacyRefs: false
586
590
  });
587
591
  const blockingDiagnostics = projected.diagnostics.filter(
588
592
  (diagnostic) => diagnostic.code !== "LEGACY_FORMAT_ID_DROPPED_UNMAPPED"
@@ -322,7 +322,8 @@ function serve(createAgent, options) {
322
322
  (0, import_auth.respondUnauthorized)(req, res, {
323
323
  signatureError: signatureCode,
324
324
  errorDescription: publicMessage,
325
- resourceMetadata: resourceMetadataUrl
325
+ resourceMetadata: resourceMetadataUrl,
326
+ ...err instanceof import_auth.AuthError && err.headers && { headers: err.headers }
326
327
  });
327
328
  return;
328
329
  }
@@ -305,7 +305,8 @@ function serve(createAgent, options) {
305
305
  respondUnauthorized(req, res, {
306
306
  signatureError: signatureCode,
307
307
  errorDescription: publicMessage,
308
- resourceMetadata: resourceMetadataUrl
308
+ resourceMetadata: resourceMetadataUrl,
309
+ ...err instanceof AuthError && err.headers && { headers: err.headers }
309
310
  });
310
311
  return;
311
312
  }