feat(compilers/openapi): populate the operation streaming fields - #352
Merged
Conversation
lowering.New gained a parameter on each side — the budgets on main (#349), the streaming media policy here — so the merged signature takes both, and the field comment that called Grouping "one of the two" caller policies no longer counts them: it names the other two instead, since a count in prose is what just went stale. The conformance table gained its capability column on main (#342), so both streaming rows come back with the matrix rows they witness. streaming-media-31 witnesses more than it may claim. Its NDJSON request body lowers to a populated RequestStream and the golden reads streaming=bidi, but ir-spec-matrix.md marks streaming-client absent for OpenAPI, and the matrix contract refuses a witness for a row a format is said not to reach. The row claims streaming-server alone, which is true and checkable; whether the matrix should now say OpenAPI reaches client streaming is a question about that document rather than about this merge.
Reading a request body's media type as a frame format populates Operation.RequestStream, so an NDJSON body now lowers to streaming: bidi. The matrix still said OpenAPI reaches client streaming in no way at all, which the conformance contract enforces from the other side: it refused the fixture that witnesses it, because a format marked absent for a row may have no witness. The cell reads ⚠ rather than ✅, which is the legend's "expressible indirectly": nothing in the document declares the stream. It is read from a media type, stamped Provenance.Inferred, and a caller who turns the policy off loses the capability entirely — none of which ✅ would convey. The two now hold each other up. Reverting the cell and keeping the claim fails with the row marked absent yet witnessed; keeping the cell and dropping the claim fails with the row unwitnessed. Neither can drift without the other saying so.
classifyStream refused to name an element type whenever a direction had more than one streaming content, without ever comparing them. A response offering one frame as both text/event-stream and application/x-ndjson — content negotiation, and the commonest streaming shape there is — therefore reached the IR with ResponseStream.Events unset, although both contents lowered to the same TypeID. Two success responses sharing one media type and one schema did the same. The refusal is right where the contents disagree: StreamDetail holds one Events per direction, so naming one of two differing elements would be the primary-content selection invariant 2 forbids. It does not reach a set that names one element between them — there is nothing to elect, and leaving it unnamed says less than the source did rather than declining to choose. The candidates are compared now; ir.TypeRef is a TypeID and a nullability bit, so == is the whole of what agreement means. The diagnostic counted media types, which was wrong twice over: it fired for contents that agreed, and called one media type on two responses several. It names the disagreement instead. streaming-media-31 gains the negotiated shape beside the one it already had, so the corpus holds both sides of the rule: reverting to the count reddens the new operation and leaves the refusal green.
OmarAlJarrah
added a commit
that referenced
this pull request
Aug 11, 2026
lowering.New now takes three caller policies: the budgets and the streaming media list arrived on main (#349, #352), the promotion mapping here. The field comment naming Grouping "one of the two" no longer counts them, and both policy types had called themselves "the second injectable-policy seam" — with Grouping there are three, so neither is second. Two lowerings this branch hooked into were refactored under it. An operation's extensions are gathered by applyOperationExtensions now, and a security scheme's by applySchemeExtensions, so the promotion runs after each rather than beside the inline reads it used to follow — it reads the assembled map, so it has to. The conformance table gained its capability column on main (#342); extension-promotion names the deprecation row, whose members are the fields it fills. It does not name vendor-extensions: extensions-x witnesses that already, and the x-* here are the means rather than the subject.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Operation.Streaming,Operation.RequestStreamandOperation.ResponseStreamwere written by nocompiler at any version. An SSE or NDJSON API declares what it does by naming a media type, and the
media type was copied to
Content.MediaTypeand never read; a 3.2itemSchemasetContent.Itemand stopped there. Compiling either shape produced
streaming: nullwith no diagnostic, so nothingdownstream could tell a stream from an ordinary body.
This adds the reading, in two halves that are deliberately not the same kind of fact:
itemSchemadeclares the stream. The keyword states that the body is a sequence ofitems, so the operation streams and the declared item schema is the element type. Nothing is
guessed, so no heuristic marker is stamped.
text/event-stream,application/x-ndjsonandapplication/jsonlare read as frame formats through a new
Options.StreamingMediapolicy — default on, replaceable,and switchable off — with
Provenance.Inferrednaming the heuristic. That is architectureprinciple 6: the list is a default, not a standard, and a caller who wants only what a document
declares turns it off. Matching ignores case and media-type parameters.
For a frame format the schema under the media type describes one frame rather than the whole body,
which is the opposite of how an ordinary content is read;
StreamDetail.Eventsis what says so.Where a direction offers several streaming contents the direction still streams but the element type
is left unnamed, with an info diagnostic:
StreamDetailholds oneEventsper direction while thepayload keeps every media type, so electing one of them would be the primary-content selection a
compiler must not make.
Two notes on the issue as filed. Its acceptance item about an
itemSchema"not double-applying"assumed an existing 3.2 writer — there was none, so the gap was wider than reported rather than
narrower, and the two paths here cannot both fire because a declaration at a content wins over the
reading of its media type. And
Provenance.Inferredholds a single string that path-prefix groupingalready used, so an operation reached by both heuristics now lists both rather than losing one.
Merge ordering: #345 also rewrites
testdata/conformance/openapi/unwitnessed.golden.txt, and #341adds
DecodeOptionsto the compiler contract. Whichever of the three lands first, the others rebase;once #341 is in,
StreamingMediashould gain textual option names there so the CLI's-optpath canreach it.
Test plan
streaming-media-30andstreaming-media-31cover a 3.0 media-type-only stream, abidirectional operation, a
charsetparameter on the media type, and the several-contents refusal.unwitnessed.golden.txtloses five entries —Operation.Streaming,Operation.RequestStream,Operation.ResponseStream,StreamDetail.EventsandProvenance.Inferred, the last of which nocorpus spec witnessed at all before.
sequential-medianow records the stream itsitemSchemadeclares; its assertion pins that it isdeclared rather than inferred.
fields to agree, which is what an implementation electing the first candidate would fail.
them, and the exported default list being the one the lowering applies.
Closes #250