Add remote subscription support so spokes can consume streaming operations
over pubsub transports (WebSocket, Redis). Extract checkAccess to access.ts
to break circular dep between call.ts and subscribe.ts.
- Remove separate spec lookup, handler lookup, access control, and input validation from buildCallHandler
- Call registry.execute() directly; access control enforced via execute() (trusted not set)
- On error, look up spec for errorSchemas and pass to mapError()
- Make callMap required in CallHandlerConfig (no longer optional)
- Update tests: remove no-callMap tests, use callMap for all handler tests
- Add test for mapError with spec errorSchemas
- All 226 tests passing
- Add access control to registry.execute(): checks requiredScopes, requiredScopesAny,
and resourceType/resourceAction; rejects with ACCESS_DENIED when identity required
but absent; skips when context.trusted is true
- Add trusted field to OperationContext schema (internal, set by buildEnv for
nested calls to skip redundant scope checks)
- Simplify CallHandler to thin adapter: delegates to registry.execute() instead of
duplicating lookup, validation, and access control
- Remove callMap option from buildEnv(): always uses execute(), propagates context
with trusted: true for nested calls
- Add access control to subscribe(): same default-deny logic as execute()
- Change execute() to throw CallError instead of plain Error for not found,
no handler, and validation errors
- Export checkAccess from call.ts and index.ts for external use
- Remove CallMap type export, update EnvOptions
- Update architecture docs: api-surface.md, call-protocol.md,
ADR-006 status to implemented, source vs spec drift sections
- All 228 tests passing
- CallEventSchema['call.responded'].output changed from Type.Unknown() to ResponseEnvelopeSchema
- PendingRequestMap.respond() now validates output with isResponseEnvelope(), throws on raw values
- PendingRequestMap.call() return type changed from Promise<unknown> to Promise<ResponseEnvelope>
- CallHandler captures handler return value instead of discarding it
- CallHandler applies result pipeline: detect envelope → wrap with localEnvelope → normalize with Value.Cast → validate with collectErrors
- CallHandler publishes call.responded via callMap.respond() with the envelope
- CallHandler publishes call.error via callMap.emitError() when callMap is provided (instead of re-throwing)
- CallHandlerConfig changed from eventTarget? to callMap? (PendingRequestMap)
- Adapter handlers pass through via isResponseEnvelope() detection (mcpEnvelope/httpEnvelope)
- All 189 tests passing, including 23 new tests for envelope behavior
The resource access check in checkAccess() was bypassed when identity.resources
was undefined because the condition evaluated to false, falling through to .
Changed to with an explicit
check inside the block, implementing
default-deny semantics per ADR-006.
Added 7 test cases covering:
- undefined resources with resourceType set (denied)
- empty resources with resourceType set (denied)
- non-matching resource type (denied)
- matching type but wrong action (denied)
- matching type and action (granted)
- no resourceType/resourceAction set (granted)
- matching resources with extra scopes (granted)
Extracted from alkhub_ts packages/core/operations/ and packages/core/mcp/.
- Runtime-agnostic (injected fs/env deps, no Deno globals)
- Direct @logtape/logtape import instead of logger wrapper
- PendingRequestMap with pubsub-wired call protocol
- Peer-dep isolation for MCP adapter (sub-path export)
- Schema const naming convention (XSchema + X type alias)
- 68 tests passing, build + lint + test all green