Skip to content

dispatcher's second streaming site answers a write-less transport res.json(result.result) — the SSE descriptor serializes with its AsyncIterable dropped, silent total event loss #9961

Description

@huangyiirene

Found while running the #9936 reader/reachability sweep (recording only, not starting). Measured on origin/main at 5989b0de90.

Blocked-by: #9936 — the correct shape for this body follows the same fork ruling that card carries; #9936 remains open and is not addressed here.

The site

packages/runtime/src/dispatcher-plugin.ts, in the dispatch-result writer (locate by text const isStream = r && typeof r === 'object', around line 351 on the measured ref; numbers drift). This is a DIFFERENT site from the mountRouteOnServer fallback #9936 names — that one is the enumerated unenveloped: 1 in check-route-envelope.mjs; this body is a relayed res.json(result.result) and is invisible to the gate's counters by design.

When a dispatch result carries the SSE/streaming descriptor and the transport's res HAS write/end, the events stream correctly. When res lacks them, the guard fails and control falls through to:

res.status(200);applySecurityHeaders();res.json(result.result);

result.result is the stream descriptor itself: { type: 'stream', contentType, events, vercelDataStream, headers } where events is an AsyncIterable. JSON.stringify serializes an async generator object to {} — the caller receives the descriptor's metadata with the payload gone, HTTP 200, and the iterable is never drained. Unlike #9936's site (which at least collects and returns the events), this one loses every event silently.

Reachability

Same population as #9936's site: both in-repo http.server providers (plugin-hono-server/src/adapter.ts, qa/http-conformance/src/adapter.ts) construct write/end unconditionally, so no shipped composition reaches this branch. It is reachable only through an externally supplied Runtime({ server }) transport that omits the optional write/end — a composition packages/spec/src/contracts/http-server.ts explicitly permits, and for which its #3607 / ADR-0076 OQ#10 contract text prescribes falling back to buffered send(). No in-repo test covers either streaming branch of dispatcher-plugin.

Why filed separately

#9936's scope is explicitly its one enumerated { events } literal body. This site needs the same ruling applied (buffered send() per the contract text, an envelope, or retirement), so it should be routed after that fork is decided rather than fixed to a shape the ruling might contradict.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions