@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
@@ -291,7 +291,10 @@ const a2a = createA2AAdapter({
291
291
  });
292
292
 
293
293
  const app = express();
294
- app.use(express.json());
294
+ // With `signedRequests` configured, record the exact request bytes so the
295
+ // signature gate can verify them: pass `verify: a2a.rawBodyVerify`, or mount the
296
+ // adapter before any body parser (it buffers the body itself).
297
+ app.use(express.json({ verify: a2a.rawBodyVerify }));
295
298
  // mount() wires: JSON-RPC at the `agentCard.url` pathname (`/a2a` here),
296
299
  // the agent card at both `{basePath}/.well-known/agent-card.json` (A2A
297
300
  // SDK discovery derives this) AND `/.well-known/agent-card.json` (origin-
@@ -301,6 +304,8 @@ a2a.mount(app);
301
304
  app.listen(3000);
302
305
  ```
303
306
 
307
+ **Request signing over A2A.** If the server you pass to `createA2AAdapter` was built with `createAdcpServer({ signedRequests })`, the adapter runs the same RFC 9421 verifier on the JSON-RPC endpoint that `serve()` runs on MCP, so `request_signing.required_for` and `protocol_methods_required_for` hold on both transports. The adapter resolves the AdCP operation from the Message's sole DataPart `skill`, rejects a body that does not resolve to exactly one operation (`request_body_malformed`: zero or several DataParts, a FilePart, a duplicate or case-variant `skill` key, a non-string `skill`, a batch, a non-JSON body) before any handler runs, and dispatches only that operation. Rejections are HTTP 401 with `WWW-Authenticate: Signature error="<code>"`, issued before `authenticate` runs; a verified signature reaches handlers as `ctx.authInfo` with `clientId: signing:<keyid>`. Because the gate needs the exact signed bytes, do not mount a body parser ahead of the adapter unless it records them (`express.json({ verify: a2a.rawBodyVerify })`); a request whose raw body is unavailable gets a 500 and is never dispatched. If you wired the verifier by hand (`serve({ preTransport })`), pass it as `createA2AAdapter({ preTransport })`: the adapter refuses to construct when the server advertises request signing but nothing verifies signatures on A2A, unless you set `allowUnenforcedSignedRequests: true` because a gateway enforces it. `protocol_methods_required_for` matches the JSON-RPC `method` by exact name, and the A2A SDK serves its task methods under both the 1.0 and 0.3 names while `legacyCompat` is on, so list both (`CancelTask` and `tasks/cancel`). Resolve operations yourself with `adcpOperationResolver` (MCP `tools/call` and A2A message methods); `mcpToolNameResolver` is MCP-only and leaves `required_for` unenforced over A2A.
308
+
304
309
  The adapter enables the official A2A SDK's v0.3 compatibility layer by
305
310
  default, preserving existing sellers and buyers. For a native A2A 1.0-only
306
311
  server, pass `legacyCompat: { enabled: false }` to `createA2AAdapter`. This
@@ -669,7 +674,7 @@ import {
669
674
  verifySignatureAsAuthenticator,
670
675
  verifyApiKey,
671
676
  requireAuthenticatedOrSigned,
672
- mcpToolNameResolver,
677
+ adcpOperationResolver,
673
678
  } from '@adcp/sdk/server';
674
679
  import { ResolvedAgentJwksResolver } from '@adcp/sdk/signing/server';
675
680
 
@@ -693,11 +698,11 @@ serve(
693
698
  signature: verifySignatureAsAuthenticator({
694
699
  capability: { supported: true, required_for: ['create_media_buy', 'update_media_buy'], covers_content_digest: 'either' },
695
700
  jwks: new ResolvedAgentJwksResolver('https://buyer.example/mcp', 'mcp'),
696
- resolveOperation: mcpToolNameResolver,
701
+ resolveOperation: adcpOperationResolver,
697
702
  }),
698
703
  fallback: verifyApiKey({ keys: { sk_live_abc: { principal: 'acct_42' } } }),
699
704
  requiredFor: ['create_media_buy', 'update_media_buy'],
700
- resolveOperation: mcpToolNameResolver,
705
+ resolveOperation: adcpOperationResolver,
701
706
  }),
702
707
  }
703
708
  );
@@ -135,6 +135,40 @@ tool is never reused. Unknown peers receive a legacy projection only when the
135
135
  selected product provides one unambiguous ref. Ambiguous or unavailable mappings throw
136
136
  `CreativeFormatProjectionError` before transport.
137
137
 
138
+ Before building creatives, ask the client which mode that write will use:
139
+
140
+ ```ts
141
+ const mode = await agent.resolveCreativeFormatWireMode('sync_creatives');
142
+ ```
143
+
144
+ This method is available on `AgentClient` and `SingleAgentClient`, accepts
145
+ `sync_creatives`, `create_media_buy`, or `update_media_buy`, and shares the
146
+ write path's capability discovery, tool-schema fallback, and buyer wire pin.
147
+ It reuses fresh cached or primed evidence; cold discovery may make read calls,
148
+ but it sends no mutation. Pass `{ signal, transport }` as the optional second
149
+ argument to use the same cancellation and transport scope as the write.
150
+
151
+ The result is `canonical`, `legacy`, or `unknown` (`CreativeFormatWireMode`).
152
+ For older peers with insufficient evidence, `unknown` means projection still
153
+ requires one unambiguous legacy reference. It does not promise canonical
154
+ support. Conflicting capability/schema evidence or missing release proof with
155
+ a 3.2+ buyer pin throws `CreativeFormatCapabilityError`, as during a write.
156
+ The standalone `resolveCreativeFormatWireMode(capabilities, version)` helper
157
+ only evaluates capability declarations; use the client method when you need
158
+ the tool-schema fallback too.
159
+
160
+ For a manual projection check, import `projectSyncCreativesForDelivery` from
161
+ `@adcp/sdk/v2/projection`. Matching the write requires the same mode, converter,
162
+ resolver, and selector inputs. Include a selector container for every assigned
163
+ `package_id`, including packages that have no selected format; `syncCreatives()`
164
+ adds those missing containers automatically. Client-configured catalogs and
165
+ cached package routes can also supply projection inputs, so this helper is not
166
+ a dry run of the client's full projection pipeline.
167
+
168
+ Resolve again after installing new evidence with `primeCapabilities()`, calling
169
+ `SingleAgentClient.refreshCapabilities()`, or changing authorization or
170
+ transport scope. Scoped transports rediscover evidence for each call.
171
+
138
172
  Use `packageRefsForFormatOptions(product, selectedIds)` when authoring package
139
173
  selectors. It returns only canonical `format_option_refs`. The SDK carries any
140
174
  legacy downgrade material in module-private weak storage, which survives package
@@ -0,0 +1,52 @@
1
+ # Durable GCS reporting write fence
2
+
3
+ `@adcp/sdk/reporting/gcs` provides a provider write-fence primitive backed by the existing PostgreSQL destination authority. It is a building block for a managed delivery adapter; it does not advertise Managed Delivery or Reconciled Billing capabilities or implement `ReportingManagedDeliveryAdapterV1`.
4
+
5
+ Install the optional official Google client with `npm install @google-cloud/storage@^7.22.0` for Node 20 or Node 22+. Google client `^8.2.0` is also supported on Node 22+. This peer is required when compiling or using the GCS subpath, including its public types; other SDK subpaths remain independent. Apply `REPORTING_LEDGER_MIGRATION`, `REPORTING_MANAGED_DELIVERY_MIGRATION`, then the additive `REPORTING_OBJECT_WRITE_MIGRATION` from `@adcp/sdk/reporting/ledger` in the same isolated tenant schema. Apply the final migration to a fresh object-write schema if experimenting with earlier, unpublished versions of these tables: `CREATE TABLE IF NOT EXISTS` does not upgrade incompatible prerelease tables.
6
+
7
+ ```typescript
8
+ import { Storage } from '@google-cloud/storage';
9
+ import { createGcsReportingObjectFenceV1 } from '@adcp/sdk/reporting/gcs';
10
+
11
+ const fence = createGcsReportingObjectFenceV1({
12
+ storage: new Storage({ retryOptions: { totalTimeout: 60, maxRetries: 3 } }), // Host-owned ADC; retry timeout is in seconds.
13
+ store, // PostgresReportingManagedDeliveryStore in the tenant's isolated schema.
14
+ bucket: hostConfiguredBucket,
15
+ namespace: deploymentNamespace,
16
+ });
17
+ const plan = await fence.register(scope, immutableDeliveryId, [rowsBytes, manifestBytes]);
18
+ await fence.write(plan, 0, rowsBytes, { signal });
19
+ await fence.write(plan, 1, manifestBytes, { signal });
20
+ ```
21
+
22
+ The host derives `scope` (`account_id`, `destination_ref`, `generation`), delivery identity, bucket and namespace from authenticated, saved configuration. These are trusted server methods, not an authorization API for buyer-supplied identifiers. Bucket names currently accept lowercase letters, digits, dots and hyphens; underscores are outside this primitive’s accepted input subset. Use a deployment namespace unique across isolated databases sharing a bucket. Identities are hashed into deterministic object names; credentials, signed URLs, resumable session URIs and other secrets never belong in plans or namespaces.
23
+
24
+ Registration freezes every writable object name and byte hash under the same account/destination advisory locks as `revokeDestination`. It binds the bucket and namespace digest immutably to that authorization generation. Changes require a new generation. Each plan allows at most 128 objects and 64 MiB in aggregate. Writes use `ifGenerationMatch: 0`; retries inspect and hash the exact existing generation, and refuse tombstones or different content.
25
+
26
+ Commit the existing durable destination revocation before provider cleanup:
27
+
28
+ ```typescript
29
+ await store.revokeDestination({ ...scope, revoked_at: new Date().toISOString() });
30
+ let complete = false;
31
+ while (!complete) {
32
+ ({ complete } = await fence.revoke(scope, { signal, maxObjects: 10 }));
33
+ }
34
+ ```
35
+
36
+ The default `maxObjects` page is 10. Choose a small page for remote providers. If cleanup exceeds its deadline, catch `DEADLINE_EXCEEDED` and resume in another worker tick with a fresh signal; previously recorded progress remains durable. Never interpret a thrown timeout as completed cleanup.
37
+
38
+ Cleanup routes through the stored provider binding even if the host's current factory namespace or bucket changes. The bucket policy is probed even when inventory is already fully fenced, so keep the bucket and credentials available until the cleanup lease has completed. Alert on repeated `PROVIDER_UNAVAILABLE` for a scope: one failing object can prevent the page from progressing, and provider permissions or an excluded external writer require administrator remediation. The injected Google client must retain access to every old bound bucket until cleanup completes. A page replaces each registered name, including pending uploads, with a persistent empty tombstone using a generation precondition, verifies the tombstone, and records progress. The first recorded tombstone generation is immutable. A new worker can resume after a provider write succeeds but SQL recording fails. `complete` means the closed inventory has no unfenced names, not that the host has completed a managed-delivery cleanup lease; that integration remains the adapter's responsibility.
39
+
40
+ The bucket must have uniform access and enforced public access prevention, explicitly disabled soft delete, disabled versioning, no retention policy, no default event hold, no enabled object retention and no lifecycle rules. `probe()` fails closed for incompatible policies; `storage.buckets.get`, `storage.objects.get`, `storage.objects.create` and `storage.objects.delete` are required: replacing existing bytes with a tombstone needs delete permission as well as create permission. Metadata-update permission alone cannot overwrite an object. This lists operational requirements, not a least-privilege deployment qualification; see [Google upload permissions](https://docs.cloud.google.com/storage/docs/uploading-objects). Provision these settings before any report bytes are stored. Disabling soft delete or versioning later does not erase existing recoverable generations. Use a dedicated fresh bucket, with policy and metadata changes restricted to trusted host administrators. The primitive cannot fence an administrator who changes policy, restores old bytes, deletes tombstones or writes without the create-only contract.
41
+
42
+ If a probe reports `UNSAFE_BUCKET`, stop new deliveries, preserve inventory and report incomplete revocation. Restore the original safe bucket settings under the host administrator’s control, inspect archived or soft-deleted generations created during drift, remove their consumer access with provider administration, then resume cleanup and verify old-version denial. The primitive refuses to claim complete fencing while policy makes old bytes recoverable; restoring settings alone cannot establish that those old generations are gone.
43
+
44
+ Retain write inventory and tombstones for as long as any stale request can execute. Do not prune them, expire them with lifecycle rules, or delete then recreate an object. Required audit bytes belong in a separate owner-only archive unavailable to revoked consumers; archival retention is not implemented here.
45
+
46
+ Operations have a deadline of at most 60 seconds and accept caller cancellation. Expiry stops new follow-on operations initiated by this primitive and destroys active verification streams. An already-issued Google client operation can continue its internal retries after expiry; configure `retryOptions.totalTimeout` (seconds) and `maxRetries` to bound that work. An already-issued upload or database commit can have an unknown outcome after expiry. See the [Google retry strategy](https://docs.cloud.google.com/storage/docs/retry-strategy). Retry from the frozen plan or closed inventory; never assume timeout proves that nothing was written. PostgreSQL methods honor cancellation between queries and before commit, with five-second lock and ten-second statement timeouts when a signal is provided. Configure a finite connection acquisition timeout in the host's pool. Rollback and connection release still run after cancellation.
47
+
48
+ `ReportingGcsFenceError.code` is stable (`INVALID_INPUT`, `UNSAFE_BUCKET`, `REVOKED`, `NOT_REVOKED`, `CONTENT_CONFLICT`, `ABORTED`, `DEADLINE_EXCEEDED`, `PROVIDER_UNAVAILABLE`, `STATE_UNAVAILABLE`). `NOT_REVOKED` includes a missing generation: commit the durable revocation before cleanup. All operations reject a promise on failure; switch on `code` rather than cross-module `instanceof` checks. Signals must be native Node `AbortSignal` instances. Public errors omit raw provider/store causes. Upload success still needs physical report verification and generation-fenced availability settlement through the existing managed delivery runtime; this primitive supplies neither contract reads nor buyer credentials.
49
+
50
+ The live worker at `scripts/reporting-interop/gcs-fence.cjs` exercises real resumable uploads finishing after expiry/revocation, committed old-generation denial, process recovery, namespace rotation, deadline cancellation and error privacy against an installed candidate package, real PostgreSQL and a private temporary GCS bucket. Its pinned report fixtures are test evidence, not production data. Qualification of this primitive does not establish a complete adapter, independent Python buyer verification, or least-privilege production deployment.
51
+
52
+ Provider guarantees: [request preconditions](https://docs.cloud.google.com/storage/docs/request-preconditions), [consistency](https://docs.cloud.google.com/storage/docs/consistency), and [bucket policy fields](https://docs.cloud.google.com/storage/docs/json_api/v1/buckets).
@@ -0,0 +1,56 @@
1
+ # Managed reporting delivery on GCS
2
+
3
+ `@adcp/sdk/reporting/gcs` supplies a complete `ReportingManagedDeliveryAdapterV1` for private GCS file transfer. It supports `manifest_checksums` and `canonical_digest` by uploading deterministic JSONL and manifest bytes, then inspecting actual provider bytes and digest-pinned private contracts. The managed runtime owns materialization settlement, cleanup leases, retries and capability publication. Verification is attributed to the producer; independent buyer qualification is a separate gate.
4
+
5
+ Install the optional official `@google-cloud/storage` peer. Use 7.22.x on Node 20.19 or later; Storage 8.2.x requires Node 22 or later. The default SDK import does not load Google Storage. Supply a host-owned official Storage client using ADC, workload identity or short-lived impersonation. Never persist credentials in reporting descriptors, contract URIs, `ctx_metadata`, inventory or error envelopes.
6
+
7
+ Apply `REPORTING_LEDGER_MIGRATION`, the applicable Core writer-fence migration, `REPORTING_MANAGED_DELIVERY_MIGRATION`, `REPORTING_OBJECT_WRITE_MIGRATION`, then `REPORTING_OBJECT_WRITE_AUTHORITY_MIGRATION` from `@adcp/sdk/reporting/ledger`. The last migration requires PostgreSQL 13 or later and creates one persistent installation UUID per isolated schema. Reapplying it preserves the UUID. Keep that row in backups and restores. **Never run delivery or cleanup from an independently writable clone of live authorization, inventory or materialization tables.** Such a clone would still address the original objects. Start a fresh isolated schema with a distinct deployment namespace and installation UUID, provision new destination grants, and import only the needed immutable Core facts. A restored replica of the same authority keeps its identity. Do not replace an identity while old write plans still exist. The adapter combines the UUID with your stable deployment namespace to avoid collisions between isolated schemas sharing a bucket.
8
+
9
+ Core and managed stores must share the exact same PostgreSQL pool, and Core must enable `managedDelivery: true`. The factory probes both stores and the provider policy before returning an adapter. Hosts supply pool acquisition/connect timeouts; transactions started with a signal additionally limit lock and statement waits. Readiness is bounded by the operation deadline, at most 60 seconds.
10
+
11
+ ```typescript
12
+ import { Storage } from '@google-cloud/storage';
13
+ import { createGcsReportingManagedDeliveryAdapterV1 } from '@adcp/sdk/reporting/gcs';
14
+ import { createReportingManagedDeliveryRuntime } from '@adcp/sdk/reporting/ledger';
15
+
16
+ const adapter = await createGcsReportingManagedDeliveryAdapterV1({
17
+ storage: new Storage(), // ADC/workload identity; no keys in reporting state
18
+ coreStore,
19
+ store: managedStore,
20
+ bucket: dedicatedReportingBucket,
21
+ namespace: stableDeploymentNamespace,
22
+ acknowledgeDedicatedFreshBucket: true,
23
+ // Both callbacks use authenticated saved host configuration.
24
+ resolveExpectedPeriod: (input, context) => contracts.expectedPeriod(input.binding, context),
25
+ resolveContractReader: (input, context) => contracts.privateReader(input.binding, context),
26
+ });
27
+ const runtime = await createReportingManagedDeliveryRuntime({
28
+ coreStore,
29
+ store: managedStore,
30
+ adapter,
31
+ offerings,
32
+ automatedRecoveryWindowSeconds,
33
+ statusRetentionDays,
34
+ resourceRetentionDays,
35
+ authorizationRevocationSeconds,
36
+ resolveConsumerId,
37
+ });
38
+ ```
39
+
40
+ The bucket must have been dedicated and safe before reporting bytes existed, with uniform access, enforced public-access prevention, explicit zero soft-delete retention, and no versioning, lifecycle, retention locks or object holds. The explicit acknowledgment is a host deployment assertion, not an SDK audit of historical recoverable bytes. Exclusively control bucket policy and object mutation through the trusted host. Follow [the fence policy and drift runbook](REPORTING-GCS-FENCE.md); the SDK refuses cleanup under incompatible policies rather than claiming that recoverable historical versions have been fenced. Preserve tombstones and write inventory indefinitely while stale requests may execute. Keep access to saved old bucket bindings through deployment changes.
41
+
42
+ Contract callbacks must independently select `ExpectedReportingPeriod` from the saved configuration and return `GcsReportingReaderOptionsV1` for that account, destination and current authorization generation. Pin report definition, row schema and canonicalization hashes when accepting the configuration. Save the exact private contract bucket and prefix separately from returned producer URIs. Report definitions and canonicalization documents require their protocol vendor MIME types; row schemas use `application/schema+json`. All documents still pass strict UTF-8/JSON, digest and existing schema safety checks.
43
+
44
+ For a buyer, `createGcsReportingResourceReaderV1` and `createGcsReportingReferenceResolverV1` accept a saved scope `{principal_id, account_id, destination_ref, generation}`, bucket, trailing-slash object prefix, an `authorize` callback and `getStorage`. The callback must verify principal ownership, account and destination generation, and exact bucket/prefix ownership. `storage.googleapis.com` origin alone is insufficient. Object keys may use letters, digits, slash, underscore, dot and hyphen; encoded paths, traversal, queries, fragments, userinfo and cross-prefix targets are refused before storage I/O. A manifest may select only simple sibling object references. Private contract failures redact refused URIs to avoid echoing credentials.
45
+
46
+ Readers authorize before obtaining an official client and again after buffering bytes. They select an exact provider generation, enforce configured byte caps and deadlines, and refuse revocation tombstones. Private document resolutions bypass every cache, including caller-supplied caches, so a previously resolved contract cannot bypass a closed grant. The generic canonical-reference API also exposes an optional `CanonicalDocumentReader` for host-authenticated transports; supplying it bypasses caches and HTTP fallback while preserving all existing validation. It is the transport's responsibility to bind credentials and authorization to the saved destination.
47
+
48
+ A repeated logical delivery reuses its frozen object plan and checks bytes after a create-only conflict. The adapter binds reads to the durable plan, manifest hash and native generation. Keep the deployment namespace stable while any write plans exist; rotating it requires a fresh authority and new grants. Set the adapter's `minimumResourceRetentionDays` to the runtime's value. Retention is extended after successful inspection with a deadline-sized settlement allowance; authoritative SQL settlement still refuses a resource under the strongest durable retention promise. An already-issued provider request can finish after a caller deadline. Follow-on work stops, inventory remains durable, and tombstones block late create-only writes. Do not treat timeout as rollback or successful cleanup.
49
+
50
+ The host passes authenticated ledger/configuration facts to the adapter. Its methods are infrastructure ports, not public buyer authorization endpoints. Expose reads and receipts through the existing account/principal-bound runtime and official MCP/A2A clients. No public capability is advertised until the runtime is fully wired and its policies adopted.
51
+
52
+ The buyer pins bucket and prefix through its authenticated destination-provisioning control plane before reading producer descriptors. A host destination grant can carry non-secret `{principal_id, account_id, destination_ref, generation, bucket, object_prefix}`; persist that grant as buyer configuration. Provisioning must obtain the prefix from the trusted provider binding and installation authority, never parse it from a returned resource URI. This SDK supplies readers for such grants; it does not implement your host's destination-provisioning endpoint. Row bytes use create-only keys and manifest hashes; the native generation selects the manifest, while each row object is independently hash-bound. Generic object MIME types do not relax JSONL decoding or integrity checks.
53
+
54
+ Choose payload limits that fit your measured network throughput: a delivery uploads and rereads its bytes, and a retry may reread them again. The 64 MiB ceiling is an upper bound, not a throughput promise. Apply host admission/concurrency and inventory quotas. Host contract callback failures use `HOST_CALLBACK_FAILED`; state and provider errors remain generic and secret-free.
55
+
56
+ Keep host clocks close to the database clock: resource expiry is proposed using the host clock, while SQL alone authorizes retention and reads. Clock lag beyond the settlement allowance fails delivery closed. Limit concurrent deliveries per account: the reused Core status reader admits 16 simultaneous lookups per account, and admission failures consume managed retry attempts. On large inventories, provision and monitor cleanup throughput against the advertised revocation window; SDK read gates close immediately at the authoritative grant, while direct provider readers lose each object when its tombstone is installed.
@@ -192,3 +192,7 @@ timeouts still depend on healthy host clocks.
192
192
  retention changes with independent protocol and security reviewers.
193
193
  - Capture dashboards, pages, runbook ownership, restore evidence, and capacity
194
194
  headroom before enabling financial decisions.
195
+
196
+ For the optional GCS provider write-fence primitive and its bucket policy, inventory retention and cancellation requirements, see [Durable GCS reporting write fence](REPORTING-GCS-FENCE.md). Complete managed delivery integration remains a separate step.
197
+
198
+ For complete GCS file-transfer delivery, private contracts and scoped buyer readers, see [Managed reporting delivery on GCS](REPORTING-GCS-MANAGED.md).
@@ -360,7 +360,7 @@ For local dev / non-KMS testing, `--key-file <jwk-path>` accepts an in-process J
360
360
 
361
361
  ```typescript
