docs: clean up ADR-005 architecture docs after envelope implementation

Remove stale ADR-005 drift tables across all architecture docs since
ResponseEnvelope types, factories, detection, and integration points
are now fully implemented in source code. Key changes:

- api-surface.md: Remove ADR-005 drift table (all items implemented),
  retain ADR-006 drift table without execute() return type (now done)
- call-protocol.md: Remove ADR-005 drift table, update ADR-006 table,
  fix CallHandlerConfig to show callMap? (current source)
- adapters.md: Remove 'current source state' and 'implementation
  changes needed' tables for from_mcp and from_openapi, replace with
  current-accurate descriptions of envelope behavior
- response-envelopes.md: Remove 'current source state' blocks,
  update migration checklist to show all code changes completed
- 005-response-envelopes.md: Change status from Draft to Implemented
- 006-unified-invocation-path.md: Update Prerequisites section to note
  ADR-005 is now implemented
- build-distribution.md: Add response-envelope.ts to source layout
- architecture.md: Add response-envelopes.md link and ADR-005/006
  entries to design decisions table
- README.md: Add response-envelopes.md to documents table
- Update last_updated dates on all changed docs
This commit is contained in:
2026-05-11 02:55:13 +00:00
parent d74b750ecb
commit ddc0607b90
9 changed files with 40 additions and 107 deletions

View File

@@ -7,6 +7,7 @@
| [architecture/README.md](architecture/README.md) | Overview, why this exists, what it provides, consumer context, threat model | | [architecture/README.md](architecture/README.md) | Overview, why this exists, what it provides, consumer context, threat model |
| [architecture/api-surface.md](architecture/api-surface.md) | All public types, registry API, call protocol API, subscribe, env, adapters | | [architecture/api-surface.md](architecture/api-surface.md) | All public types, registry API, call protocol API, subscribe, env, adapters |
| [architecture/call-protocol.md](architecture/call-protocol.md) | PendingRequestMap, CallHandler, call≡subscribe semantics, events, error model, access control | | [architecture/call-protocol.md](architecture/call-protocol.md) | PendingRequestMap, CallHandler, call≡subscribe semantics, events, error model, access control |
| [architecture/response-envelopes.md](architecture/response-envelopes.md) | Response envelope types, factory functions, detection, schemas, integration points |
| [architecture/adapters.md](architecture/adapters.md) | from_schema, from_openapi, from_mcp, scanner — how they work, how to add new adapters | | [architecture/adapters.md](architecture/adapters.md) | from_schema, from_openapi, from_mcp, scanner — how they work, how to add new adapters |
| [architecture/build-distribution.md](architecture/build-distribution.md) | Dependencies, project structure, sub-path exports, peer deps, build tooling | | [architecture/build-distribution.md](architecture/build-distribution.md) | Dependencies, project structure, sub-path exports, peer deps, build tooling |
@@ -17,4 +18,6 @@
| [001](architecture/decisions/001-logger-direct-import.md) | Direct @logtape/logtape import instead of wrapper module | | [001](architecture/decisions/001-logger-direct-import.md) | Direct @logtape/logtape import instead of wrapper module |
| [002](architecture/decisions/002-fs-injection.md) | Inject filesystem dependencies for runtime agnosticism | | [002](architecture/decisions/002-fs-injection.md) | Inject filesystem dependencies for runtime agnosticism |
| [003](architecture/decisions/003-peer-dep-adapters.md) | Peer dependencies for adapter isolation (MCP SDK, @std/path) | | [003](architecture/decisions/003-peer-dep-adapters.md) | Peer dependencies for adapter isolation (MCP SDK, @std/path) |
| [004](architecture/decisions/004-schema-const-naming.md) | Schema const naming convention (AccessControlSchema + AccessControl type) | | [004](architecture/decisions/004-schema-const-naming.md) | Schema const naming convention (AccessControlSchema + AccessControl type) |
| [005](architecture/decisions/005-response-envelopes.md) | Response Envelopes for transport-aware results |
| [006](architecture/decisions/006-unified-invocation-path.md) | Unified Invocation Path (execute as single entry point) |

View File

