@adcp/sdk 15.0.0 → 15.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/bin/adcp-grade.js +46 -2
  2. package/bin/adcp.js +7 -2
  3. package/dist/lib/canonical-references/index.d.mts +19 -1
  4. package/dist/lib/canonical-references/index.d.ts +19 -1
  5. package/dist/lib/canonical-references/index.js +81 -9
  6. package/dist/lib/canonical-references/index.mjs +80 -9
  7. package/dist/lib/client/core.d.mts +1 -0
  8. package/dist/lib/client/core.d.ts +1 -0
  9. package/dist/lib/core/AgentClient.d.mts +9 -1
  10. package/dist/lib/core/AgentClient.d.ts +9 -1
  11. package/dist/lib/core/AgentClient.js +10 -0
  12. package/dist/lib/core/AgentClient.mjs +10 -0
  13. package/dist/lib/core/SingleAgentClient.d.mts +13 -2
  14. package/dist/lib/core/SingleAgentClient.d.ts +13 -2
  15. package/dist/lib/core/SingleAgentClient.js +23 -5
  16. package/dist/lib/core/SingleAgentClient.mjs +23 -5
  17. package/dist/lib/index.d.mts +1 -1
  18. package/dist/lib/index.d.ts +1 -1
  19. package/dist/lib/reporting/gcs/adapter.d.mts +31 -0
  20. package/dist/lib/reporting/gcs/adapter.d.ts +31 -0
  21. package/dist/lib/reporting/gcs/adapter.js +342 -0
  22. package/dist/lib/reporting/gcs/adapter.mjs +325 -0
  23. package/dist/lib/reporting/gcs/bucket-policy.d.mts +3 -0
  24. package/dist/lib/reporting/gcs/bucket-policy.d.ts +3 -0
  25. package/dist/lib/reporting/gcs/bucket-policy.js +31 -0
  26. package/dist/lib/reporting/gcs/bucket-policy.mjs +7 -0
  27. package/dist/lib/reporting/gcs/errors.d.mts +5 -0
  28. package/dist/lib/reporting/gcs/errors.d.ts +5 -0
  29. package/dist/lib/reporting/gcs/errors.js +41 -0
  30. package/dist/lib/reporting/gcs/errors.mjs +16 -0
  31. package/dist/lib/reporting/gcs/fence.d.mts +36 -0
  32. package/dist/lib/reporting/gcs/fence.d.ts +36 -0
  33. package/dist/lib/reporting/gcs/fence.js +250 -0
  34. package/dist/lib/reporting/gcs/fence.mjs +230 -0
  35. package/dist/lib/reporting/gcs/index.d.mts +7 -0
  36. package/dist/lib/reporting/gcs/index.d.ts +7 -0
  37. package/dist/lib/reporting/gcs/index.js +41 -0
  38. package/dist/lib/reporting/gcs/index.mjs +12 -0
  39. package/dist/lib/reporting/gcs/io.d.mts +7 -0
  40. package/dist/lib/reporting/gcs/io.d.ts +7 -0
  41. package/dist/lib/reporting/gcs/io.js +95 -0
  42. package/dist/lib/reporting/gcs/io.mjs +69 -0
  43. package/dist/lib/reporting/gcs/reader.d.mts +43 -0
  44. package/dist/lib/reporting/gcs/reader.d.ts +43 -0
  45. package/dist/lib/reporting/gcs/reader.js +193 -0
  46. package/dist/lib/reporting/gcs/reader.mjs +169 -0
  47. package/dist/lib/reporting/gcs/validation.d.mts +5 -0
  48. package/dist/lib/reporting/gcs/validation.d.ts +5 -0
  49. package/dist/lib/reporting/gcs/validation.js +49 -0
  50. package/dist/lib/reporting/gcs/validation.mjs +25 -0
  51. package/dist/lib/reporting/ledger/handler.d.mts +1 -0
  52. package/dist/lib/reporting/ledger/handler.d.ts +1 -0
  53. package/dist/lib/reporting/ledger/index.d.mts +1 -0
  54. package/dist/lib/reporting/ledger/index.d.ts +1 -0
  55. package/dist/lib/reporting/ledger/index.js +2 -0
  56. package/dist/lib/reporting/ledger/index.mjs +1 -0
  57. package/dist/lib/reporting/ledger/managed-postgres.d.mts +16 -1
  58. package/dist/lib/reporting/ledger/managed-postgres.d.ts +16 -1
  59. package/dist/lib/reporting/ledger/managed-postgres.js +243 -2
  60. package/dist/lib/reporting/ledger/managed-postgres.mjs +248 -2
  61. package/dist/lib/reporting/ledger/object-writes.d.mts +69 -0
  62. package/dist/lib/reporting/ledger/object-writes.d.ts +69 -0
  63. package/dist/lib/reporting/ledger/object-writes.js +147 -0
  64. package/dist/lib/reporting/ledger/object-writes.mjs +114 -0
  65. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  66. package/dist/lib/server/a2a-adapter.d.mts +50 -1
  67. package/dist/lib/server/a2a-adapter.d.ts +50 -1
  68. package/dist/lib/server/a2a-adapter.js +230 -7
  69. package/dist/lib/server/a2a-adapter.mjs +237 -7
  70. package/dist/lib/server/auth-signature.d.mts +84 -46
  71. package/dist/lib/server/auth-signature.d.ts +84 -46
  72. package/dist/lib/server/auth-signature.js +48 -14
  73. package/dist/lib/server/auth-signature.mjs +58 -15
  74. package/dist/lib/server/create-adcp-server.js +5 -16
  75. package/dist/lib/server/create-adcp-server.mjs +5 -16
  76. package/dist/lib/server/index.d.mts +1 -1
  77. package/dist/lib/server/index.d.ts +1 -1
  78. package/dist/lib/server/index.js +2 -0
  79. package/dist/lib/server/index.mjs +3 -1
  80. package/dist/lib/signing/agent-fetch.js +3 -33
  81. package/dist/lib/signing/agent-fetch.mjs +3 -33
  82. package/dist/lib/signing/middleware.d.mts +9 -1
  83. package/dist/lib/signing/middleware.d.ts +9 -1
  84. package/dist/lib/signing/operation-resolution.d.mts +37 -0
  85. package/dist/lib/signing/operation-resolution.d.ts +37 -0
  86. package/dist/lib/signing/operation-resolution.js +154 -0
  87. package/dist/lib/signing/operation-resolution.mjs +125 -0
  88. package/dist/lib/signing/postgres-replay-store.d.mts +1 -1
  89. package/dist/lib/signing/postgres-replay-store.d.ts +1 -1
  90. package/dist/lib/signing/redis-replay-store.d.mts +1 -1
  91. package/dist/lib/signing/redis-replay-store.d.ts +1 -1
  92. package/dist/lib/signing/server.d.mts +1 -0
  93. package/dist/lib/signing/server.d.ts +1 -0
  94. package/dist/lib/signing/server.js +7 -0
  95. package/dist/lib/signing/server.mjs +8 -0
  96. package/dist/lib/signing/verifier.d.mts +36 -1
  97. package/dist/lib/signing/verifier.d.ts +36 -1
  98. package/dist/lib/signing/verifier.js +66 -34
  99. package/dist/lib/signing/verifier.mjs +61 -34
  100. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +12 -0
  101. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +12 -0
  102. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +29 -2
  103. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +27 -1
  104. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.mts +61 -0
  105. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.ts +61 -0
  106. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.js +298 -0
  107. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.mjs +276 -0
  108. package/dist/lib/testing/storyboard/request-signing/grader.d.mts +31 -1
  109. package/dist/lib/testing/storyboard/request-signing/grader.d.ts +31 -1
  110. package/dist/lib/testing/storyboard/request-signing/grader.js +11 -0
  111. package/dist/lib/testing/storyboard/request-signing/grader.mjs +11 -0
  112. package/dist/lib/testing/storyboard/request-signing/index.d.mts +5 -4
  113. package/dist/lib/testing/storyboard/request-signing/index.d.ts +5 -4
  114. package/dist/lib/testing/storyboard/request-signing/index.js +13 -0
  115. package/dist/lib/testing/storyboard/request-signing/index.mjs +18 -2
  116. package/dist/lib/testing/storyboard/request-signing/types.d.mts +43 -0
  117. package/dist/lib/testing/storyboard/request-signing/types.d.ts +43 -0
  118. package/dist/lib/testing/storyboard/request-signing/types.js +3 -0
  119. package/dist/lib/testing/storyboard/request-signing/types.mjs +2 -0
  120. package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +42 -1
  121. package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +42 -1
  122. package/dist/lib/testing/storyboard/request-signing/vector-loader.js +100 -1
  123. package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +104 -3
  124. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  125. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  126. package/dist/lib/version.d.mts +3 -3
  127. package/dist/lib/version.d.ts +3 -3
  128. package/dist/lib/version.js +3 -3
  129. package/dist/lib/version.mjs +3 -3
  130. package/docs/TYPE-SUMMARY.md +2 -2
  131. package/docs/guides/BUILD-AN-AGENT.md +9 -4
  132. package/docs/guides/CREATIVE-DELIVERY.md +34 -0
  133. package/docs/guides/REPORTING-GCS-FENCE.md +52 -0
  134. package/docs/guides/REPORTING-GCS-MANAGED.md +56 -0
  135. package/docs/guides/REPORTING-OPERATIONS.md +4 -0
  136. package/docs/guides/SIGNING-GUIDE.md +20 -6
  137. package/docs/guides/VALIDATE-YOUR-AGENT.md +4 -0
  138. package/docs/llms.txt +2 -2
  139. package/docs/migration-14.x-rc-worksheet.md +5 -4
  140. package/docs/migration-buyer-agent-registry.md +1 -1
  141. package/package.json +19 -1
  142. package/skills/build-seller-agent/specialisms/signed-requests.md +2 -0
