feat(com): propagate caller identity across cross-environment calls and subscriptions#2851
feat(com): propagate caller identity across cross-environment calls and subscriptions#2851vladbazl wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces end-to-end caller identity propagation for cross-environment RPC calls and listener subscriptions by stamping identity onto inbound Socket.IO messages and flowing it through the message forwarding chain, exposing it to server-side handlers via an AsyncLocalStorage-backed accessor.
Changes:
- Add
callerIdentityto the core message model and propagate it on call/listen/unlisten messages. - Introduce AsyncLocalStorage-based caller context utilities (
getCurrentCaller,runWithCaller) and wrap inbound handler execution to set/restore identity. - Add runtime-node support for extracting identity from Socket.IO handshake (
IdentityExtractor) and stamping it onto every inbound message; add unit tests covering single-hop, chained calls, concurrency isolation, and subscription/unsubscription.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-node/test/caller-identity.unit.ts | Adds unit tests for identity extraction, propagation, and isolation across calls and subscriptions. |
| packages/runtime-node/src/ws-node-host.ts | Adds IdentityExtractor, stores per-client identity, and stamps callerIdentity onto inbound messages. |
| packages/runtime-node/src/node-env-manager.ts | Plumbs optional identityExtractor into WsServerHost setup. |
| packages/core/src/com/message-types.ts | Adds callerIdentity?: unknown to BaseMessage. |
| packages/core/src/com/index.ts | Exports the new caller-context utilities. |
| packages/core/src/com/communication.ts | Attaches callerIdentity to outbound messages and wraps inbound call/listen/unlisten handling in runWithCaller. |
| packages/core/src/com/caller-context.ts | Introduces AsyncLocalStorage-backed caller context (getCurrentCaller, runWithCaller). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
| } | ||
| try { | ||
| const moduleName = 'node:async_hooks'; | ||
| const { AsyncLocalStorage } = await import(moduleName); |
There was a problem hiding this comment.
@engine/core is isomorphic.
please do not use node APIs in isomorphic code.
avoid using a dynamic import in a try-catch to account for this.
There was a problem hiding this comment.
refactoring this part
There was a problem hiding this comment.
Made it a configuration of COM feature [optional], without a conditional dependency (dynamic import) on NodeJS lib.
| getStore<T = unknown>(): T | undefined; | ||
| run<R>(identity: unknown, callback: () => R): R; | ||
| } | ||
| let callerStore: CallerContext | undefined | null; |
There was a problem hiding this comment.
Because it leaks between different runs in the same runtime environment, as it depends on module evaluation to initialize and cleanup. This fact makes harder to manage and not testing friendly, by design.
It is a hack/cheat to use it a in a controlled environment, where you'd like to remove leftovers and ensure cleanup.
Not the first instance I'm commenting about this. Told the AI Squad the same when they did it in one of the tools.
There was a problem hiding this comment.
This part was updated, now it is configurable through COM feature settings and each thread can receive a different implementation.
The CallerContext is still set in module though, this is needed to make the Caller Context API nice and avoid changes (e.g. dependencies on Communication instance) in every place where caller identity is needed.
Also this functionality is covered with tests.
…figurable instead Co-authored-by: Copilot <copilot@github.com>
Problem.
Server-side environments need to know who is making API calls.
Identity is derived from the Socket.IO handshake (e.g. cookies, auth headers). When a request is forwarded through multiple envs — client → processing → workspace — every server-side handler in the chain must see the original client's identity.
Solution.
autoLaunchreceivesidentityExtractor. Example:callerContextinCOMfeature config with using a standard AsyncLocalStorage library. Example:getCurrentCaller()in any method. Example: