# ADR 0126: Schema-owned stream frames end at the terminal item

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0126-schema-owned-stream-frames-end-at-the-terminal-item.md

---

# ADR 0126 — Schema-owned stream frames end at the terminal item

## Context

The default contract stream envelope provides three independent wire facts:
data, a safe post-header error, and explicit normal end. Some established
NDJSON protocols already encode those facts in their item schema, including a
terminal item. Wrapping those items changes the protocol and forces an
application to retain a second parser beside the contract client.

An unwrapped stream cannot distinguish successful EOF from a producer failure
after headers. Treating either as success would weaken the contract. Likewise,
observing a terminal item without closing the owned request leaves capacity
occupied while the caller is suspended at the yielded value.

## Decision

Contract streams keep `framing: 'envelope'` and `completion: 'stream-end'` as
their defaults. An endpoint may instead declare NDJSON `framing: 'item'` only
with `completion: 'terminal'` and a terminal schema. Every wire frame is then
the validated item itself; there is no Stitchkit `data` / `error` / `end`
envelope.

The server stops its producer immediately after the matching terminal item and
does not consume trailing values. The client aborts the owned request and
cancels the body reader before yielding that terminal item. EOF before the
terminal fails as `STREAM_TERMINAL_MISSING`; a post-header producer or lifetime
failure closes the item-framed response and reaches the same missing-terminal
proof instead of placing a foreign error object on the schema-owned wire.

NDJSON final-line handling is an independent policy. `finalLine: 'allow'`
remains the default for compatibility. `finalLine: 'require-newline'` rejects a
valid JSON document whose delimiter is missing, so protocols that use the
newline as truncation evidence can retain that guarantee. The low-level
`parseNDJSON` surface accepts the same policy.

## Consequences

- Existing envelope-framed streams retain their exact wire and completion
  behaviour.
- Schema-owned framing is safe only where a terminal item proves success; a
  terminal-free raw feed continues to use `streamingRoute` / `parseNDJSON`.
- Releasing I/O before terminal delivery prevents a consumer suspended at
  `yield` from holding request or admission capacity.
- Blank NDJSON heartbeat lines, fatal UTF-8 decoding, frame-byte bounds,
  backpressure, caller abort and iterator cleanup remain shared mechanics.