@@ -19,7 +19,7 @@
19
19
  * verifyApiKey,
20
20
  * anyOf,
21
21
  * verifySignatureAsAuthenticator,
22
- * mcpToolNameResolver,
22
+ * adcpOperationResolver,
23
23
  * } from '@adcp/sdk/server';
24
24
  *
25
25
  * serve(createAgent, {
@@ -28,7 +28,7 @@
28
28
  * verifySignatureAsAuthenticator({
29
29
  * jwks, replayStore, revocationStore,
30
30
  * capability: { supported: true, required_for: [], covers_content_digest: 'either' },
31
- * resolveOperation: mcpToolNameResolver,
31
+ * resolveOperation: adcpOperationResolver,
32
32
  * }),
33
33
  * ),
34
34
  * });
@@ -42,6 +42,7 @@ import type { JwksResolver } from '../signing/jwks.mjs';
42
42
  import { type ReplayStore } from '../signing/replay.mjs';
43
43
  import { type RevocationStore } from '../signing/revocation.mjs';
44
44
  import type { VerifiedSigner, VerifierCapability } from '../signing/types.mjs';
45
+ import { type OperationResolutionInput, type ResolvedOperation } from '../signing/operation-resolution.mjs';
45
46
  import { type AuthPrincipal, type Authenticator } from './auth.mjs';
