← stitchkitDocumentation

ADR 0117 — Contract streams own validation and wire termination

Context

rawResponse, streamingRoute, parseNDJSON and parseSSE intentionally solve separate low-level jobs. Used alone they cannot prove that a producer yields the declared shape, that a client validates it, or that normal EOF is different from a truncated operation. An unconstrained line can also grow before JSON parsing.

Decision

An HTTP endpoint may declare stream: { item, format, maxFrameBytes, lifetimeMs, heartbeatMs, idleTimeoutSeconds, terminal }. Its handler returns an AsyncIterable of schema-derived items and its typed client returns an owned AsyncIterableIterator of the same output type.

The wire is a bounded internal envelope: data, safe error, and explicit end. The server validates before encoding; the client validates both envelope and item. EOF without end, or end without a declared terminal item, fails explicitly. Iterator return, request abort, producer failure and operation lifetime converge on one cancellation signal.

The descriptor is HTTP-only. It does not create a tool, cursor, replay log, topic, durable subscription or business completion model. rawResponse and raw streamingRoute remain available where the application owns the protocol.

The low-level NDJSON/SSE parsers gain a one-mebibyte default line ceiling and fatal UTF-8 decoding. Parse errors fail closed unless the caller deliberately supplies onParseError.

Consequences

  • Finite logs and quiet observations share one contract/client pipeline without claiming durable delivery.
  • The default encoded contract frame limit is 256 KiB and may be tightened per endpoint.
  • Post-header failures use safe codes because an HTTP status is no longer available.
  • Existing tolerant parser consumers must opt back into skipping malformed records with onParseError.