@@ -1,6 +1,6 @@
--- ---
status: draft status: draft
last_updated: 2026-05-09 last_updated: 2026-05-11
--- ---
# @alkdev/operations Architecture # @alkdev/operations Architecture
@@ -74,6 +74,7 @@ Spokes will import `@alkdev/operation` for operation definitions and `@alkdev/pu
|----------|---------| |----------|---------|
| [api-surface.md](api-surface.md) | All public types, registry, call protocol, subscribe, env, adapters | | [api-surface.md](api-surface.md) | All public types, registry, call protocol, subscribe, env, adapters |
| [call-protocol.md](call-protocol.md) | PendingRequestMap, CallHandler, call≡subscribe, events, error model, access control | | [call-protocol.md](call-protocol.md) | PendingRequestMap, CallHandler, call≡subscribe, events, error model, access control |
| [response-envelopes.md](response-envelopes.md) | Response envelope types, factory functions, detection, schemas, integration points |
| [adapters.md](adapters.md) | from_schema, from_openapi, from_mcp, scanner — how they work, how to add new adapters | | [adapters.md](adapters.md) | from_schema, from_openapi, from_mcp, scanner — how they work, how to add new adapters |
| [build-distribution.md](build-distribution.md) | Dependencies, project structure, sub-path exports, peer deps, build tooling | | [build-distribution.md](build-distribution.md) | Dependencies, project structure, sub-path exports, peer deps, build tooling |

View File

@@ -1,6 +1,6 @@
--- ---
status: draft status: draft
last_updated: 2026-05-09 last_updated: 2026-05-11
--- ---
# Adapters # Adapters
@@ -79,13 +79,7 @@ Processes all paths in the spec. For each path and method combination:
- Applies auth headers from config - Applies auth headers from config
- Returns JSON, text, or `ArrayBuffer` based on response content type - Returns JSON, text, or `ArrayBuffer` based on response content type
**Current source state** (`src/from_openapi.ts`): The handler currently returns raw response data — `response.json()`, `response.text()`, or `response.arrayBuffer()` (lines 273-279). It does NOT wrap the result in `httpEnvelope()`. Error handling throws a plain `Error` (line 268) instead of `CallError`. The response-envelopes spec requires wrapping in `httpEnvelope()` and throwing `CallError` on HTTP errors. The `Value.Cast()` normalization step against `outputSchema` is also not yet implemented. See [response-envelopes.md](response-envelopes.md) for the full specification. The handler wraps results in `httpEnvelope()` with HTTP metadata (status code, headers, content type). On HTTP error status, it throws `CallError("EXECUTION_ERROR", ...)`. `Value.Cast()` normalization against `outputSchema` is applied by `registry.execute()` and `CallHandler` as part of the shared result pipeline — see [response-envelopes.md](response-envelopes.md#shared-result-pipeline).
| What | Current source (`src/from_openapi.ts`) | Target (per response-envelopes spec) |
|------|------------------------------------------|---------------------------------------|
| Handler return value | Returns raw `response.json()` / `.text()` / `.arrayBuffer()` (lines 273-279) | Returns `httpEnvelope(data, { statusCode, headers, contentType })` |
| Error handling | Throws `Error(\`HTTP ${status}\`)` (line 268) | Throws `CallError("EXECUTION_ERROR", ...)` |
| `Value.Cast()` | Not used | If `outputSchema !== Unknown`, cast `Value.Cast(outputSchema, data)` |
### `FromOpenAPIFile(path, config, fs?)` ### `FromOpenAPIFile(path, config, fs?)`
@@ -179,11 +173,11 @@ async function createMCPClient(
- `namespace`: the `name` parameter (used as grouping) - `namespace`: the `name` parameter (used as grouping)
- `type`: `MUTATION` (all MCP tools are mutations) - `type`: `MUTATION` (all MCP tools are mutations)
### `FromSchema(tool.inputSchema)` (converts JSON Schema to TypeBox) ### `FromSchema(tool.inputSchema)` (converts JSON Schema to TypeBox)
- `outputSchema`: `tool.outputSchema ? FromSchema(tool.outputSchema) : Type.Unknown()` (MCP spec 2025-06-18+ provides `outputSchema`; older tools lack it) - `outputSchema`: `tool.outputSchema ? FromSchema(tool.outputSchema) : Type.Unknown()` (MCP spec 2025-06-18+ provides `outputSchema`; older tools lack it)
- `handler`: calls `client.callTool({ name, arguments })`, wraps result in `mcpEnvelope()` - `handler`: calls `client.callTool({ name, arguments })`, wraps result in `mcpEnvelope()`
- `accessControl`: `{ requiredScopes: [] }` (no auth by default) - `accessControl`: `{ requiredScopes: [] }` (no auth by default)
**Current source state** (`src/from_mcp.ts` line 66): `outputSchema: Type.Unknown()` for all tools. The `tool.outputSchema` property is not used. The handler currently throws on `isError` (line 76) and returns `result.content` directly (line 79) instead of wrapping in `mcpEnvelope()`. The `structuredContent` field on `CallToolResult` is not used. See [response-envelopes.md](response-envelopes.md) for the full specification of what needs to change. The handler returns pre-built `ResponseEnvelope` instances via `mcpEnvelope()`. `isError: true` results are wrapped in the envelope (not thrown), so consumers check `envelope.meta.isError`. `structuredContent` is preferred as `envelope.data` when available; otherwise `mapMCPContentBlocks(result.content)` is used. `Value.Cast()` normalization against `outputSchema` is applied by `registry.execute()` and `CallHandler` as part of the shared result pipeline.
### outputSchema and structuredContent ### outputSchema and structuredContent
@@ -203,17 +197,7 @@ When a tool does NOT declare `outputSchema`:
See [response-envelopes.md](response-envelopes.md) for the full envelope specification and envelope stripping with `Value.Cast()`. See [response-envelopes.md](response-envelopes.md) for the full envelope specification and envelope stripping with `Value.Cast()`.
**Implementation changes needed** (tracking spec vs. current source): The `CallToolResult` type in the installed SDK (`@modelcontextprotocol/sdk` DRAFT-2026-v1) includes `structuredContent?: { [key: string]: unknown }` and the `Tool` type includes `outputSchema?`. The adapter code extracts `outputSchema` at discovery time and uses `structuredContent` at call time.
| What | Current source (`src/from_mcp.ts`) | Target (per response-envelopes spec) |
|------|--------------------------------------|---------------------------------------|
| `outputSchema` at discovery | `Type.Unknown()` for all tools (line 66) | `tool.outputSchema ? FromSchema(tool.outputSchema) : Type.Unknown()` |
| Handler return value | Returns `result.content` (line 79) | Returns `mcpEnvelope(data, meta)` |
| `structuredContent` | Not used | Prefer as `data` when present; fall back to `mapMCPContentBlocks(result.content)` |
| `isError` handling | Throws `Error` (line 76) | Wraps in envelope with `meta.isError: true`, does NOT throw |
| `Value.Cast()` | Not used | If `structuredContent && outputSchema !== Unknown`, cast `Value.Cast(outputSchema, structuredContent)` |
The `CallToolResult` type in the installed SDK (`@modelcontextprotocol/sdk` DRAFT-2026-v1) already includes `structuredContent?: { [key: string]: unknown }` and the `Tool` type already includes `outputSchema?`. No SDK upgrade needed — only the adapter code needs updating.
### `MCPClientConfig` ### `MCPClientConfig`

View File

@@ -1,6 +1,6 @@
--- ---
status: draft status: draft
last_updated: 2026-05-10 last_updated: 2026-05-11
--- ---
# API Surface # API Surface
@@ -373,23 +373,7 @@ See [adapters.md](adapters.md) for detailed adapter documentation.
## Source vs. Spec Drift ## Source vs. Spec Drift
This section documents differences between the architecture spec (this document) and the current source code. Items marked **ADR-005** or **ADR-006** are planned changes not yet implemented. This section documents differences between the architecture spec and the current source code. ADR-005 (Response Envelopes) has been fully implemented — all envelope types, factories, detection, and integration points are in source and match the spec. ADR-006 (Unified Invocation Path) is not yet implemented.
### ADR-005 (Response Envelopes) — not yet implemented
| What | Spec says | Source currently does |
|------|----------|----------------------|
| `ResponseEnvelope`, `ResponseMeta`, factory functions, `isResponseEnvelope()`, `unwrap()` | Exported from `src/response-envelope.ts` | None of these types or functions exist in source |
| `execute()` return type | `Promise<ResponseEnvelope<TOutput>>` | `Promise<TOutput>` |
| `execute()` result pipeline | Detect → wrap → normalize → validate | Returns raw `result`, validates raw output with `collectErrors` |
| `OperationEnv` inner function return type | `Promise<ResponseEnvelope>` | `Promise<unknown>` |
| `PendingRequestMap.call()` return type | `Promise<ResponseEnvelope>` | `Promise<unknown>` |
| `PendingRequestMap.respond()` validation | Enforces `isResponseEnvelope()`, throws on raw values | Accepts `unknown`, no validation |
| `subscribe()` yield type | `AsyncGenerator<ResponseEnvelope, void, unknown>` | `AsyncGenerator<unknown, void, unknown>` |
| `CallRespondedEvent.output` | `ResponseEnvelope` | `unknown` |
| `CallHandler` description | Wraps handler result, applies pipeline, publishes `call.responded` | Discards handler return value; handler publishes `call.responded` itself |
| `from_mcp` handler | Returns `mcpEnvelope()`, uses `structuredContent`, extracts `outputSchema` | Returns `result.content`, types `outputSchema` as `Type.Unknown()`, throws on `isError` |
| `from_openapi` handler | Returns `httpEnvelope()` with HTTP metadata | Returns raw response data, throws on HTTP error status |
### ADR-006 (Unified Invocation Path) — not yet implemented ### ADR-006 (Unified Invocation Path) — not yet implemented

View File

@@ -1,6 +1,6 @@
--- ---
status: draft status: draft
last_updated: 2026-05-09 last_updated: 2026-05-11
--- ---
# Build & Distribution # Build & Distribution
@@ -43,6 +43,7 @@ Dependencies, project structure, sub-path exports, peer deps, and build tooling.
types.ts # Core types: IOperationDefinition, OperationSpec, OperationType, etc. types.ts # Core types: IOperationDefinition, OperationSpec, OperationType, etc.
registry.ts # OperationRegistry: registerSpec, registerHandler, execute, get, list registry.ts # OperationRegistry: registerSpec, registerHandler, execute, get, list
validation.ts # assertIsSchema, validateOrThrow, collectErrors, formatValueErrors validation.ts # assertIsSchema, validateOrThrow, collectErrors, formatValueErrors
response-envelope.ts # ResponseEnvelope types, factories, detection, schemas, unwrap
call.ts # PendingRequestMap, buildCallHandler, CallEventMap, event types call.ts # PendingRequestMap, buildCallHandler, CallEventMap, event types
subscribe.ts # subscribe(): direct AsyncGenerator execution subscribe.ts # subscribe(): direct AsyncGenerator execution
env.ts # buildEnv(): namespace-keyed env with direct/call-protocol modes env.ts # buildEnv(): namespace-keyed env with direct/call-protocol modes

View File

@@ -1,6 +1,6 @@
--- ---
status: draft status: draft
last_updated: 2026-05-10 last_updated: 2026-05-11
--- ---
# Call Protocol # Call Protocol
@@ -159,7 +159,7 @@ function buildCallHandler(config: CallHandlerConfig): CallHandler
interface CallHandlerConfig { interface CallHandlerConfig {
registry: OperationRegistry registry: OperationRegistry
eventTarget?: EventTarget callMap?: PendingRequestMap
} }
type CallHandler = (event: CallRequestedEvent) => Promise<void> type CallHandler = (event: CallRequestedEvent) => Promise<void>
@@ -309,20 +309,7 @@ This allows spec-only registration for scenarios where handlers are provided sep
## Source vs. Spec Drift ## Source vs. Spec Drift
This section documents differences between the architecture spec (this document) and the current source code. Items are planned changes not yet implemented. This section documents differences between the architecture spec (this document) and the current source code. ADR-005 (Response Envelopes) has been fully implemented — `CallEventSchema["call.responded"].output` uses `ResponseEnvelopeSchema`, `CallHandler` wraps handler return values and publishes `call.responded`, `call()` returns `Promise<ResponseEnvelope>`, `respond()` enforces `isResponseEnvelope()`, `subscribe()` yields `ResponseEnvelope`, and `buildEnv()` returns `Promise<ResponseEnvelope>` per function. ADR-006 (Unified Invocation Path) is not yet implemented.
### ADR-005 (Response Envelopes) — not yet implemented
| What | Spec says | Source currently does |
|------|----------|----------------------|
| `CallEventSchema["call.responded"].output` | `ResponseEnvelopeSchema` | `Type.Unknown()` |
| `CallHandler` behavior | Wraps handler return value, publishes `call.responded` | Discards handler return value; handler must publish itself |
| `CallHandler` error handling | Publishes `call.error` via pubsub | Re-throws `CallError` (does not publish) |
| `call()` return type | `Promise<ResponseEnvelope>` | `Promise<unknown>` |
| `call()` resolution | Resolves with `ResponseEnvelope` from `output` field | Resolves with raw `unknown` from `output` |
| `respond()` validation | Enforces `isResponseEnvelope()` guard, throws on raw values | Accepts `unknown`, no validation |
| `subscribe()` yield type | `AsyncGenerator<ResponseEnvelope, void, unknown>`, wraps yields | `AsyncGenerator<unknown, void, unknown>`, yields raw values |
| `buildEnv()` return types | `Promise<ResponseEnvelope>` per function | `Promise<unknown>` per function |
### ADR-006 (Unified Invocation Path) — not yet implemented ### ADR-006 (Unified Invocation Path) — not yet implemented
@@ -333,7 +320,6 @@ This section documents differences between the architecture spec (this document)
| `CallHandler` calls `execute()` | Thin adapter that calls `registry.execute()` internally | Reimplements lookup, validation, and access control independently | | `CallHandler` calls `execute()` | Thin adapter that calls `registry.execute()` internally | Reimplements lookup, validation, and access control independently |
| `buildEnv()` | Always uses `execute()`, no `callMap` option | Toggles between `execute()` and `callMap.call()` via `if (callMap)` | | `buildEnv()` | Always uses `execute()`, no `callMap` option | Toggles between `execute()` and `callMap.call()` via `if (callMap)` |
| `OperationContext.trusted` | New field for nested call bypass | Does not exist | | `OperationContext.trusted` | New field for nested call bypass | Does not exist |
| `execute()` return type | `Promise<ResponseEnvelope<TOutput>>` | `Promise<TOutput>` |
| `execute()` error type | Throws `CallError` | Throws plain `Error` | | `execute()` error type | Throws `CallError` | Throws plain `Error` |
## References ## References

View File

@@ -1,6 +1,6 @@
# ADR-005: Response Envelopes for Transport-Aware Results # ADR-005: Response Envelopes for Transport-Aware Results
**Status**: Draft **Status**: Implemented
**Date**: 2026-05-10 **Date**: 2026-05-10
## Context ## Context

View File

@@ -1,6 +1,6 @@
--- ---
status: draft status: draft
last_updated: 2026-05-10 last_updated: 2026-05-11
--- ---
# ADR-006: Unified Invocation Path # ADR-006: Unified Invocation Path
@@ -31,7 +31,7 @@ Meanwhile `execute()` is a **domain call** — same-process, same-trust, rich ty
### Prerequisites ### Prerequisites
This ADR depends on ADR-005 (response envelopes) being implemented in source first. The unified `execute()` requires `ResponseEnvelope` types, `isResponseEnvelope()`, and factory functions that don't exist in source yet. This ADR depends on ADR-005 (response envelopes) being implemented in source first. **ADR-005 is now implemented** `ResponseEnvelope` types, `isResponseEnvelope()`, and factory functions exist in `src/response-envelope.ts`. The prerequisite is met.
## Decision ## Decision

View File

@@ -1,6 +1,6 @@
--- ---
status: draft status: draft
last_updated: 2026-05-10 last_updated: 2026-05-11
--- ---
# Response Envelopes # Response Envelopes
@@ -221,15 +221,6 @@ Flow:
**Note**: `isResponseEnvelope()` does not validate that the envelope's `source` matches the operation's origin. An MCP handler that explicitly returns a `localEnvelope(...)` passes through as-is. Handlers that explicitly construct envelopes take responsibility for their metadata. **Note**: `isResponseEnvelope()` does not validate that the envelope's `source` matches the operation's origin. An MCP handler that explicitly returns a `localEnvelope(...)` passes through as-is. Handlers that explicitly construct envelopes take responsibility for their metadata.
**Current source state** (`src/registry.ts` lines 80-105): `execute()` returns `Promise<TOutput>` directly. It does not wrap results in envelopes, does not call `isResponseEnvelope()`, and validates raw `result` against `outputSchema`. The `Value.Cast()` normalization step is not implemented. Changes needed:
| What | Current source | Target |
|------|---------------|--------|
| Return type | `Promise<TOutput>` | `Promise<ResponseEnvelope<TOutput>>` |
| Wrapping | None — returns raw `result` | `isResponseEnvelope(result) ? result : localEnvelope(result, operationId)` |
| Validation | `collectErrors(spec.outputSchema, result)` — on raw value | `collectErrors(spec.outputSchema, envelope.data)` — on envelope data |
| `Value.Cast()` | Not used | If `outputSchema !== Unknown`, `envelope.data = Value.Cast(spec.outputSchema, envelope.data)` |
### `CallHandler` ### `CallHandler`
Takes full ownership of publishing `call.responded`. Handlers return values; they do NOT publish events. Takes full ownership of publishing `call.responded`. Handlers return values; they do NOT publish events.
@@ -246,29 +237,14 @@ Flow:
9. Publish `call.responded` via `callMap.respond(requestId, envelope)` 9. Publish `call.responded` via `callMap.respond(requestId, envelope)`
10. On handler exception → publish `call.error` (existing). Note: an envelope with `meta.isError: true` does **not** trigger `call.error`. Only thrown exceptions do. 10. On handler exception → publish `call.error` (existing). Note: an envelope with `meta.isError: true` does **not** trigger `call.error`. Only thrown exceptions do.
**Current source state** (`src/call.ts` lines 182-233): `buildCallHandler` calls `handler(input, context)` (line 226) but does not use the return value. The handler is expected to publish `call.responded` itself. Changes needed:
| What | Current source | Target |
|------|---------------|--------|
| Handler model | Handler publishes `call.responded` itself; return value ignored | Handler returns value; `CallHandler` wraps and publishes |
| Return value | `await handler(input, context)` called but result discarded | Result captured, wrapped in envelope if needed, then published |
| Envelope detection | Not applicable | `isResponseEnvelope(result)` check before wrapping |
| Result pipeline | None | Detect → wrap → normalize → validate → publish |
| `call.responded.output` | `Type.Unknown()` | `ResponseEnvelopeSchema` |
| `PendingRequestMap.respond()` | Accepts any `unknown` value | Must enforce `isResponseEnvelope()` guard |
### `PendingRequestMap.respond()` ### `PendingRequestMap.respond()`
Enforces that `output` is a `ResponseEnvelope` via the `isResponseEnvelope()` type guard (not full schema validation). If called with a non-envelope value, throws. This prevents any code bypassing `CallHandler`'s envelope wrapping. A future iteration may make `respond()` internal (not exported on the public API surface) to further enforce this invariant. Enforces that `output` is a `ResponseEnvelope` via the `isResponseEnvelope()` type guard (not full schema validation). If called with a non-envelope value, throws. This prevents any code bypassing `CallHandler`'s envelope wrapping. A future iteration may make `respond()` internal (not exported on the public API surface) to further enforce this invariant.
**Current source state** (`src/call.ts` lines 151-156): `respond()` publishes `call.responded` with `output: unknown`. No envelope validation.
### `PendingRequestMap.call()` ### `PendingRequestMap.call()`
Resolves with the `ResponseEnvelope` from `call.responded.output` instead of raw `unknown`. Resolves with the `ResponseEnvelope` from `call.responded.output` instead of raw `unknown`.
**Current source state** (`src/call.ts` lines 120-149): `call()` returns `Promise<unknown>`. The type should become `Promise<ResponseEnvelope>`.
### `subscribe()` ### `subscribe()`
Each yielded value is wrapped in `ResponseEnvelope`. `SubscriptionHandler` implementations still yield raw values — `subscribe()` wraps each yield, consistent with how local handlers return raw values and `execute()` wraps them. If a yielded value `isResponseEnvelope()`, it passes through. Otherwise, `localEnvelope(value, operationId)` with a fresh `timestamp` per yield. Each yielded value is wrapped in `ResponseEnvelope`. `SubscriptionHandler` implementations still yield raw values — `subscribe()` wraps each yield, consistent with how local handlers return raw values and `execute()` wraps them. If a yielded value `isResponseEnvelope()`, it passes through. Otherwise, `localEnvelope(value, operationId)` with a fresh `timestamp` per yield.
@@ -455,26 +431,24 @@ The following documentation changes have been completed:
| `api-surface.md` | `CallHandler` | Wraps handler result, publishes `call.responded`. No longer "handler publishes" model. | ✅ | | `api-surface.md` | `CallHandler` | Wraps handler result, publishes `call.responded`. No longer "handler publishes" model. | ✅ |
| `call-protocol.md` | `PendingRequestMap.respond()` | Now enforces `isResponseEnvelope()` check — throws on raw values. | ✅ | | `call-protocol.md` | `PendingRequestMap.respond()` | Now enforces `isResponseEnvelope()` check — throws on raw values. | ✅ |
| `api-surface.md` | `PendingRequestMap.respond()` | `respond()` now requires `ResponseEnvelope` argument. | ✅ | | `api-surface.md` | `PendingRequestMap.respond()` | `respond()` now requires `ResponseEnvelope` argument. | ✅ |
| `adapters.md` | `from_mcp` | Handler returns `mcpEnvelope()`. MCP `isError: true` no longer throws. | ✅ (previous) | | `adapters.md` | `from_mcp` | Handler returns `mcpEnvelope()`. MCP `isError: true` no longer throws. | ✅ |
| `adapters.md` | `from_mcp` | `outputSchema` extracted when available, via `FromSchema`. Falls back to `Type.Unknown()`. | ✅ (previous) | | `adapters.md` | `from_mcp` | `outputSchema` extracted when available, via `FromSchema`. Falls back to `Type.Unknown()`. | ✅ |
| `adapters.md` | `from_openapi` | Handler returns `httpEnvelope()`. Error on HTTP error status still throws `CallError`. | ✅ (previous) | | `adapters.md` | `from_openapi` | Handler returns `httpEnvelope()`. Error on HTTP error status still throws `CallError`. | ✅ |
The following **code** changes are still needed: The following **code** changes have been completed:
| Code | Change | | Code | Change | Status |
|------|--------| |------|--------|--------|
| `src/registry.ts` | `execute()` returns `Promise<ResponseEnvelope<TOutput>>` | | `src/response-envelope.ts` | New file: types, factory functions, detection, schemas | ✅ |
| `src/call.ts` | `CallHandler` captures return value, wraps in envelope, publishes `call.responded` | | `src/registry.ts` | `execute()` returns `Promise<ResponseEnvelope<TOutput>>` | ✅ |
| `src/call.ts` | `CallEventSchema` `output` field changes to `ResponseEnvelopeSchema` | | `src/call.ts` | `CallHandler` captures return value, wraps in envelope, publishes `call.responded` | ✅ |
| `src/call.ts` | `PendingRequestMap.respond()` adds `isResponseEnvelope()` guard | | `src/call.ts` | `CallEventSchema` `output` field changes to `ResponseEnvelopeSchema` | ✅ |
| `src/call.ts` | `PendingRequestMap.call()` resolves with `ResponseEnvelope` | | `src/call.ts` | `PendingRequestMap.respond()` adds `isResponseEnvelope()` guard | ✅ |
| `src/subscribe.ts` | `subscribe()` wraps yields in `ResponseEnvelope` | | `src/call.ts` | `PendingRequestMap.call()` resolves with `ResponseEnvelope` | ✅ |
| `src/env.ts` | `buildEnv()` functions return `Promise<ResponseEnvelope>` | | `src/subscribe.ts` | `subscribe()` wraps yields in `ResponseEnvelope` | ✅ |
| `src/response-envelope.ts` | New file: types, factory functions, detection, schemas | | `src/env.ts` | `buildEnv()` functions return `Promise<ResponseEnvelope>` | ✅ |
| `src/from_mcp.ts` | Handler returns `mcpEnvelope()`, extracts `outputSchema`, uses `structuredContent` | | `src/from_mcp.ts` | Handler returns `mcpEnvelope()`, extracts `outputSchema`, uses `structuredContent` | ✅ |
| `src/from_openapi.ts` | Handler returns `httpEnvelope()` | | `src/from_openapi.ts` | Handler returns `httpEnvelope()` | ✅ |
Additionally, any code subscribing to `"call.responded"` events via the pubsub system (not just `PendingRequestMap`, but any direct pubsub consumer) must expect `ResponseEnvelope` instead of `unknown` in the event payload.
## References ## References