46
47
  export interface VerifySignatureAsAuthenticatorOptions {
47
48
  /** Verifier capability block. `required_for` is not enforced here — unsigned
@@ -82,17 +83,22 @@ export interface VerifySignatureAsAuthenticatorOptions {
82
83
  * before this runs. Same semantics as
83
84
  * `ExpressMiddlewareOptions.resolveOperation` on {@link createExpressVerifier}.
84
85
  *
86
+ * Pass {@link adcpOperationResolver}: it resolves MCP `tools/call` and A2A
87
+ * `SendMessage` / `message/send` requests, and reports a body that does not
88
+ * resolve to exactly one operation as unresolvable, which the verifier
89
+ * rejects with `request_body_malformed`.
90
+ *
85
91
  * SECURITY: a resolver that always returns `undefined` disables
86
- * `capability.required_for` enforcement for this authenticator. Since the
87
- * bypass here is intentional (this adapter is for composition with other
88
- * authenticators — see the module docstring), the fall-through still
89
- * happens when the resolver returns `undefined`, so `required_for`
90
- * enforcement should live in a separate `preTransport`-mounted verifier
91
- * when composition is active.
92
+ * `capability.required_for` enforcement for this authenticator. This adapter
93
+ * is for composition with other authenticators (see the module docstring),
94
+ * so unsigned requests fall through to the next authenticator regardless;
95
+ * `required_for` enforcement on the unsigned path belongs to
96
+ * {@link requireSignatureWhenPresent}, which fails closed on an
97
+ * unresolvable body.
92
98
  */
93
99
  resolveOperation: (req: IncomingMessage & {
94
100
  rawBody?: string;
95
- }) => string | undefined;
101
+ }) => ResolvedOperation;
96
102
  /**
97
103
  * Override how the request's full URL is reconstructed. Use when the
98
104
  * server sits behind a TLS-terminating or path-rewriting load balancer
@@ -202,21 +208,37 @@ export interface RequireSignatureWhenPresentOptions {
202
208
  */
203
209
  requiredFor?: readonly string[];
204
210
  /**
205
- * Extract the AdCP operation name (or any identifier that can be
206
- * matched against `requiredFor`) from the incoming request.
211
+ * JSON-RPC protocol methods (e.g. `CancelTask`, `tasks/cancel`) that MUST be
212
+ * signed, matching `capability.protocol_methods_required_for`. Matched
213
+ * against the JSON-RPC `method` only, never against the resolved operation.
214
+ * Like {@link requiredFor}, a valid fallback credential satisfies it.
215
+ */
216
+ protocolMethodsRequiredFor?: readonly string[];
217
+ /**
218
+ * Resolve the AdCP operation a request carries. Defaults to
219
+ * {@link adcpOperationResolver}, which understands MCP `tools/call` and A2A
220
+ * `SendMessage` / `SendStreamingMessage` / `message/send` / `message/stream`.
207
221
  *
208
- * For MCP agents, pass the exported {@link mcpToolNameResolver} — it
209
- * implements the standard `tools/call` → `params.name` parse. For A2A
210
- * agents (or other non-JSON-RPC envelopes), supply a bespoke resolver.
222
+ * A resolver may return:
223
+ * - an operation name, matched against {@link requiredFor};
224
+ * - `undefined` for a request that carries no AdCP operation (discovery
225
+ * probes, `tasks/*`) — the request is not subject to {@link requiredFor};
226
+ * - {@link UNRESOLVABLE_OPERATION} for a body that does not resolve to
227
+ * exactly one operation. The gate rejects it with `request_body_malformed`
228
+ * before it looks at any signature header or credential, whether or not
229
+ * the request is signed. An ambiguous body is never treated as "an
230
+ * operation that is not in `requiredFor`".
211
231
  *
212
- * When `requiredFor` is set but `resolveOperation` is omitted OR
213
- * returns `undefined`, the pre-check is skipped — better to let the
214
- * downstream handler produce a precise `INVALID_REQUEST` than to
215
- * reject every unsigned call as signature-required.
232
+ * Omitting this option no longer disables the pre-check: a gate with
233
+ * `requiredFor` set and no resolver used to skip enforcement for every
234
+ * request. The default resolver is used instead. `requiredFor` and the
235
+ * resolver are only as good as each other — a resolver that returns
236
+ * `undefined` for A2A messages (such as {@link mcpToolNameResolver}) leaves
237
+ * `requiredFor` unenforced over A2A.
216
238
  */
