@adcp/sdk 15.3.0 → 15.5.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 (130) hide show
  1. package/dist/lib/advanced.d.mts +2 -2
  2. package/dist/lib/advanced.d.ts +2 -2
  3. package/dist/lib/advanced.js +8 -2
  4. package/dist/lib/advanced.mjs +8 -2
  5. package/dist/lib/core/AgentClient.d.mts +4 -0
  6. package/dist/lib/core/AgentClient.d.ts +4 -0
  7. package/dist/lib/core/AgentClient.js +8 -0
  8. package/dist/lib/core/AgentClient.mjs +8 -0
  9. package/dist/lib/core/SingleAgentClient.d.mts +154 -0
  10. package/dist/lib/core/SingleAgentClient.d.ts +154 -0
  11. package/dist/lib/core/SingleAgentClient.js +661 -23
  12. package/dist/lib/core/SingleAgentClient.mjs +666 -23
  13. package/dist/lib/core/agent-discovery-cache.d.mts +103 -0
  14. package/dist/lib/core/agent-discovery-cache.d.ts +103 -0
  15. package/dist/lib/core/agent-discovery-cache.js +121 -0
  16. package/dist/lib/core/agent-discovery-cache.mjs +92 -0
  17. package/dist/lib/index.d.mts +5 -3
  18. package/dist/lib/index.d.ts +5 -3
  19. package/dist/lib/index.js +7 -0
  20. package/dist/lib/index.mjs +6 -0
  21. package/dist/lib/protocols/index.d.mts +2 -2
  22. package/dist/lib/protocols/index.d.ts +2 -2
  23. package/dist/lib/protocols/index.js +1 -1
  24. package/dist/lib/protocols/index.mjs +1 -1
  25. package/dist/lib/protocols/mcp-call-context.d.mts +109 -0
  26. package/dist/lib/protocols/mcp-call-context.d.ts +109 -0
  27. package/dist/lib/protocols/mcp-call-context.js +221 -0
  28. package/dist/lib/protocols/mcp-call-context.mjs +186 -0
  29. package/dist/lib/protocols/mcp-modern.d.mts +9 -1
  30. package/dist/lib/protocols/mcp-modern.d.ts +9 -1
  31. package/dist/lib/protocols/mcp-modern.js +180 -104
  32. package/dist/lib/protocols/mcp-modern.mjs +193 -104
  33. package/dist/lib/protocols/mcp-scope.d.mts +15 -0
  34. package/dist/lib/protocols/mcp-scope.d.ts +15 -0
  35. package/dist/lib/protocols/mcp.d.mts +4 -2
  36. package/dist/lib/protocols/mcp.d.ts +4 -2
  37. package/dist/lib/protocols/mcp.js +178 -154
  38. package/dist/lib/protocols/mcp.mjs +189 -153
  39. package/dist/lib/protocols/transportDiagnostics.d.mts +33 -1
  40. package/dist/lib/protocols/transportDiagnostics.d.ts +33 -1
  41. package/dist/lib/protocols/transportDiagnostics.js +128 -12
  42. package/dist/lib/protocols/transportDiagnostics.mjs +128 -12
  43. package/dist/lib/reporting/gcs/index.d.mts +2 -0
  44. package/dist/lib/reporting/gcs/index.d.ts +2 -0
  45. package/dist/lib/reporting/gcs/index.js +3 -0
  46. package/dist/lib/reporting/gcs/index.mjs +2 -0
  47. package/dist/lib/reporting/gcs/row-provider.d.mts +32 -0
  48. package/dist/lib/reporting/gcs/row-provider.d.ts +32 -0
  49. package/dist/lib/reporting/gcs/row-provider.js +205 -0
  50. package/dist/lib/reporting/gcs/row-provider.mjs +188 -0
  51. package/dist/lib/reporting/ledger/change-feed.d.mts +41 -0
  52. package/dist/lib/reporting/ledger/change-feed.d.ts +41 -0
  53. package/dist/lib/reporting/ledger/change-feed.js +101 -0
  54. package/dist/lib/reporting/ledger/change-feed.mjs +74 -0
  55. package/dist/lib/reporting/ledger/handler.d.mts +6 -0
  56. package/dist/lib/reporting/ledger/handler.d.ts +6 -0
  57. package/dist/lib/reporting/ledger/handler.js +51 -8
  58. package/dist/lib/reporting/ledger/handler.mjs +50 -8
  59. package/dist/lib/reporting/ledger/index.d.mts +5 -0
  60. package/dist/lib/reporting/ledger/index.d.ts +5 -0
  61. package/dist/lib/reporting/ledger/index.js +11 -1
  62. package/dist/lib/reporting/ledger/index.mjs +5 -0
  63. package/dist/lib/reporting/ledger/lifecycle.js +3 -2
  64. package/dist/lib/reporting/ledger/lifecycle.mjs +3 -2
  65. package/dist/lib/reporting/ledger/managed-postgres.d.mts +2 -0
  66. package/dist/lib/reporting/ledger/managed-postgres.d.ts +2 -0
  67. package/dist/lib/reporting/ledger/managed-postgres.js +26 -6
  68. package/dist/lib/reporting/ledger/managed-postgres.mjs +26 -6
  69. package/dist/lib/reporting/ledger/managed.d.mts +9 -0
  70. package/dist/lib/reporting/ledger/managed.d.ts +9 -0
  71. package/dist/lib/reporting/ledger/managed.js +18 -0
  72. package/dist/lib/reporting/ledger/managed.mjs +18 -0
  73. package/dist/lib/reporting/ledger/postgres.d.mts +179 -1
  74. package/dist/lib/reporting/ledger/postgres.d.ts +179 -1
  75. package/dist/lib/reporting/ledger/postgres.js +882 -19
  76. package/dist/lib/reporting/ledger/postgres.mjs +890 -19
  77. package/dist/lib/reporting/ledger/producer.js +13 -3
  78. package/dist/lib/reporting/ledger/producer.mjs +13 -3
  79. package/dist/lib/reporting/ledger/revision-metadata.d.mts +17 -0
  80. package/dist/lib/reporting/ledger/revision-metadata.d.ts +17 -0
  81. package/dist/lib/reporting/ledger/revision-metadata.js +71 -0
  82. package/dist/lib/reporting/ledger/revision-metadata.mjs +44 -0
  83. package/dist/lib/reporting/ledger/row-encoding.d.mts +128 -0
  84. package/dist/lib/reporting/ledger/row-encoding.d.ts +128 -0
  85. package/dist/lib/reporting/ledger/row-encoding.js +355 -0
  86. package/dist/lib/reporting/ledger/row-encoding.mjs +315 -0
  87. package/dist/lib/reporting/ledger/row-storage-errors.d.mts +7 -0
  88. package/dist/lib/reporting/ledger/row-storage-errors.d.ts +7 -0
  89. package/dist/lib/reporting/ledger/row-storage-errors.js +41 -0
  90. package/dist/lib/reporting/ledger/row-storage-errors.mjs +16 -0
  91. package/dist/lib/reporting/ledger/row-storage-migration.d.mts +41 -0
  92. package/dist/lib/reporting/ledger/row-storage-migration.d.ts +41 -0
  93. package/dist/lib/reporting/ledger/row-storage-migration.js +234 -0
  94. package/dist/lib/reporting/ledger/row-storage-migration.mjs +208 -0
  95. package/dist/lib/reporting/ledger/row-storage-object.d.mts +112 -0
  96. package/dist/lib/reporting/ledger/row-storage-object.d.ts +112 -0
  97. package/dist/lib/reporting/ledger/row-storage-object.js +281 -0
  98. package/dist/lib/reporting/ledger/row-storage-object.mjs +239 -0
  99. package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +51 -0
  100. package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +51 -0
  101. package/dist/lib/reporting/ledger/row-storage-provider-io.js +152 -0
  102. package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +119 -0
  103. package/dist/lib/reporting/ledger/row-storage.d.mts +224 -0
  104. package/dist/lib/reporting/ledger/row-storage.d.ts +224 -0
  105. package/dist/lib/reporting/ledger/row-storage.js +692 -0
  106. package/dist/lib/reporting/ledger/row-storage.mjs +680 -0
  107. package/dist/lib/reporting/ledger/types.d.mts +45 -0
  108. package/dist/lib/reporting/ledger/types.d.ts +45 -0
  109. package/dist/lib/reporting/service/index.d.mts +41 -1
  110. package/dist/lib/reporting/service/index.d.ts +41 -1
  111. package/dist/lib/reporting/service/index.js +68 -1
  112. package/dist/lib/reporting/service/index.mjs +70 -1
  113. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  114. package/dist/lib/signing/agent-resolver/fetch-helpers.js +1 -1
  115. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +1 -1
  116. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  117. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  118. package/dist/lib/version.d.mts +3 -3
  119. package/dist/lib/version.d.ts +3 -3
  120. package/dist/lib/version.js +3 -3
  121. package/dist/lib/version.mjs +3 -3
  122. package/docs/README.md +1 -1
  123. package/docs/TYPE-SUMMARY.md +35 -2
  124. package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +142 -0
  125. package/docs/guides/EXISTING-PLATFORM.md +43 -12
  126. package/docs/guides/REPORTING-LEDGER.md +209 -0
  127. package/docs/llms.txt +4 -2
  128. package/docs/migration-14.x-rc-worksheet.md +4 -4
  129. package/docs/recipes/verifying-inbound-webhooks.md +8 -0
  130. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  # AdCP Type Summary
