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:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-10
|
||||
last_updated: 2026-05-11
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
**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`
|
||||
|
||||
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)`
|
||||
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()`
|
||||
|
||||
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()`
|
||||
|
||||
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()`
|
||||
|
||||
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. | ✅ |
|
||||
| `call-protocol.md` | `PendingRequestMap.respond()` | Now enforces `isResponseEnvelope()` check — throws on raw values. | ✅ |
|
||||
| `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` | `outputSchema` extracted when available, via `FromSchema`. Falls back to `Type.Unknown()`. | ✅ (previous) |
|
||||
| `adapters.md` | `from_openapi` | Handler returns `httpEnvelope()`. Error on HTTP error status still throws `CallError`. | ✅ (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()`. | ✅ |
|
||||
| `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 |
|
||||
|------|--------|
|
||||
| `src/registry.ts` | `execute()` returns `Promise<ResponseEnvelope<TOutput>>` |
|
||||
| `src/call.ts` | `CallHandler` captures return value, wraps in envelope, publishes `call.responded` |
|
||||
| `src/call.ts` | `CallEventSchema` `output` field changes to `ResponseEnvelopeSchema` |
|
||||
| `src/call.ts` | `PendingRequestMap.respond()` adds `isResponseEnvelope()` guard |
|
||||
| `src/call.ts` | `PendingRequestMap.call()` resolves with `ResponseEnvelope` |
|
||||
| `src/subscribe.ts` | `subscribe()` wraps yields in `ResponseEnvelope` |
|
||||
| `src/env.ts` | `buildEnv()` functions return `Promise<ResponseEnvelope>` |
|
||||
| `src/response-envelope.ts` | New file: types, factory functions, detection, schemas |
|
||||
| `src/from_mcp.ts` | Handler returns `mcpEnvelope()`, extracts `outputSchema`, uses `structuredContent` |
|
||||
| `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.
|
||||
| Code | Change | Status |
|
||||
|------|--------|--------|
|
||||
| `src/response-envelope.ts` | New file: types, factory functions, detection, schemas | ✅ |
|
||||
| `src/registry.ts` | `execute()` returns `Promise<ResponseEnvelope<TOutput>>` | ✅ |
|
||||
| `src/call.ts` | `CallHandler` captures return value, wraps in envelope, publishes `call.responded` | ✅ |
|
||||
| `src/call.ts` | `CallEventSchema` `output` field changes to `ResponseEnvelopeSchema` | ✅ |
|
||||
| `src/call.ts` | `PendingRequestMap.respond()` adds `isResponseEnvelope()` guard | ✅ |
|
||||
| `src/call.ts` | `PendingRequestMap.call()` resolves with `ResponseEnvelope` | ✅ |
|
||||
| `src/subscribe.ts` | `subscribe()` wraps yields in `ResponseEnvelope` | ✅ |
|
||||
| `src/env.ts` | `buildEnv()` functions return `Promise<ResponseEnvelope>` | ✅ |
|
||||
| `src/from_mcp.ts` | Handler returns `mcpEnvelope()`, extracts `outputSchema`, uses `structuredContent` | ✅ |
|
||||
| `src/from_openapi.ts` | Handler returns `httpEnvelope()` | ✅ |
|
||||
|
||||
## References
|
||||
|
||||
|
||||
Reference in New Issue
Block a user