217
239
  resolveOperation?: (req: IncomingMessage & {
218
240
  rawBody?: string;
219
- }) => string | undefined;
241
+ }) => ResolvedOperation;
220
242
  }
221
243
  /**
222
244
  * Compose a signature authenticator with a fallback under presence-gated
@@ -252,10 +274,12 @@ export interface RequireAuthenticatedOrSignedOptions {
252
274
  * declaration in `capabilities.request_signing.required_for`.
253
275
  */
254
276
  requiredFor?: readonly string[];
277
+ /** See {@link RequireSignatureWhenPresentOptions.protocolMethodsRequiredFor}. */
278
+ protocolMethodsRequiredFor?: readonly string[];
255
279
  /** See {@link RequireSignatureWhenPresentOptions.resolveOperation}. */
256
280
  resolveOperation?: (req: IncomingMessage & {
257
281
  rawBody?: string;
258
- }) => string | undefined;
282
+ }) => ResolvedOperation;
259
283
  }
260
284
  /**
261
285
  * Bundled composition of {@link requireSignatureWhenPresent} with an
@@ -279,7 +303,7 @@ export interface RequireAuthenticatedOrSignedOptions {
279
303
  * anyOf,
280
304
  * verifySignatureAsAuthenticator,
281
305
  * requireAuthenticatedOrSigned,
282
- * mcpToolNameResolver,
306
+ * adcpOperationResolver,
283
307
  * MUTATING_TASKS,
284
308
  * } from '@adcp/sdk/server';
285
309
  *
@@ -287,43 +311,57 @@ export interface RequireAuthenticatedOrSignedOptions {
287
311
  * authenticate: requireAuthenticatedOrSigned({
288
312
  * signature: verifySignatureAsAuthenticator({
289
313
  * jwks, replayStore, revocationStore, capability,
290
- * resolveOperation: mcpToolNameResolver,
314
+ * resolveOperation: adcpOperationResolver,
291
315
  * }),
292
316
  * fallback: anyOf(verifyApiKey({ keys }), verifyBearer({ jwksUri, issuer, audience })),
293
317
  * requiredFor: [...MUTATING_TASKS],
294
- * resolveOperation: mcpToolNameResolver,
318
+ * resolveOperation: adcpOperationResolver,
295
319
  * }),
296
320
  * });
297
321
  * ```
298
322
  */
299
323
  export declare function requireAuthenticatedOrSigned(options: RequireAuthenticatedOrSignedOptions): Authenticator;
300
324
  /**
301
- * Default `resolveOperation` for MCP agents. Parses the buffered JSON-RPC
302
- * body on `req.rawBody` and returns `params.name` when `method === 'tools/call'`.
303
- * Returns `undefined` for non-`tools/call` methods, a missing body, or
304
- * malformed JSON — the same "skip the pre-check and let the handler produce
305
- * a precise error" semantics every other `resolveOperation` in the SDK
306
- * uses.
325
+ * Resolve the AdCP operation a request carries, for MCP and A2A alike
326
+ * (adcp#7945, "Operation resolution over A2A"). Pass it as `resolveOperation`
327
+ * on {@link verifySignatureAsAuthenticator}, {@link requireSignatureWhenPresent},
328
+ * {@link requireAuthenticatedOrSigned}, or `createExpressVerifier` from
329
+ * `@adcp/sdk/signing`; it is the default for the composers.
307
330
  *
308
- * Pass directly as `resolveOperation` on {@link verifySignatureAsAuthenticator},
309
- * {@link requireSignatureWhenPresent}, {@link requireAuthenticatedOrSigned},
310
- * or `createExpressVerifier` from `@adcp/sdk/signing`:
331
+ * | Request | Result |
332
+ * |-------------------------------------------------------------------------|--------------------------------|
333
+ * | MCP `tools/call` | `params.name` |
334
+ * | A2A `SendMessage`, `SendStreamingMessage`, `message/send`, `message/stream` | `skill` of the sole DataPart |
335
+ * | any other JSON-RPC method (`tasks/*`, `CancelTask`, `initialize`, …) | `undefined` (no operation) |
336
+ * | non-`POST` request | `undefined` |
337
+ * | anything ambiguous | {@link UNRESOLVABLE_OPERATION} |
311
338
  *
312
- * ```ts
313
- * serve(createAgent, {
314
- * authenticate: requireAuthenticatedOrSigned({
315
- * signature: verifySignatureAsAuthenticator({
316
- * jwks, replayStore, revocationStore, capability,
317
- * resolveOperation: mcpToolNameResolver,
318
- * }),
319
- * fallback: anyOf(verifyApiKey({ keys }), verifyBearer({ jwksUri, issuer, audience })),
320
- * requiredFor: [...MUTATING_TASKS],
321
- * resolveOperation: mcpToolNameResolver,
322
- * }),
323
- * });
324
- * ```
339
+ * Ambiguous means: an empty or non-JSON body on a `POST`, a JSON-RPC batch or
340
+ * non-object body, a missing `method`, duplicate object keys (compared after
341
+ * JSON string decoding), a case variant of a recognized member name (`Skill`,
342
+ * `Parts`), zero or more than one DataPart, a FilePart (`raw`, `url`, `file`),
343
+ * a Part with other than one content member or a `kind` that disagrees with
344
+ * it, `data` that is not an object, or a `skill` / `params.name` that is not a
345
+ * non-empty string. Matching is exact and case-sensitive.
346
+ *
347
+ * The consumers reject {@link UNRESOLVABLE_OPERATION} with
348
+ * `request_body_malformed`, never as "an operation that is not in
349
+ * `required_for`". `req.rawBody` must hold the exact bytes the client sent.
350
+ */
351
+ export declare function adcpOperationResolver(req: OperationResolutionInput | (IncomingMessage & {
352
+ rawBody?: string | Buffer;
353
+ })): ResolvedOperation;
354
+ /**
355
+ * Resolver for MCP agents. Parses the buffered JSON-RPC body on `req.rawBody`
356
+ * and returns `params.name` when `method === 'tools/call'`; returns
357
+ * `undefined` for every other method, a missing body, or malformed JSON.
325
358
  *
326
- * A2A agents use a different envelope — write a bespoke resolver there.
359
+ * **MCP only, and lenient.** It does not understand A2A: an A2A
360
+ * `SendMessage` resolves to `undefined`, so a `requiredFor` list guarded by
361
+ * this resolver is NOT enforced over A2A. It also treats an ambiguous body as
362
+ * "no operation". Prefer {@link adcpOperationResolver}, which resolves A2A and
363
+ * reports ambiguity as {@link UNRESOLVABLE_OPERATION}. Kept for callers that
364
+ * pin the previous behavior.
327
365
  */
