v0.39.0 — the externalized-response cap is enforced, not just advertised
TestExternalizedResponseCap is a new required conformance group. Ports must supply a conformance_http_externalized_cap_port fixture; without it the group errors rather than skipping, which is deliberate.
What it found
max_externalized_response_bytes was advertised and, in two of four ports, never enforced. Rust read the configured value only to emit the header and add it to the CORS expose list — a worker capped at 65,536 bytes uploaded 524,744 and answered success. Java was identical. Nothing noticed because nothing tested enforcement.
Worse: three of four ports never externalized stream output over HTTP at all, so there were no external bytes to cap. That hid because inline delivery is observationally identical to a resolved pointer from the client's side — a conformance variant that re-runs the entire suite forcing externalization passed cleanly through a path that externalized nothing. Go, Rust and Java have all since implemented it.
What the group pins
- an overshooting unary response fails
- a payload under the cap still round-trips through the external channel, so the cap is a cap and not a wall
- a producer gets no continuation escape — the direct opposite of
TestHttpResponseCapSoftWire, which pins that a producer does get one for the wire cap. The external cap cannot work that way: the upload has already happened by the time a continuation could be minted.
Supply the fixture with a tight max_externalized_response_bytes and a deliberately generous max_response_bytes. With both tight, the body cap fails first and the group passes while proving nothing.
Reference fix
The reference's own producer pre-flight was gated on not out.finished, so a turn that emitted data and called finish() skipped the cap check while _flush_collector uploaded the batch anyway — and the producer path has no post-flush backstop (_enforce_response_budgets runs on the exchange path only).
Emitting and finishing in one turn is well-formed, not an abuse: completion is signalled by the absence of a continuation sentinel, so a response carrying a data batch and no sentinel says "here it is, and we're done" in one round trip instead of two. That already worked; only validate() and the cap were skipped.
Compatibility
Minor. Ports gain a required fixture, and the reference's producer cap now applies to a finishing turn.