362
362
  import { createExpressVerifier, StaticJwksResolver } from '@adcp/sdk/signing';
363
- import { mcpToolNameResolver } from '@adcp/sdk/server';
363
+ import { adcpOperationResolver } from '@adcp/sdk/server';
364
364
 
365
365
  // Raw-body capture MUST be mounted ahead of the verifier — express.json()
366
366
  // would otherwise consume the stream and the verifier would have no bytes to
@@ -378,7 +378,7 @@ app.post(
378
378
  required_for: ['create_media_buy', 'update_media_buy'],
379
379
  },
380
380
  jwks: new StaticJwksResolver(buyerPublicKeys),
381
- resolveOperation: mcpToolNameResolver,
381
+ resolveOperation: adcpOperationResolver,
382
382
  }),
383
383
  handler
384
384
  );
@@ -414,7 +414,7 @@ app.use(createExpressVerifier({
414
414
  capability: { ... },
415
415
  jwks,
416
416
  replayStore, // <-- shared across instances
417
- resolveOperation: mcpToolNameResolver,
417
+ resolveOperation: adcpOperationResolver,
418
418
  }));
419
419
  ```
420
420
 
@@ -422,6 +422,20 @@ The schema uses one table with `(keyid, scope, nonce)` as the primary key, index
422
422
 
423
423
  On successful verification, `req.verifiedSigner` contains `{ keyid, agent_url?, verified_at }`. On failure, the middleware returns `401` with `WWW-Authenticate: Signature error="<code>"`.
424
424
 
425
+ ### Operation resolution: MCP and A2A
426
+
427
+ `required_for`, `supported_for`, and `warn_for` match the operation resolved from the request, not the JSON-RPC method (adcp#7945). `adcpOperationResolver` (from `@adcp/sdk/server`) implements the rule for both transports:
428
+
429
+ | Request | Resolved operation |
430
+ |---|---|
431
+ | MCP `tools/call` | `params.name` |
432
+ | A2A `SendMessage`, `SendStreamingMessage`, `message/send`, `message/stream` | `skill` of the Message's sole DataPart |
433
+ | Any other JSON-RPC method (`tasks/*`, `CancelTask`, `initialize`) | none; matched by `protocol_methods_*` only |
434
+
435
+ A body that does not resolve to exactly one operation returns the distinguished `UNRESOLVABLE_OPERATION`, and the verifier rejects it with `request_body_malformed` before it reads any signature header or capability list, signed or not. That covers zero or several DataParts, FileParts, a Part with two content members, a `skill` that is not a non-empty string, duplicate keys (after JSON string decoding), case variants of a recognized member name (`Skill`, `Parts`), and batches. Never treat an unresolvable body as "an operation that is not in `required_for`": a resolver that returns `undefined` for an A2A message (as `mcpToolNameResolver` does) leaves `required_for` unenforced over A2A.
436
+
437
+ `createAdcpServer({ signedRequests })` and `createA2AAdapter` use this resolver for you. When you compose authenticators by hand, `requireAuthenticatedOrSigned` defaults to it, rejects unresolvable bodies, and also runs the verifier's unsigned-branch checks: `protocolMethodsRequiredFor`, and the webhook-authentication rule, which a valid bearer does not satisfy.
438
+
425
439
  ### Composing with bearer auth
426
440
 
427
441
  `requireAuthenticatedOrSigned` bundles signature verification with credential fallback in one call: when signature headers are present, only signature auth runs (no bearer fallback — that prevents bypass attacks); when absent, the credential authenticator runs as normal; and `requiredFor` enforces the spec's `request_signature_required` 401 on mutating operations that arrive unsigned without other credentials.
@@ -433,7 +447,7 @@ import {
433
447
  verifyApiKey,
434
448
  verifySignatureAsAuthenticator,
435
449
  requireAuthenticatedOrSigned,
436
- mcpToolNameResolver,
450
+ adcpOperationResolver,
437
451
  } from '@adcp/sdk/server';
438
452
  import { ResolvedAgentJwksResolver } from '@adcp/sdk/signing/server';
439
453
 
@@ -442,11 +456,11 @@ serve(createAgent, {
442
456
  signature: verifySignatureAsAuthenticator({
443
457
  capability: { supported: true, required_for: ['create_media_buy'], covers_content_digest: 'either' },
444
458
  jwks: new ResolvedAgentJwksResolver(expectedBuyerAgentUrl, 'mcp'),
445
- resolveOperation: mcpToolNameResolver,
459
+ resolveOperation: adcpOperationResolver,
446
460
  }),
447
461
  fallback: verifyApiKey({ keys: { 'sk_live_abc': { principal: 'acct_42' } } }),
448
462
  requiredFor: ['create_media_buy', 'update_media_buy'],
449
- resolveOperation: mcpToolNameResolver,
463
+ resolveOperation: adcpOperationResolver,
450
464
  }),
451
465
  });
452
466
  ```
@@ -347,6 +347,10 @@ npx @adcp/sdk@adcp-3.2 grade request-signing https://sandbox.agent.example/mcp -
347
347
  vectors. Library callers can select
348
348
  the same set with `gradeRequestSigning(agentUrl, { signingProfileVersion: '3.2' })`.
349
349
 
350
+ #### A2A operation-resolution vectors (`--transport a2a`)
351
+
352
+ `--transport a2a` also grades the A2A operation-resolution vectors (adcp#7945, GHSA-frxv-c96c-4vqw): `required_for` must be enforced against the operation named by the invocation DataPart's `skill`, not the JSON-RPC method, so an unsigned `SendMessage` carrying `create_media_buy` is rejected with `request_signature_required` and a body that does not resolve to exactly one operation is rejected with `request_body_malformed`. Unlike the other vectors these go out byte-for-byte (the defect is in the body) to the JSON-RPC endpoint your Agent Card declares; the four signed ones are re-signed over that URL. Rows are prefixed `a2a/` and tagged by tier: `contradiction-resolution` is MUST for a 3.2 verifier, `hardening` is SHOULD in 3.2.x and MUST from 3.3. Both count as failures; the summary under the table splits them. Pass `--required-for create_media_buy` (and `--protocol-methods-required-for SendMessage` for `negative/007`) so vectors for operations you do not require skip instead of failing. The two signed `create_media_buy` positives need `--allow-live-side-effects` against a non-sandbox endpoint. If the compliance bundle predates the vectors (a repo checkout falls back to a vendored copy; elsewhere point `ADCP_A2A_VECTORS_DIR` at one), the run says "a2a operation-resolution vectors unavailable in this compliance bundle" and reports a skipped `a2a/operation-resolution-vectors` row rather than passing silently.
353
+
350
354
  #### Same vectors inside `storyboard run`
351
355
 
352
356
  The `signed_requests` storyboard synthesizes one step per vector and grades them through the same engine. `storyboard run` exposes the knobs that matter there:
package/docs/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # Ad Context Protocol (AdCP)
2
2
 
3
- > Generated at: 2026-10-06
4
- > Library: @adcp/sdk v15.0.0
3
+ > Generated at: 2026-10-07
4
+ > Library: @adcp/sdk v15.2.0
5
5
  > AdCP major version: 3
6
6
  > Canonical URL: https://adcontextprotocol.github.io/adcp-client/llms.txt
7
7
  > Note: the `Library` stamp reflects the package.json version at doc-generation time. The narrative below describes the surface that lands on the next-published minor — including any 6.7 helpers documented here ahead of the release tag.
@@ -11,8 +11,8 @@ below as the publication/deployment gate.
11
11
 
12
12
  | Fact | Value |
13
13
  | --- | --- |
14
- | Exact npm package | `@adcp/sdk@15.0.0` |
15
- | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.0.0 dist.integrity` |
14
+ | Exact npm package | `@adcp/sdk@15.2.0` |
15
+ | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.2.0 dist.integrity` |
16
16
  | Node.js runtime | `^20.19.0 || >=22.12.0` |
17
17
  | Default AdCP wire release | `3.2.1` |
18
18
  | Maintained wire releases | `v2.5`, `v2.6`, `v3`, `3.0.0`, `3.0`, `3.0.1`, `3.0.2`, `3.0.3`, `3.0.4`, `3.0.5`, `3.0.6`, `3.0.7`, `3.0.8`, `3.0.9`, `3.0.10`, `3.0.11`, `3.0.12`, `3.0.13`, `3.0.14`, `3.0.15`, `3.0.16`, `3.0.17`, `3.0.18`, `3.0.19`, `3.0.20`, `3.0.21`, `3.0.22`, `3.0.23`, `3.0.24`, `3.0.25`, `3.1.0`, `3.1`, `3.1.1`, `3.1.2`, `3.1.3`, `3.1.4`, `3.1.5`, `3.1.6`, `3.1.7`, `3.1.8`, `3.1.9`, `3.1.10`, `3.1.11`, `3.1.12`, `3.1.13`, `3.1.14`, `3.1.15`, `3.1.16`, `3.1.17`, `3.1.18`, `3.1.19`, `3.1.20`, `3.1.21`, `3.1.22`, `3.1.23`, `3.1.24`, `3.2.1`, `3.2` |
@@ -21,8 +21,8 @@ below as the publication/deployment gate.
21
21
  Install exact production inputs rather than a moving range or dist-tag:
22
22
 
23
23
  ```bash
24
- npm install --save-exact '@adcp/sdk@15.0.0'
25
- npm view '@adcp/sdk@15.0.0' dist.integrity
24
+ npm install --save-exact '@adcp/sdk@15.2.0'
25
+ npm view '@adcp/sdk@15.2.0' dist.integrity
26
26
  ```
27
27
 
28
28
  ### Required and optional peers
@@ -32,6 +32,7 @@ Install the peer packages used by your application at versions satisfying these
32
32
  | Peer | Supported range | Installation |
33
33
  | --- | --- | --- |
34
34
  | `@a2a-js/sdk` | `^1.0.1` | Required |
35
+ | `@google-cloud/storage` | `^7.22.0 || ^8.2.0` | Optional |
35
36
  | `@modelcontextprotocol/sdk` | `^1.31.0` | Required |
36
37
  | `@opentelemetry/api` | `^1.0.0` | Optional |
37
38
  | `redis` | `^4.6.0 || ^5.0.0 || ^6.0.0` | Optional |
@@ -311,7 +311,7 @@ import { verifySignatureAsAuthenticator } from '@adcp/sdk/server';
311
311
  const auth = verifySignatureAsAuthenticator({
312
312
  capability,
313
313
  jwks,
314
- resolveOperation: mcpToolNameResolver,
314
+ resolveOperation: adcpOperationResolver,
315
315
  // CONFIGURE THIS: maps the verified keyid back to the agent_url it
316
316
  // belongs to. The verifier stamps `credential.agent_url` from this
317
317
  // lookup; without it, the http_sig credential is omitted (no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcp/sdk",
3
- "version": "15.0.0",
3
+ "version": "15.2.0",
4
4
  "description": "AdCP SDK — client, server, and compliance harnesses for the AdContext Protocol (MCP + A2A)",
5
5
  "workspaces": [
6
6
  ".",
@@ -409,6 +409,16 @@
409
409
  "types": "./dist/lib/media-buy/actions.d.ts",
410
410
  "default": "./dist/lib/media-buy/actions.js"
411
411
  }
412
+ },
413
+ "./reporting/gcs": {
414
+ "import": {
415
+ "types": "./dist/lib/reporting/gcs/index.d.mts",
416
+ "default": "./dist/lib/reporting/gcs/index.mjs"
417
+ },
418
+ "require": {
419
+ "types": "./dist/lib/reporting/gcs/index.d.ts",
420
+ "default": "./dist/lib/reporting/gcs/index.js"
421
+ }
412
422
  }
413
423
  },
414
424
  "typesVersions": {
@@ -529,6 +539,9 @@
529
539
  ],
530
540
  "media-buy/actions": [
531
541
  "dist/lib/media-buy/actions.d.ts"
542
+ ],
543
+ "reporting/gcs": [
544
+ "dist/lib/reporting/gcs/index.d.ts"
532
545
  ]
533
546
  }
534
547
  },
@@ -733,12 +746,16 @@
733
746
  },
734
747
  "peerDependencies": {
735
748
  "@a2a-js/sdk": "^1.0.1",
749
+ "@google-cloud/storage": "^7.22.0 || ^8.2.0",
736
750
  "@modelcontextprotocol/sdk": "^1.31.0",
737
751
  "@opentelemetry/api": "^1.0.0",
738
752
  "redis": "^4.6.0 || ^5.0.0 || ^6.0.0",
739
753
  "zod": "^4.1.5"
740
754
  },
741
755
  "peerDependenciesMeta": {
756
+ "@google-cloud/storage": {
757
+ "optional": true
758
+ },
742
759
  "@opentelemetry/api": {
743
760
  "optional": true
744
761
  },
@@ -753,6 +770,7 @@
753
770
  "@changesets/cli": "^3.0.3",
754
771
  "@commitlint/cli": "^20.5.3",
755
772
  "@commitlint/config-conventional": "^20.5.3",
773
+ "@google-cloud/storage": "^7.22.0",
756
774
  "@modelcontextprotocol/sdk": "^1.31.0",
757
775
  "@opentelemetry/api": "^1.9.1",
758
776
  "@types/express": "4.17.25",
@@ -25,6 +25,8 @@ The `WWW-Authenticate` header is the grading surface — return the right error
25
25
 
26
26
  **Auto-wiring via `createAdcpServerFromPlatform`.** Pass `signedRequests: { jwks, replayStore, revocationStore }` alongside your platform and add `'signed-requests'` to `capabilities.specialisms` — the framework builds the verifier preTransport for you and `serve()` auto-mounts it. `createAdcpServerFromPlatform` throws at startup when `signedRequests` is set without the specialism claim (buyers wouldn't sign), and logs a loud error in the other direction. Keep `request_signing` in capabilities separately — it's still how buyers discover your `required_for` policy.
27
27
 
28
+ **A2A.** If you also mount `createA2AAdapter({ server })`, it runs the same verifier on the A2A JSON-RPC endpoint automatically, and `required_for` matches the `skill` of the Message's sole DataPart (`SendMessage`, `SendStreamingMessage`, `message/send`, `message/stream`), exactly as `params.name` does over MCP. A body that does not resolve to exactly one operation (several DataParts, a FilePart, duplicate or case-variant keys, a non-string `skill`, a batch) is rejected with `request_body_malformed` before any handler. Record the raw bytes if a body parser runs ahead of the adapter: `app.use(express.json({ verify: a2a.rawBodyVerify }))`. Since SDK 15.0.1 the adapter refuses to construct when the server advertises request signing and nothing verifies signatures on A2A; before that, A2A silently skipped `required_for` entirely (GHSA-frxv-c96c-4vqw), so upgrade if you serve A2A.
29
+
28
30
  ```typescript
29
31
  const platform = definePlatform({
30
32
  capabilities: {