328
366
  export declare function mcpToolNameResolver(req: {
329
367
  rawBody?: string;
@@ -19,7 +19,7 @@
19
19
  * verifyApiKey,
20
20
  * anyOf,
21
21
  * verifySignatureAsAuthenticator,
22
- * mcpToolNameResolver,
22
+ * adcpOperationResolver,
23
23
  * } from '@adcp/sdk/server';
24
24
  *
25
25
  * serve(createAgent, {
@@ -28,7 +28,7 @@
28
28
  * verifySignatureAsAuthenticator({
29
29
  * jwks, replayStore, revocationStore,
30
30
  * capability: { supported: true, required_for: [], covers_content_digest: 'either' },
31
- * resolveOperation: mcpToolNameResolver,
31
+ * resolveOperation: adcpOperationResolver,
32
32
  * }),
33
33
  * ),
34
34
  * });
@@ -42,6 +42,7 @@ import type { JwksResolver } from '../signing/jwks';
42
42
  import { type ReplayStore } from '../signing/replay';
43
43
  import { type RevocationStore } from '../signing/revocation';
44
44
  import type { VerifiedSigner, VerifierCapability } from '../signing/types';
45
+ import { type OperationResolutionInput, type ResolvedOperation } from '../signing/operation-resolution';
45
46
  import { type AuthPrincipal, type Authenticator } from './auth';
46
47
  export interface VerifySignatureAsAuthenticatorOptions {
47
48
  /** Verifier capability block. `required_for` is not enforced here — unsigned
@@ -82,17 +83,22 @@ export interface VerifySignatureAsAuthenticatorOptions {
82
83
  * before this runs. Same semantics as
83
84
  * `ExpressMiddlewareOptions.resolveOperation` on {@link createExpressVerifier}.
84
85
  *
86
+ * Pass {@link adcpOperationResolver}: it resolves MCP `tools/call` and A2A
87
+ * `SendMessage` / `message/send` requests, and reports a body that does not
88
+ * resolve to exactly one operation as unresolvable, which the verifier
89
+ * rejects with `request_body_malformed`.
90
+ *
85
91
  * SECURITY: a resolver that always returns `undefined` disables
86
- * `capability.required_for` enforcement for this authenticator. Since the
87
- * bypass here is intentional (this adapter is for composition with other
88
- * authenticators — see the module docstring), the fall-through still
89
- * happens when the resolver returns `undefined`, so `required_for`
90
- * enforcement should live in a separate `preTransport`-mounted verifier
91
- * when composition is active.
92
+ * `capability.required_for` enforcement for this authenticator. This adapter
93
+ * is for composition with other authenticators (see the module docstring),
94
+ * so unsigned requests fall through to the next authenticator regardless;
95
+ * `required_for` enforcement on the unsigned path belongs to
96
+ * {@link requireSignatureWhenPresent}, which fails closed on an
97
+ * unresolvable body.
92
98
  */
93
99
  resolveOperation: (req: IncomingMessage & {
94
100
  rawBody?: string;
95
- }) => string | undefined;
101
+ }) => ResolvedOperation;
96
102
  /**
97
103
  * Override how the request's full URL is reconstructed. Use when the
98
104
  * server sits behind a TLS-terminating or path-rewriting load balancer
@@ -202,21 +208,37 @@ export interface RequireSignatureWhenPresentOptions {
202
208
  */
203
209
  requiredFor?: readonly string[];