2
2
 
3
- > Generated at: 2026-10-08
4
- > @adcp/sdk v15.3.0
3
+ > Generated at: 2026-10-09
4
+ > @adcp/sdk v15.5.0
5
5
 
6
6
  Curated reference of the types that matter for using the AdCP client. For full generated types see `src/lib/types/tools.generated.ts` and `src/lib/types/core.generated.ts`.
7
7
 
@@ -56,6 +56,39 @@ interface TransportOptions {
56
56
  legacyCompat?: A2ALegacyCompatOptions; // A2A only
57
57
  }
58
58
 
59
+ // Selected transport observer fields; correlate all events by transportRequestId.
60
+ type TransportActivityType = 'request_started' | 'response_received' | 'response_completed' | 'request_failed';
61
+ type TransportResponseOutcome = 'ended' | 'errored' | 'aborted' | 'cancelled';
62
+ interface TransportActivity {
63
+ type: TransportActivityType;
64
+ transportRequestId: string;
65
+ durationMs?: number; // response_received: time to headers; response_completed: total time
66
+ streaming?: boolean; // Uncaptured body: terminal observation follows consumption or abort
67
+ outcome?: TransportResponseOutcome; // response_completed only
68
+ bytes?: number; // Bytes forwarded before the terminal outcome; no stream payload retained
69
+ }
70
+
71
+ // Share discovery across request-local clients using an application-owned authorization identity.
72
+ interface AgentDiscoveryCacheConfig {
73
+ cache: AgentDiscoveryCache; // get / set / delete; synchronous or asynchronous
74
+ authIdentity: string; // Trusted principal and authorization scope; never a token
75
+ ttlMs?: number; // Default: five minutes; maximum: 24 hours
76
+ }
77
+ const discoveryCache = createInMemoryAgentDiscoveryCache({ maxEntries: 256 });
78
+ const client = new SingleAgentClient(agentConfig, {
79
+ discoveryCache: { cache: discoveryCache, authIdentity: 'tenant:buyer-scope', ttlMs: 300_000 },
80
+ });
81
+ // Public from @adcp/sdk, @adcp/sdk/client, and @adcp/sdk/advanced.
82
+ await withMCPConnectionScope(async () => { await client.getCapabilities(); /* ...tool calls... */ });
83
+ // closeScopedConnections() can close the current workflow early.
84
+ interface AgentDiscoverySeed {
85
+ capabilities: AdcpCapabilities; // Authoritative evidence, never synthetic
86
+ toolSchemas?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
87
+ endpoint?: { agentUri: string; mcpEra?: 'legacy' | 'modern' }; // Same origin, no URL userinfo
88
+ }
89
+ // client.primeDiscoveryCache(seed): Promise<boolean>; also available on AgentClient.
90
+ // client.invalidateDiscoveryCache(): Promise<void>; refresh after a seller change.
91
+
59
92
  interface TaskOptions {
60
93
  skipStatusHandlers?: boolean; // Skip inline completion handlers, including continuations
61
94
  timeout?: number; // Absolute whole-task deadline
@@ -0,0 +1,142 @@
1
+ # Discovery cache and MCP session reuse
2
+
3
+ A server-side caller that makes one AdCP call per incoming request, building a `SingleAgentClient` (or `AgentClient`) per request because auth, headers or deadlines differ, used to pay for the SDK's whole setup sequence on every call: an endpoint probe, a `tools/list` and a `get_adcp_capabilities`, each on its own MCP session. Against a slow seller that comes straight out of the caller's deadline.
4
+
5
+ Two independent mechanisms remove that cost:
6
+
7
+ 1. an **opt-in shared discovery cache** so client instances for the same agent and principal reuse one endpoint / `tools/list` / capabilities observation, and
8
+ 2. **reusable MCP connections** that per-call correlation headers, `AbortSignal`s and timeouts no longer split, plus a public **connection scope** to run a whole workflow on one session.
9
+
10
+ ## Shared discovery cache
11
+
12
+ ```ts
13
+ import { AgentClient, createInMemoryAgentDiscoveryCache } from '@adcp/sdk';
14
+
15
+ // One per process (or implement AgentDiscoveryCache over your own store).
16
+ const discoveryCache = createInMemoryAgentDiscoveryCache({ maxEntries: 256 });
17
+
18
+ function clientFor(request: IncomingRequest) {
19
+ return new AgentClient(
20
+ {
21
+ id: 'seller',
22
+ name: 'Seller',
23
+ agent_uri: 'https://seller.example/mcp',
24
+ protocol: 'mcp',
25
+ auth_token: request.token,
26
+ },
27
+ {
28
+ discoveryCache: {
29
+ cache: discoveryCache,
30
+ authIdentity: request.authorizationScopeId, // principal + tenant + granted permissions
31
+ ttlMs: 5 * 60_000, // default 5 minutes, max 24 hours
32
+ },
33
+ }
34
+ );
35
+ }
36
+ ```
37
+
38
+ ### What is shared, and for how long
39
+
40
+ An entry holds the endpoint that discovery resolved, the `tools/list` input-schema map and the capabilities from an authoritative `get_adcp_capabilities` response. Every entry carries `observedAt` / `expiresAt`; the SDK enforces the TTL on read, whatever your backend does. A reader also applies its own `ttlMs`, so a short-TTL client cannot inherit a longer writer's freshness window. A long-lived client honors that bound for both endpoints and capabilities in its own in-memory copy.
41
+
42
+ ### Isolation (the part to get right)
43
+
44
+ The key is a SHA-256 over:
45
+
46
+ | Input | Why |
47
+ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
48
+ | agent URL and protocol | endpoint identity |
49
+ | `authIdentity` | **caller-owned** principal. Deliberately _not_ derived from the token: you decide who may share evidence |
50
+ | credential kind (anonymous, bearer, OAuth or header) | Keeps anonymous and authenticated discovery separate while allowing token rotation |
51
+ | configured `adcpVersion` / `wireAdcpVersion` | negotiated version |
52
+ | every non-correlation, non-credential header | unknown `x-*` tenant / routing / policy headers can select a different upstream view, so they fail closed into the key. This includes `baggage`, **ambient OpenTelemetry baggage included** |
53
+ | request-signing identity and policy | `kind`, `kid`, `alg`, agent URL and signing policy; never the private key |
54
+ | effective `allowPrivateIp`, `maxResponseBytes` | the transport policy the evidence was gathered under (per-call `transport` overrides included) |
55
+
56
+ Not part of the key: correlation headers (`x-request-id`, `x-correlation-id`, `traceparent`, ...), and credential header values (`Authorization`, `x-adcp-auth`) — OAuth and `auth_token` credentials are also owned by `authIdentity`; a token rotation for the same authorization scope keeps the entry. **Choose `authIdentity` at least as narrow as the credential's authorization scope.** Change the identity or invalidate the cache when permissions change or access is revoked; use a distinct identity for anonymous access. Two principals must never share an identity. Keys are hashes and never contain the identity, token or header values.
57
+
58
+ Never shared:
59
+
60
+ - **Synthetic capabilities** (a seller with no `get_adcp_capabilities`, or whose capabilities call failed) stay private to the instance that built them.
61
+ - **Scoped `trustedFetchFn` calls** bypass the cache in both directions: their network boundary is per call.
62
+ - Entries are copied in and out, so mutating a returned capabilities object cannot poison the cache.
63
+
64
+ ### Concurrency, cancellation, failures
65
+
66
+ - Cold concurrent clients for the same key share one in-process discovery: one leader discovers, followers wait for it under **their own** signal and request timeout. A leader that is cancelled or fails never fails its followers; they discover for themselves.
67
+ - A cache backend that throws, returns a malformed entry or hangs costs a rediscovery, never a failed call. Each cache read or mutation wait is bounded by a 2 second deadline and, for reads and automatic writes, by the caller's `AbortSignal`. Mutations of one key are ordered in-process. A timed-out backend mutation retains its place until it settles, so a late writer cannot overwrite or delete a newer seed; later mutation waits also time out while discovery proceeds live. The queue is bounded: a write whose caller already stopped waiting is dropped if it has not started, a full per-key queue refuses further writes (a failed write, never a failed call), and invalidations queued behind a hung delete coalesce into one follow-up delete. Nothing is ever replayed automatically.
68
+ - The shared cache is in-process singleflight. Across processes each cold process still discovers once.
69
+
70
+ ### Invalidation
71
+
72
+ - `client.invalidateDiscoveryCache()` drops this client's local evidence and the shared entry. `SingleAgentClient.refreshCapabilities()` does the same and re-discovers; on `AgentClient`, call `invalidateDiscoveryCache()` and then `getCapabilities()`.
73
+ - A feature check (`require`, or the pre-flight check before a task) that fails against **shared** evidence invalidates it and re-decides on live capabilities before refusing the call.
74
+ - A seller error of `VERSION_UNSUPPORTED`, `UNSUPPORTED_FEATURE`, `FEATURE_UNSUPPORTED`, `TOOL_NOT_FOUND`, `METHOD_NOT_FOUND` or `UNKNOWN_TOOL` invalidates the shared entry, on every task path including `executeTask()` and the typed methods. Each such live re-check costs one rediscovery; a seller that genuinely lacks a feature pays it on every refused call.
75
+ - A malformed or expired entry is deleted and treated as a miss. An entry older than this reader's shorter TTL is also a miss.
76
+ - Invalidation by any client sharing the cache in this process also drops other instances' local evidence and endpoint for that key (application-owned `primeCapabilities` evidence keeps its own freshness bound). In this process an invalidation leaves a tombstone, so a shared entry observed before it is a miss even when the backend delete failed or is still queued; the tombstone record set is bounded and, once pruned, errs toward a miss. Other processes still rely on the backend delete.
77
+ - In-process invalidation takes precedence over an earlier discovery still in flight. External backends with concurrent writers in other processes must supply their own atomic invalidation/versioning if they need that guarantee across processes.
78
+
79
+ ### Pre-seeding
80
+
81
+ | Method | Lifetime | Result |
82
+ | --------------------- | -------------------------------------------------------------- | --------------------------------------------------- |
83
+ | `primeCapabilities` | One client, bound to its capability scope | Synchronous; `true` if accepted, otherwise `false` |
84
+ | `primeDiscoveryCache` | Shared cache, bound to explicit authorization identity and TTL | Asynchronous; `true` if accepted, otherwise `false` |
85
+
86
+ ```ts
87
+ const ok = await client.primeDiscoveryCache({
88
+ capabilities, // authoritative, non-synthetic
89
+ toolSchemas, // optional when capabilities.discoveredTools is present
90
+ endpoint: { agentUri: 'https://seller.example/mcp', mcpEra: 'modern' }, // optional
91
+ });
92
+ ```
93
+
94
+ Unlike [`primeCapabilities`](../TYPE-SUMMARY.md) — which stays client-bound and scope-keyed — seeded evidence outlives the instance. It returns `true` only once the backing cache accepted the entry, and `false` (seeding nothing) for synthetic, malformed, uncloneable or foreign-origin evidence. When the cache rejects the write or does not answer within the 2 second deadline, `false` means *not confirmed*, not *empty*: a write already handed to the backend may still land later, while one still queued behind earlier operations is dropped. After such a `false`, seed again or invalidate rather than assuming nothing was stored. Without `endpoint` the first call of a cold client still runs the endpoint probe once.
95
+
96
+ ## Reusable MCP connections
97
+
98
+ This behavior applies by default, including calls that pass a signal or timeout outside a connection scope. The process-wide legacy/OAuth session cache retains at most 20 connections per cache with LRU eviction and has no idle expiry. For request workflows with many distinct credentials, use a scope to bound session lifetime. At process shutdown, `closeConnections()` from `@adcp/sdk/advanced` closes the process-wide connections.
99
+
100
+ A reusable session is keyed only by what changes the _connection_: endpoint, credential, tenant / routing headers, signing identity, `allowPrivateIp`, a caller-injected fetch, and the workflow scope. These no longer choose or split a session:
101
+
102
+ - **Correlation headers**: `traceparent`, `tracestate`, `x-request-id`, `x-correlation-id`, `x-trace-id`, `x-span-id`, `x-debug-id`, `x-client-request-id`, `x-b3-*`, `x-amzn-trace-id`, `x-cloud-trace-context`, `x-datadog-*`, `sentry-trace`, and vendor-prefixed `x-<vendor>-request-id` / `-correlation-id` / `-trace-id` / `-span-id` / `-debug-id`. Names suggesting credentials or tenant/routing identity (including `auth`, `token`, `tenant`, `account`, `workspace`, `seat`, `advertiser`, `publisher`, `buyer` and `seller`) are never treated as correlation. **`baggage` stays in the key**: it can carry tenant or routing members. Any header outside the list above also stays in the key (fail closed).
103
+ - **`AbortSignal`** and **`requestTimeoutMs`**: they bound a call.
104
+
105
+ Each call still sends _its own_ correlation headers, deadline and cancellation on every request over the shared session (including concurrent calls), through per-call context rather than values frozen at connect time. Explicit caller headers win over ambient OpenTelemetry context.
106
+
107
+ Isolation properties:
108
+
109
+ - A cancelled or timed-out call tears down only its own request. It does not evict or terminate a session other calls are using; other failures retire the session and close it once its in-flight calls finish. A session the seller has expired may fail the current call once; the SDK retires that connection and never replays a dispatched call, so a caller must reconcile the outcome of a mutation that failed that way (for example by re-reading it) before retrying with the same `idempotency_key`.
110
+ - A caller that joins another caller's in-flight connect waits under its own signal and timeout. If the creator cancels the connect, the joiner retries for itself, with at most two retries after foreign aborts.
111
+ - A caller-injected `fetchFn` (a per-call network trust boundary) keeps its own one-shot session outside a connection scope; inside a scope its identity is part of the key.
112
+ - Connections are never shared across credentials, tenant headers, signing identities or `allowPrivateIp` settings.
113
+
114
+ ### Public connection scope
115
+
116
+ ```ts
117
+ import { withMCPConnectionScope } from '@adcp/sdk'; // also from '@adcp/sdk/advanced'
118
+
119
+ await withMCPConnectionScope(async () => {
120
+ await client.syncAccounts(accounts);
121
+ await client.getCapabilities();
122
+ await client.getProducts(brief); // same MCP session
123
+ }); // the session is terminated once, here
124
+ ```
125
+
126
+ Await every operation before the scope callback returns; the callback owns the session lifetime. Open process-wide sessions can keep sockets alive, so close them at shutdown.
127
+
128
+ Inside a scope the endpoint probe, capability discovery, `tools/list` and tool calls share one negotiated session per identity. `closeScopedConnections()` closes the current MCP scope's sessions early and does nothing when no MCP scope is active (A2A connections are not scoped). Nested `withMCPConnectionScope` calls join the outer scope unless you pass `{ isolate: true }`.
129
+
130
+ ### One exception: the first legacy handoff
131
+
132
+ For a seller on the pre-2026 MCP protocol era, the SDK negotiates with the v2 client first and then hands the call to the v1 client so 2025 Tasks keep working. That handoff is a cold cost the SDK cannot avoid: the first scope against such a seller opens one classification handshake plus one shared v1 session. Within the scope everything after that (probe aside) shares the v1 session, including `tools/list`, and the classification is remembered across scopes for five minutes, so a later scope opens only its own session. Modern-era sellers need a single negotiation per scope.
133
+
134
+ ### Troubleshooting cache misses
135
+
136
+ Unknown headers participate in identity. A varying header such as `x-forwarded-for` causes a cache miss even when `authIdentity` matches, and so does varying `baggage`, including ambient OpenTelemetry baggage that differs per request. Ambient OpenTelemetry context is not a way to split or merge keys: set the baggage identically for callers that should share evidence, or keep it out of routing. Calls with a scoped `trustedFetchFn` bypass the cache entirely, so a client that always passes one never hits it. Also check version, transport policy, signing policy and TTL before assuming the cache backend failed.
137
+
138
+ The cache holds capabilities, schemas and the endpoint URL. Endpoint sharing excludes URLs with userinfo, a query string or a fragment; those endpoints stay local while their capabilities and schemas can still be shared. `primeDiscoveryCache` refuses such an endpoint. Put credentials in authentication headers. Treat a custom `AgentDiscoveryCache` backend as trusted storage.
139
+
140
+ ## Related
141
+
142
+ - [Session reuse for multi-tool workflows](https://github.com/adcontextprotocol/adcp-client/issues/2118).
@@ -77,22 +77,53 @@ The submitted continuation's `waitForCompletion` function is deliberately proces
77
77
  Do not catch every outcome into a string. Switch on `result.status`; use `result.adcpError` for failed results, and catch thrown cancellation/configuration errors separately. Internal transport retries reuse an idempotency key. A new application intent must receive a new key; after an ambiguous timeout, reconcile by the persisted natural key before deciding to retry.
78
78
 
79
79
  Transport diagnostics never delay delivery of the operational `Response`.
80
- SSE, non-text, and explicitly over-limit bodies are not cloned; their single
81
- response event is emitted immediately with `responseBodyTruncated: true`. Other
82
- diagnostic text bodies, including chunked responses without `Content-Length`
83
- and responses with an invalid length, are cloned synchronously and captured in
84
- the background up to 64 KiB for at most `BODY_SNIPPET_TIMEOUT_MS` (currently 1
85
- second). Their single response event is emitted when capture completes,
86
- truncates, or expires, while the original response stream remains exclusively
87
- available to the protocol client. The enclosing task then waits within
80
+ Each request emits `request_started`, then `response_received`, then (for
81
+ streamed replies) `response_completed`. `response_received` means **response
82
+ headers arrived**: its `durationMs` is time to headers. For an MCP server that
83
+ answers over `text/event-stream`, that is not the end of the reply, so do not
84
+ record it as a finished call.
85
+
86
+ Bodies are captured only when they are text-like and declare a finite
87
+ `Content-Length` of at most 64 KiB. Those are cloned synchronously and read in
88
+ the background for at most `BODY_SNIPPET_TIMEOUT_MS` (currently 1 second), and
89
+ their single `response_received` event is emitted when capture completes,
90
+ truncates, or expires. The enclosing task then waits within
88
91
  `OBSERVER_FLUSH_TIMEOUT_MS` for that event's asynchronous observer, so
89
- short-lived processes do not lose the final audit record. Observer
90
- failures remain isolated. Applications should synchronously enqueue each event into their own
91
- bounded in-memory or durable queue and return promptly; flushing that queue is
92
- an application lifecycle concern.
92
+ short-lived processes do not lose the final audit record.
93
+
94
+ Every other body is never cloned or buffered: SSE, non-text content, chunked
95
+ responses or responses with a missing or invalid `Content-Length`, and bodies
96
+ declared over the limit. Their `response_received` event is emitted
97
+ immediately with `responseBodyTruncated: true` and, when the body can be observed, `streaming: true`; `streaming` is the signal that a `response_completed` event will follow. The body
98
+ is passed through an observing stream with the same bytes, status, headers,
99
+ `url`, `redirected`, and `type`, and with pull-driven backpressure. When the
100
+ caller finishes with it, exactly one `response_completed` event reports
101
+ `outcome`, total `bytes`, and `durationMs` measured from `startedAt`:
102
+
103
+ | `outcome` | Meaning |
104
+ | ----------- | -------------------------------------------------------------------------- |
105
+ | `ended` | The caller read the stream to EOF. |
106
+ | `errored` | The stream failed (`errorName` / sanitized `errorMessage` are set). |
107
+ | `aborted` | The request's `AbortSignal` fired (also when the body is cancelled after it fired), or the stream failed with an abort error. |
108
+ | `cancelled` | The consumer cancelled the body, including SDK cleanup after delivering a successful MCP result. |
109
+
110
+ A streamed call that never answers therefore shows `response_received` with no
111
+ matching `response_completed` until the caller aborts it (for example, when its
112
+ deadline fires an `AbortSignal` on the request), then an `aborted` completion. `response_completed` is fire-and-forget: it can arrive
113
+ after the diagnostics scope exits, the scope never waits for it, and a body
114
+ that is never read or aborted never emits one. It carries the request correlation
115
+ fields, `httpStatus`, and `statusText`, but no request or response bodies and no
116
+ response headers. The SDK does not parse SSE messages, so it cannot say whether
117
+ a stream carried a final result.
118
+
119
+ Observer failures remain isolated. Applications should synchronously enqueue
120
+ each event into their own bounded in-memory or durable queue and return
121
+ promptly; flushing that queue is an application lifecycle concern.
93
122
 
94
123
  ## Reuse scoped capability evidence
95
124
 
125
+ For evidence shared across separate client instances, use the opt-in [discovery cache](./DISCOVERY-CACHE-AND-SESSION-REUSE.md). That guide also covers MCP connection scopes and per-call session reuse.
126
+
96
127
  An application factory that already performs a bounded seller preflight can
97
128
  construct and prime the specific client instance before exposing it for task
98
129
  dispatch:
@@ -272,6 +272,215 @@ Official configurations also pin a `finalityPolicy` (`policyId` plus `source_fin
272
272
 
273
273
  Every revision stores its rows together with an RFC 8785 JCS SHA-256 binding and exact decimal control totals for requested numeric metrics. A revision number and obligation are immutable. Official revisions are terminal; later source corrections are immutable adjustments bound to the official revision, never superseding revisions. Status snapshots omit row payloads, are capped at 8 MiB, expire after 15 minutes, and keep cursor pages stable over the flat obligation/revision/adjustment union. A periods response returns an opaque `changes_checkpoint`; echo that value verbatim as `changes_after` rather than supplying a timestamp. Account-scoped write/snapshot locks make those checkpoints gap-free for SDK store writes. The default table set is deployment-wide; use a dedicated database/schema and acknowledge that boundary explicitly. `sourceScope` must contain opaque routing identities only—never credentials or bearer tokens—because it is retained with the obligation. Retained resource locations are held to the same rule, and at the seam that persists them rather than only in the worker's pre-flight, because a caller driving `settleMaterialization` directly would otherwise store a presigned location that `get_reporting_status` then publishes. Query and fragment are refused on the raw string rather than on a successful parse, because a relative path carrying a presigning query never parsed as a URL at all; userinfo is refused as a colon-separated pair before the `@`, which is the credential shape, plus any `http(s)` userinfo at all. A blanket `@` rule would refuse `abfss://container@account.dfs.core.windows.net/...` and a Snowflake stage reference, neither of which carries a secret.
274
274
 
275
+ ## Row storage
276
+
277
+ By default every revision document carries its rows inline. At pulse cadence
278
+ that keeps bulk report bytes in the transactional database. Opt in to row
279
+ storage to keep only the revision header in the ledger document and store
280
+ rows as verified canonical JSONL chunks (the shared SDK persistence layout,
281
+ adcontextprotocol/adcp#7996):
282
+
283
+ ```ts
284
+ import { REPORTING_ROW_STORAGE_MIGRATION } from '@adcp/sdk/reporting/ledger';
285
+
286
+ await pool.query(REPORTING_ROW_STORAGE_MIGRATION); // after REPORTING_LEDGER_MIGRATION; PostgreSQL 13+
287
+ const store = new PostgresReportingLedgerStore(pool, {
288
+ acknowledgeIsolatedDatabase: true,
289
+ rowStorage: true,
290
+ });
291
+ await store.readyRowStorage();
292
+ ```
293
+
294
+ `createPostgresReliableReportingProductionService` accepts the same
295
+ `rowStorage` option and adds the migration to `setup.migrations`.
296
+
297
+ - Rows are split into 500-row segments and chunks of at most 10,000 rows or
298
+ 8 MiB. Chunk, segment and manifest digests are committed with the revision,
299
+ and every read is verified before rows are released. Delivery pages read only
300
+ the segments they need.
301
+ - A revision whose rows are byte-identical to a live row set in the same
302
+ obligation is stored as a header-only revision. It gets a new identity,
303
+ freshness and binding, and references the existing chunks
304
+ (`rows_shared_from_row_set_id`), so an unchanged pulse uploads nothing.
305
+ - Revisions written before you enable row storage keep their inline rows.
306
+ Reads handle both forms whether or not the option is set.
307
+ - Missing or corrupt rows inside the retention window make
308
+ `get_media_buy_delivery` fail with `SERVICE_UNAVAILABLE` rather than reporting
309
+ the revision as absent; expired rows read like an unknown revision.
310
+ - Managed Delivery claims hydrate PostgreSQL-stored rows automatically.
311
+
312
+ To move existing inline rows into row storage, run the resumable migration in
313
+ bounded batches. A revision whose rows do not reproduce its committed binding is
314
+ reported in `quarantined` and left untouched:
315
+
316
+ ```ts
317
+ let cursor: string | undefined;
318
+ do {
319
+ const batch = await store.migrateInlineRows({ limit: 100, cursor });
320
+ if (batch.quarantined.length) console.warn('unverifiable inline rows', batch.quarantined);
321
+ cursor = batch.cursor;
322
+ } while (cursor);
323
+ ```
324
+
325
+ Removing rows from ledger documents frees space only after `VACUUM FULL` or an
326
+ online repack of `adcp_reporting_revisions` and `adcp_reporting_adjustments`.
327
+
328
+ ### Object storage bindings
329
+
330
+ Rows can live in object storage instead of PostgreSQL. A binding is defined in
331
+ host code, which makes the configuration the allowlist: the database records
332
+ which binding each revision used and its identity digest, but it can never
333
+ point reads or writes at a destination the host did not configure.
334
+
335
+ ```ts
336
+ import {
337
+ PostgresReportingLedgerStore,
338
+ createFilesystemReportingRowObjectProviderV1,
339
+ } from '@adcp/sdk/reporting/ledger';
340
+
341
+ const store = new PostgresReportingLedgerStore(pool, {
342
+ acknowledgeIsolatedDatabase: true,
343
+ rowStorage: {
344
+ deploymentNamespace: 'prod-us',
345
+ bindings: {
346
+ inline: { kind: 'postgres' },
347
+ reports: {
348
+ kind: 'object',
349
+ provider: 'filesystem', // or a cloud provider registered below
350
+ location: { root: 'reports' },
351
+ prefix: 'adcp-rows',
352
+ compression: 'gzip',
353
+ },
354
+ },
355
+ providers: {
356
+ filesystem: createFilesystemReportingRowObjectProviderV1({ roots: { reports: '/var/lib/adcp/rows' } }),
357
+ },
358
+ selectRowBinding: ({ canonical_byte_count }) => (canonical_byte_count < 256 * 1024 ? 'inline' : 'reports'),
359
+ },
360
+ });
361
+ await store.readyRowStorage();
362
+ ```
363
+
364
+ - Each chunk is one object, written create-only. If an object already exists at
365
+ a planned key, it is adopted only when its bytes decode to the exact chunk;
366
+ otherwise the commit fails with `CONTENT_CONFLICT`.
367
+ - Keys always start with `{prefix}/{namespace_key}/` and use a hashed account
368
+ segment, never a raw account ID. Override `keyTemplate` with the placeholders
369
+ in `REPORTING_ROW_KEY_PLACEHOLDERS`; templates must include
370
+ `{content_sha256}` and `{chunk_index}`.
371
+ - An upload runs only while the obligation lease still covers
372
+ `writeDeadlineMilliseconds` plus five seconds. A fenced write intent records
373
+ every object the upload created, and the ledger commit closes the intent in
374
+ the same transaction. Objects of an upload that never committed are left for
375
+ the intent sweep.
376
+ - Every read verifies the stored bytes against the recorded physical digest
377
+ before decompressing, then verifies segments, chunks and the revision binding.
378
+ - Keep a retired binding configured while revisions still reference it.
379
+ - Custom providers implement `ReportingRowObjectProviderV1`. Run
380
+ `runReportingRowObjectProviderConformanceV1(provider, { location, prefix })`
381
+ against a dedicated test prefix before using one.
382
+
383
+ #### Cloud providers
384
+
385
+ Cloud providers wrap official clients that the host constructs with its own
386
+ credentials. A binding's `location` holds only non-secret coordinates, and its
387
+ optional `credentialRef` names one of the clients you injected; an unknown
388
+ reference fails `UNSAFE_BINDING`. Provider errors surface only as stable
389
+ `ReportingRowStoreError` codes. Row buckets are separate from Managed Delivery
390
+ buckets.
391
+
392
+ **Google Cloud Storage** (`@adcp/sdk/reporting/gcs`, optional peer
393
+ `@google-cloud/storage`):
394
+
395
+ ```ts
396
+ import { Storage } from '@google-cloud/storage';
397
+ import { createGcsReportingRowObjectProviderV1 } from '@adcp/sdk/reporting/gcs';
398
+
399
+ const gcs = createGcsReportingRowObjectProviderV1({
400
+ storage: new Storage(), // keyless credentials (workload identity / ADC)
401
+ clients: { 'tenant-eu': euStorage }, // optional, selected by credentialRef
402
+ });
403
+ // binding: { kind: 'object', provider: 'gcs', location: { bucket: 'acme-adcp-rows' }, prefix: 'adcp-rows' }
404
+ // providers: { gcs }
405
+ ```
406
+
407
+ - Writes are single-request uploads with `ifGenerationMatch: 0` and a declared
408
+ MD5, without `Content-Encoding`. The recorded native version is the object
409
+ generation; reads and deletes pin it, and reads never transcode.
410
+ - The probe requires uniform bucket-level access and enforced public access
411
+ prevention. Versioning, soft delete and retention policies are allowed. A
412
+ lifecycle `Delete` rule that can match live objects under the binding prefix
413
+ (no `matchesPrefix`, or a prefix that overlaps it) is refused; rules limited
414
+ to noncurrent versions (`isLive: false`, `numNewerVersions`,
415
+ `daysSinceNoncurrentTime`, `noncurrentTimeBefore`) are allowed. Keep
416
+ retention-driven deletion in the ledger's pruning, not in bucket lifecycle.
417
+ - The client needs `storage.buckets.get` for the probe plus object create, get
418
+ and delete on the bucket.
419
+
420
+ ### Retention
421
+
422
+ Retention is period-aligned and opt-in. A period becomes eligible once both its
423
+ end and its latest publication are older than `max(statusRetentionDays,
424
+ recordRetentionDays)`, no worker holds its lease, and no Managed Delivery
425
+ materialization still references it. Superseded snapshots stay readable for the
426
+ whole window, as the protocol requires.
427
+
428
+ ```ts
429
+ await store.retireExpiredPeriods({ statusRetentionDays: 30, recordRetentionDays: 45, limit: 100 });
430
+ await store.sweepRowWriteIntents({ limit: 100 }); // abandoned object uploads
431
+ ```
432
+
433
+ Retirement runs in three resumable phases. First it records a `retiring`
434
+ tombstone and marks the period's rows as expired. Then it deletes external row
435
+ objects at their recorded versions. Finally it deletes the period's rows and
436
+ ledger records together. The tombstone keeps the final revision ID and digest,
437
+ and planners never re-create a retired period. `ledger_retained_from` advances
438
+ because the retired obligations no longer exist.
439
+
440
+ `createPostgresReliableReportingProductionService` accepts
441
+ `retention: { enabled: true, recordRetentionDays?, limit? }`. Its scheduler
442
+ always sweeps abandoned row uploads and expired cursor snapshots and
443
+ checkpoints, and retires periods when retention is enabled.
444
+
445
+ ### Host reads: current revisions and the change feed
446
+
447
+ In-process consumers such as pacing, alerts and warehouse loaders read from the
448
+ ledger, never from a warehouse copy.
449
+
450
+ ```ts
451
+ import { REPORTING_LEDGER_CHANGES_MIGRATION } from '@adcp/sdk/reporting/ledger';
452
+
453
+ await pool.query(REPORTING_LEDGER_CHANGES_MIGRATION); // PostgreSQL 13+
454
+ const store = new PostgresReportingLedgerStore(pool, { acknowledgeIsolatedDatabase: true, changeFeed: true });
455
+
456
+ const current = await store.getCurrentRevision({ account_id, reporting_obligation_id });
457
+ const { revisions, cursor: next } = await store.listCurrentRevisions({
458
+ account_id,
459
+ period_start_from: '2026-10-01T00:00:00Z',
460
+ period_start_to: '2026-10-08T00:00:00Z',
461
+ });
462
+
463
+ let cursor = await loadCursor();
464
+ const page = await store.changesAfter({ account_id, cursor, kinds: ['revision'] });
465
+ // ...apply page.records...
466
+ await store.saveFeedConsumerCursor('pacing', page.cursor); // holds back pruning
467
+ ```
468
+
469
+ - The current revision is the official revision if one exists, otherwise the
470
+ highest-numbered snapshot. An official revision is terminal, so this is always
471
+ one index lookup.
472
+ - `changesAfter` lists committed obligation, revision, adjustment and retirement
473
+ changes. Account feeds order by sequence. The deployment-wide feed (omit
474
+ `account_id`) orders by `(xid, seq)` and returns only changes whose
475
+ transaction precedes every transaction still in flight, so a slow commit is
476
+ never skipped.
477
+ - `pruneChanges` deletes changes older than `changeRetentionDays` (default 30)
478
+ that every consumer updated within `maxFeedHoldDays` (default 7) has passed.
479
+ An older cursor throws `ReportingChangeCursorExpiredError`; resynchronize from
480
+ `listCurrentRevisions`.
481
+ - The production service accepts `changeFeed: true | { changeRetentionDays,
482
+ maxFeedHoldDays }` and prunes the feed on its scheduler.
483
+
275
484
  ## Managed Delivery and Reconciled Billing
276
485
 
277
486
  Core remains the default and has no destination, external-resource, or receipt dependency. To opt into the higher tiers, apply `REPORTING_MANAGED_DELIVERY_MIGRATION` **after** `REPORTING_LEDGER_MIGRATION`, explicitly construct the Core store with `managedDelivery: true`, create a `PostgresReportingManagedDeliveryStore`, and pass both stores with a destination adapter to `createReportingManagedDeliveryRuntime`. The async factory proves the stores share one authority and validates the RC3 tier wiring before returning it. It advertises `managed_delivery` only when an immutable binding, delivery, bounded resource reading, generation-fenced revocation, and at least one verification profile are installed. The advertised automated recovery window must be at least the widest installed managed Core configuration recovery window. It advertises `reconciled_billing` and `receipt_task` only when an authenticated consumer resolver and canonical-digest verification are also installed.
package/docs/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # Ad Context Protocol (AdCP)
2
2
 
3
- > Generated at: 2026-10-08
4
- > Library: @adcp/sdk v15.3.0
3
+ > Generated at: 2026-10-09
4
+ > Library: @adcp/sdk v15.5.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.
@@ -136,6 +136,8 @@ A submitted mutation must settle before it can be controlled; retain the task ha
136
136
 
137
137
  **Existing applications.** The [thin existing-platform recipe](./guides/EXISTING-PLATFORM.md) shows one SDK task inside application-owned auth and transactions, durable submitted-task recording, the error/cancellation matrix, non-blocking bounded diagnostics, and same-instance capability evidence reuse with `AgentClient.createWithCapabilityPreflight()` (or the lower-level `getCapabilityEvidenceScope()` + `primeCapabilities()` pair). The SDK 14 release represented by the checkout, its registry integrity check, peer/runtime ranges, wire pin, and a historical rc.33/rc.35 → rc.36 example are generated in [the release worksheet](./migration-14.x-rc-worksheet.md).
138
138
 
139
+ **Request-local clients.** Share endpoint and capability discovery with an application-owned `discoveryCache` and explicit authorization identity. The public `withMCPConnectionScope()` groups a workflow's MCP calls onto reusable sessions; correlation headers, cancellation and deadlines remain per call. See [Discovery cache and session reuse](./guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md).
140
+
139
141
  ## Canonical Reference Resolver
140
142
 
141
143
  `format_schema` and `platform_extensions` references use immutable `{ uri, digest }` pointers. Use `createCanonicalReferenceResolver` from `@adcp/sdk/canonical-references` instead of raw fetches; it applies SSRF-safe DNS-pinned fetches, redirect blocking, timeout/body caps, SHA-256 verification, structured non-throwing statuses, and bounded policy-scoped LRU caching. The zero-argument cache holds at most 64 entries / 32 MiB estimated retained data; use `createCanonicalReferenceCache({ maxEntries, maxBytes })` to tune the per-resolver budget or inject a fully caller-owned cache.
@@ -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.3.0` |
15
- | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.3.0 dist.integrity` |
14
+ | Exact npm package | `@adcp/sdk@15.5.0` |
15
+ | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.5.0 dist.integrity` |
16
16
  | Node.js runtime | `^20.19.0 || >=22.12.0` |
17
17
  | Default AdCP wire release | `3.2.3` |
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`, `3.2.2`, `3.2.3` |
@@ -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.3.0'
25
- npm view '@adcp/sdk@15.3.0' dist.integrity
24
+ npm install --save-exact '@adcp/sdk@15.5.0'
25
+ npm view '@adcp/sdk@15.5.0' dist.integrity
26
26
  ```
27
27
 
28
28
  ### Required and optional peers
@@ -372,6 +372,14 @@ await processWebhook(sender, payload);
372
372
 
373
373
  ## Failure Responses
374
374
 
375
+ Use `WebhookSignatureError.retryable` when deciding whether a failed RFC 9421
376
+ verification can be retried. Capabilities or brand.json discovery failures with
377
+ HTTP 408, 429, or 5xx are retryable, as are DNS failures, timeouts, and connection
378
+ resets. Other HTTP 4xx and SSRF policy refusals remain terminal; a temporary
379
+ discovery failure still requires successful signature verification before
380
+ processing the webhook.
381
+ Retry transient failures with backoff.
382
+
375
383
  Use `401` for signature failures. Do not include resolved private IPs, raw
376
384
  signature bases, JWKS bodies, secrets, or raw payload bytes in the response.
377
385
  Log correlation IDs and typed error codes; keep detailed material in a secure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcp/sdk",
3
- "version": "15.3.0",
3
+ "version": "15.5.0",
4
4
  "description": "AdCP SDK — client, server, and compliance harnesses for the AdContext Protocol (MCP + A2A)",
5
5
  "workspaces": [
6
6
  ".",