Skip to content

refactor(signing): thread signingContext via AsyncLocalStorage#615

Merged
bokelley merged 2 commits into
mainfrom
bokelley/issue-597
Apr 19, 2026
Merged

refactor(signing): thread signingContext via AsyncLocalStorage#615
bokelley merged 2 commits into
mainfrom
bokelley/issue-597

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Closes #597.

Summary

  • Adds signingContextStorage (AsyncLocalStorage<AgentSigningContext | undefined>) alongside the existing capability cache helpers in src/lib/signing/agent-context.ts.
  • Top-level protocol entries (callMCPTool, callMCPToolRaw, callMCPToolWithTasks, callA2ATool) now wrap their implementations in signingContextStorage.run(signingContext, ...) so the internal helpers can read the context from storage instead of receiving it as a parameter.
  • Drops the signingContext parameter from the internal plumbing — withCachedConnection, getOrCreateConnection, connectMCPWithFallback, connectMCPWithFallbackImpl, callMCPToolImpl, callMCPToolRawImpl, getOrCreateA2AClient, createA2AClient, buildFetchImpl. Each now reads signingContextStorage.getStore() at the point it needs the context (cache-key build, fetch-wrapper build).
  • Keeps the public entry-point signatures untouched — ProtocolClient.callTool, callMCPToolWithOAuth, connectMCP, and the top-level MCP / A2A helpers still accept an explicit signingContext, per the issue's non-goal.
  • buildFetchImpl captures the context at client-creation time; the A2A client cache is already keyed per signing-fingerprint, so different signing identities get different cached clients and build their own fetch wrappers in separate ALS scopes.
  • Always wraps in ALS.run(context) — including with undefined — so non-signing calls cannot inherit a stale context from an enclosing scope.

Test plan

  • Existing test/request-signing-agent-integration.test.js (all 12 prior tests) — passes unmodified.
  • New test: four concurrent ProtocolClient.callTools against the same seller URL with two distinct signing identities (Ed25519 + ES256) — each inbound request's Signature-Input carries its own keyid, proving no cross-contamination.
  • New test: signing call followed by non-signing call in the same async chain — the non-signing request lands at the stub without Signature-Input, proving the ALS scope is released when the first call resolves.
  • Full suite: 3860 pass, 4 pre-existing governance failures that also fail on main.

🤖 Generated with Claude Code

bokelley and others added 2 commits April 19, 2026 07:03
Flattens the signingContext parameter that #593 pushed through nine
function signatures in the MCP and A2A transports. Top-level entries
(callMCPTool, callMCPToolRaw, callMCPToolWithTasks, callA2ATool) push
the context onto a new signingContextStorage ALS for the duration of
the call; internal helpers read it from storage instead of receiving
it as a parameter.

Public entry-point signatures are unchanged, so external callers and
integration tests continue to pass signingContext explicitly. The
cache-key disambiguator still encodes the signing fingerprint so
cached transports can't be reused across signing identities.

Adds tests for interleaved concurrent callTool invocations with
distinct signing identities and for the no-leak property of sequential
signing/non-signing calls in the same async chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses reviewer feedback on #615:
- Note that ProtocolClient.callTool's explicit signingContext arg is the
  seed for each transport's signingContextStorage.run, not incidental
  plumbing.
- Document that task lifecycle methods (tasks/get, tasks/result,
  tasks/list, tasks/cancel) are MCP protocol methods, not AdCP ops, so
  extractAdcpOperation returns undefined and the signing fetch passes
  them through unsigned regardless of ALS state.
- Note that callMCPToolWithOAuth consumes signingContext via the
  transport (connectMCP attaches the signing-fetch wrapper at
  creation time) rather than via ALS, because OAuth connections are
  not pooled.

No behavior change — comments only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit bba6f3e into main Apr 19, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: flatten signingContext threading via AsyncLocalStorage

1 participant