204
210
  /**
205
- * Extract the AdCP operation name (or any identifier that can be
206
- * matched against `requiredFor`) from the incoming request.
211
+ * JSON-RPC protocol methods (e.g. `CancelTask`, `tasks/cancel`) that MUST be
212
+ * signed, matching `capability.protocol_methods_required_for`. Matched
213
+ * against the JSON-RPC `method` only, never against the resolved operation.
214
+ * Like {@link requiredFor}, a valid fallback credential satisfies it.
215
+ */
216
+ protocolMethodsRequiredFor?: readonly string[];
217
+ /**
218
+ * Resolve the AdCP operation a request carries. Defaults to
219
+ * {@link adcpOperationResolver}, which understands MCP `tools/call` and A2A
220
+ * `SendMessage` / `SendStreamingMessage` / `message/send` / `message/stream`.
207
221
  *
208
- * For MCP agents, pass the exported {@link mcpToolNameResolver} — it
209
- * implements the standard `tools/call` → `params.name` parse. For A2A
210
- * agents (or other non-JSON-RPC envelopes), supply a bespoke resolver.
222
+ * A resolver may return:
223
+ * - an operation name, matched against {@link requiredFor};
224
+ * - `undefined` for a request that carries no AdCP operation (discovery
225
+ * probes, `tasks/*`) — the request is not subject to {@link requiredFor};
226
+ * - {@link UNRESOLVABLE_OPERATION} for a body that does not resolve to
227
+ * exactly one operation. The gate rejects it with `request_body_malformed`
228
+ * before it looks at any signature header or credential, whether or not
229
+ * the request is signed. An ambiguous body is never treated as "an
230
+ * operation that is not in `requiredFor`".
211
231
  *
212
- * When `requiredFor` is set but `resolveOperation` is omitted OR
213
- * returns `undefined`, the pre-check is skipped — better to let the
214
- * downstream handler produce a precise `INVALID_REQUEST` than to
215
- * reject every unsigned call as signature-required.
232
+ * Omitting this option no longer disables the pre-check: a gate with
233
+ * `requiredFor` set and no resolver used to skip enforcement for every
234
+ * request. The default resolver is used instead. `requiredFor` and the
235
+ * resolver are only as good as each other — a resolver that returns
236
+ * `undefined` for A2A messages (such as {@link mcpToolNameResolver}) leaves
237
+ * `requiredFor` unenforced over A2A.
216
238
  */
217
239
  resolveOperation?: (req: IncomingMessage & {
218
240
  rawBody?: string;
219
- }) => string | undefined;
241
+ }) => ResolvedOperation;
220
242
  }
221
243
  /**
222
244
  * Compose a signature authenticator with a fallback under presence-gated
@@ -252,10 +274,12 @@ export interface RequireAuthenticatedOrSignedOptions {
252
274
  * declaration in `capabilities.request_signing.required_for`.
253
275
  */
254
276
  requiredFor?: readonly string[];
277
+ /** See {@link RequireSignatureWhenPresentOptions.protocolMethodsRequiredFor}. */
278
+ protocolMethodsRequiredFor?: readonly string[];
255
279
  /** See {@link RequireSignatureWhenPresentOptions.resolveOperation}. */
256
280
  resolveOperation?: (req: IncomingMessage & {
257
281
  rawBody?: string;
258
- }) => string | undefined;
282
+ }) => ResolvedOperation;
259
283
  }
260
284
  /**
261
285
  * Bundled composition of {@link requireSignatureWhenPresent} with an
@@ -279,7 +303,7 @@ export interface RequireAuthenticatedOrSignedOptions {
279
303
  * anyOf,
280
304
  * verifySignatureAsAuthenticator,
281
305
  * requireAuthenticatedOrSigned,
282
- * mcpToolNameResolver,
306
+ * adcpOperationResolver,
283
307
  * MUTATING_TASKS,
284
308
  * } from '@adcp/sdk/server';
285
309
  *
@@ -287,43 +311,57 @@ export interface RequireAuthenticatedOrSignedOptions {
287
311
  * authenticate: requireAuthenticatedOrSigned({
288
312
  * signature: verifySignatureAsAuthenticator({
289
313
  * jwks, replayStore, revocationStore, capability,
290
- * resolveOperation: mcpToolNameResolver,
314
+ * resolveOperation: adcpOperationResolver,
291
315
  * }),
292
316
  * fallback: anyOf(verifyApiKey({ keys }), verifyBearer({ jwksUri, issuer, audience })),
293
317
  * requiredFor: [...MUTATING_TASKS],
294
- * resolveOperation: mcpToolNameResolver,
318
+ * resolveOperation: adcpOperationResolver,
295
319
  * }),
296
320
  * });
297
321
  * ```
298
322
  */
299
323
  export declare function requireAuthenticatedOrSigned(options: RequireAuthenticatedOrSignedOptions): Authenticator;
300
324
  /**
301
- * Default `resolveOperation` for MCP agents. Parses the buffered JSON-RPC
302
- * body on `req.rawBody` and returns `params.name` when `method === 'tools/call'`.
303
- * Returns `undefined` for non-`tools/call` methods, a missing body, or
304
- * malformed JSON — the same "skip the pre-check and let the handler produce
305
- * a precise error" semantics every other `resolveOperation` in the SDK
306
- * uses.
325
+ * Resolve the AdCP operation a request carries, for MCP and A2A alike
326
+ * (adcp#7945, "Operation resolution over A2A"). Pass it as `resolveOperation`
327
+ * on {@link verifySignatureAsAuthenticator}, {@link requireSignatureWhenPresent},
328
+ * {@link requireAuthenticatedOrSigned}, or `createExpressVerifier` from
329
+ * `@adcp/sdk/signing`; it is the default for the composers.
307
330
  *
308
- * Pass directly as `resolveOperation` on {@link verifySignatureAsAuthenticator},
309
- * {@link requireSignatureWhenPresent}, {@link requireAuthenticatedOrSigned},
310
- * or `createExpressVerifier` from `@adcp/sdk/signing`:
331
+ * | Request | Result |
332
+ * |-------------------------------------------------------------------------|--------------------------------|
333
+ * | MCP `tools/call` | `params.name` |
334
+ * | A2A `SendMessage`, `SendStreamingMessage`, `message/send`, `message/stream` | `skill` of the sole DataPart |
335
+ * | any other JSON-RPC method (`tasks/*`, `CancelTask`, `initialize`, …) | `undefined` (no operation) |
336
+ * | non-`POST` request | `undefined` |
337
+ * | anything ambiguous | {@link UNRESOLVABLE_OPERATION} |
311
338
  *
312
- * ```ts
313
- * serve(createAgent, {
314
- * authenticate: requireAuthenticatedOrSigned({
315
- * signature: verifySignatureAsAuthenticator({
316
- * jwks, replayStore, revocationStore, capability,
317
- * resolveOperation: mcpToolNameResolver,
318
- * }),
319
- * fallback: anyOf(verifyApiKey({ keys }), verifyBearer({ jwksUri, issuer, audience })),
320
- * requiredFor: [...MUTATING_TASKS],
321
- * resolveOperation: mcpToolNameResolver,
322
- * }),
323
- * });
324
- * ```
339
+ * Ambiguous means: an empty or non-JSON body on a `POST`, a JSON-RPC batch or
340
+ * non-object body, a missing `method`, duplicate object keys (compared after
341
+ * JSON string decoding), a case variant of a recognized member name (`Skill`,
342
+ * `Parts`), zero or more than one DataPart, a FilePart (`raw`, `url`, `file`),
343
+ * a Part with other than one content member or a `kind` that disagrees with
344
+ * it, `data` that is not an object, or a `skill` / `params.name` that is not a
345
+ * non-empty string. Matching is exact and case-sensitive.
346
+ *
347
+ * The consumers reject {@link UNRESOLVABLE_OPERATION} with
348
+ * `request_body_malformed`, never as "an operation that is not in
349
+ * `required_for`". `req.rawBody` must hold the exact bytes the client sent.
350
+ */
351
+ export declare function adcpOperationResolver(req: OperationResolutionInput | (IncomingMessage & {
352
+ rawBody?: string | Buffer;
353
+ })): ResolvedOperation;
354
+ /**
355
+ * Resolver for MCP agents. Parses the buffered JSON-RPC body on `req.rawBody`
356
+ * and returns `params.name` when `method === 'tools/call'`; returns
357
+ * `undefined` for every other method, a missing body, or malformed JSON.
325
358
  *
326
- * A2A agents use a different envelope — write a bespoke resolver there.
359
+ * **MCP only, and lenient.** It does not understand A2A: an A2A
360
+ * `SendMessage` resolves to `undefined`, so a `requiredFor` list guarded by
361
+ * this resolver is NOT enforced over A2A. It also treats an ambiguous body as
362
+ * "no operation". Prefer {@link adcpOperationResolver}, which resolves A2A and
363
+ * reports ambiguity as {@link UNRESOLVABLE_OPERATION}. Kept for callers that
364
+ * pin the previous behavior.
327
365
  */
328
366
  export declare function mcpToolNameResolver(req: {
329
367
  rawBody?: string;
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var auth_signature_exports = {};
20
20
  __export(auth_signature_exports, {
21
+ adcpOperationResolver: () => adcpOperationResolver,
21
22
  mcpToolNameResolver: () => mcpToolNameResolver,
22
23
  principalForVerifiedSigner: () => principalForVerifiedSigner,
23
24
  requireAuthenticatedOrSigned: () => requireAuthenticatedOrSigned,
@@ -28,6 +29,7 @@ module.exports = __toCommonJS(auth_signature_exports);
28
29
  var import_errors = require('../signing/errors.js');
29
30
  var import_replay = require('../signing/replay.js');
30
31
  var import_revocation = require('../signing/revocation.js');
32
+ var import_operation_resolution = require('../signing/operation-resolution.js');
31
33
  var import_verifier = require('../signing/verifier.js');
32
34
  var import_buyer_agent = require('./decisioning/buyer-agent.js');
33
35
  var import_auth = require('./auth.js');
@@ -112,8 +114,18 @@ function principalForVerifiedSigner(signer, makePrincipal) {
112
114
  }
113
115
  function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {}) {
114
116
  const requiredFor = new Set(options.requiredFor ?? []);
115
- const resolveOperation = options.resolveOperation;
117
+ const unsignedCapability = {
118
+ required_for: [...requiredFor],
119
+ ...options.protocolMethodsRequiredFor ? { protocol_methods_required_for: [...options.protocolMethodsRequiredFor] } : {}
120
+ };
121
+ const resolveOperation = options.resolveOperation ?? (requiredFor.size > 0 || unsignedCapability.protocol_methods_required_for ? adcpOperationResolver : void 0);
116
122
  const combined = async (req) => {
123
+ const request = req;
124
+ const operation = resolveOperation?.(request);
125
+ if ((0, import_operation_resolution.isUnresolvableOperation)(operation)) {
126
+ logUnresolvable(request);
127
+ throw signatureAuthError((0, import_verifier.unresolvableOperationError)());
128
+ }
117
129
  if (hasSignatureHeader(req)) {
118
130
  const result = await signatureAuth(req);
119
131
  if (result === null) {
@@ -121,6 +133,12 @@ function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {})
121
133
  }
122
134
  return result;
123
135
  }
136
+ const body = bodyText(request);
137
+ try {
138
+ (0, import_verifier.assertUnsignedWebhookAuthenticationAbsent)(body);
139
+ } catch (err) {
140
+ throw asSignatureAuthError(err);
141
+ }
124
142
  let fallbackResult = null;
125
143
  let fallbackError;
126
144
  let fallbackThrew = false;
@@ -131,23 +149,16 @@ function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {})
131
149
  fallbackError = err;
132
150
  }
133
151
  if (fallbackResult !== null) return fallbackResult;
134
- if (requiredFor.size > 0 && resolveOperation) {
135
- const operation = resolveOperation(req);
136
- if (operation && requiredFor.has(operation)) {
137
- throw new import_auth.AuthError(`Signature required for ${operation}.`, {
138
- cause: new import_errors.RequestSignatureError(
139
- "request_signature_required",
140
- 0,
141
- `Operation ${operation} requires an RFC 9421 request signature when no other credentials are presented.`
142
- )
143
- });
144
- }
152
+ try {
153
+ (0, import_verifier.assertUnsignedOperationNotRequired)(operation, unsignedCapability);
154
+ (0, import_verifier.assertUnsignedProtocolMethodNotRequired)(body, unsignedCapability);
155
+ } catch (err) {
156
+ throw asSignatureAuthError(err);
145
157
  }
146
158
  if (fallbackThrew) throw fallbackError;
147
159
  return null;
148
160
  };
149
- const anyChildNeedsRawBody = (0, import_auth.authenticatorNeedsRawBody)(signatureAuth) || (0, import_auth.authenticatorNeedsRawBody)(fallbackAuth);
150
- if (anyChildNeedsRawBody || resolveOperation) {
161
+ if (resolveOperation || (0, import_auth.authenticatorNeedsRawBody)(signatureAuth) || (0, import_auth.authenticatorNeedsRawBody)(fallbackAuth)) {
151
162
  (0, import_auth.tagAuthenticatorNeedsRawBody)(combined);
152
163
  }
153
164
  (0, import_auth.tagAuthenticatorPresenceGated)(combined);
@@ -156,9 +167,13 @@ function requireSignatureWhenPresent(signatureAuth, fallbackAuth, options = {})
156
167
  function requireAuthenticatedOrSigned(options) {
157
168
  return requireSignatureWhenPresent(options.signature, options.fallback, {
158
169
  requiredFor: options.requiredFor,
170
+ protocolMethodsRequiredFor: options.protocolMethodsRequiredFor,
159
171
  resolveOperation: options.resolveOperation
160
172
  });
161
173
  }
174
+ function adcpOperationResolver(req) {
175
+ return (0, import_operation_resolution.resolveRequestOperation)(operationInput(req));
176
+ }
162
177
  function mcpToolNameResolver(req) {
163
178
  const raw = req.rawBody;
164
179
  if (!raw) return void 0;
@@ -171,6 +186,24 @@ function mcpToolNameResolver(req) {
171
186
  return void 0;
172
187
  }
173
188
  }
189
+ function operationInput(req) {
190
+ const candidate = req;
191
+ return { rawBody: candidate.rawBody, method: candidate.method, url: candidate.url };
192
+ }
193
+ function bodyText(req) {
194
+ const raw = req.rawBody;
195
+ return typeof raw === "string" ? raw : raw?.toString("utf8");
196
+ }
197
+ function signatureAuthError(err) {
198
+ return new import_auth.AuthError(`Signature rejected (${err.code}).`, { cause: err });
199
+ }
200
+ function asSignatureAuthError(err) {
201
+ return err instanceof import_errors.RequestSignatureError ? signatureAuthError(err) : err;
202
+ }
203
+ function logUnresolvable(req) {
204
+ const reason = (0, import_operation_resolution.describeUnresolvable)(operationInput(req));
205
+ console.error(`[adcp/auth] request body does not resolve to one operation: ${reason ?? "unknown"}`);
206
+ }
174
207
  const SAFE_KEYID = /^[A-Za-z0-9._-]{1,256}$/;
175
208
  function hasSignatureHeader(req) {
176
209
  return nonEmptyHeader(req.headers["signature-input"]) || nonEmptyHeader(req.headers["signature"]);
@@ -204,6 +237,7 @@ function firstHeader(value) {
204
237
  }
205
238
  // Annotate the CommonJS export names for ESM import in node:
206
239
  0 && (module.exports = {
240
+ adcpOperationResolver,
207
241
  mcpToolNameResolver,
208
242
  principalForVerifiedSigner,
209
243
  requireAuthenticatedOrSigned,