--- id: review-002-fu-stream-error-coverage name: Streaming error-arm coverage — forward_stream invalid input, PEM read-failure, over-cap poll_write mirror (post-bulk coverage gap) status: done depends_on: [] scope: narrow risk: low impact: component level: implementation tags: [infra, review-002, follow-up, coverage] --- ## Description Bracketed follow-up discovery from the post-bulk coverage re-pass (95.89% regions overall; the review-002 bulk's own new code verified fully covered — PRJ-16 guard, body cap, router reorder, batch cap, WS-13/18/19, the OAI-11 memo node-budget all exercised). The residue is small but one item matters more than its line count: - **`forward_stream` build-request error arm** (forward.rs:1085-1090): a Sub op invoked with an invalid input (undeclared key, non-scalar placeholder value) must produce exactly one error envelope from the stream — the streaming analog of the Once-path error handling. No test drives it: every SSE test (from_jsonschema `integration_sse_subscription…`, forward.rs's stream family) sends valid input. A regression returning an empty stream (silently swallowing the error) would be invisible — and an empty-200-subscribe is the worst failure shape for a subscriptions consumer. - **PEM read-failure arms** (http_client.rs CaBundleRead/ClientCertRead, sync + async): the *parse*-failure arms are covered (`corrupt_ca_bundle_fails_ca_bundle_parse_with_path`, `garbage_client_cert_fails_…` in client_tls.rs, landed in 6490d15), but the **unreadable-file** arms (nonexistent path, unreadable permissions) have no test on either build path. The error taxonomy half-documented at `HttpClientBuildError`'s doc block (:214) is half-specified. - **Over-cap `poll_write` rejection** (byte_adapter.rs:789-798): WS-14's moved check rejects writes *above* `PENDING_BUFFER_CAP` with `InvalidData` before entry to the queue — the "at cap" boundary is tested (`tungstenite_write_at_the_cap_is_accepted`, 1158) and the framing-level cap-trip is tested both flavors (`axum_framing_cap_trip…`), but the single-call over-cap rejection itself (the WS-14 acceptance test's own criterion) never runs: no test writes `PENDING_BUFFER_CAP + 1`. - **`forward_stream` SSE-parser residual edges**: split-CRLF-across- chunks (the `\r` + `"\n…"` case), invalid-UTF8-line drop — the parser edges the bulk's stream arms work next to. - **`from_value` structural rejections** (openapi_spec.rs:391-398, 416-418): a non-object document, a missing-`info`, a non-object `paths` — none ever exercised (all other structural rejects are). - **Connection-failure arms**: `protocol_handler` accept-path `stream_error_to_handler` (adapter.rs:597-599) and the read-pump demux-gone `break` (byte_adapter.rs:374-381) — both are connection-failure semantics with zero direct coverage. - **Dead-ish**: `impl Default for WsTimeouts` (upgrade.rs:344-351) has zero callers (the extension is constructed explicitly in the one test that uses it) — delete or `#[allow(dead_code)]` with the reason. ## Acceptance Criteria - [ ] Wire test: Sub op + undeclared key (or non-scalar placeholder value) → the subscriber receives exactly one `INVALID_INPUT` envelope and the stream ends (upstream receives zero requests) - [ ] Two PEM read-failure tests: `SharedHttpClient::new` with a nonexistent CA path → `CaBundleRead` carrying the path; `reload` with a nonexistent client-cert path → `ClientCertRead` - [ ] One over-cap `poll_write` test: `write_all(PENDING_BUFFER_CAP+1)` → `InvalidData` error naming the cap (asserts the WS-14 moved check's rejection leg; the = cap test exists) - [ ] SSE parser edge tests: CRLF split across chunks (`…\r` ends chunk 1, `\n…` opens the next), invalid-UTF8 line dropped - [ ] from_value structural tests: non-object doc, missing-`info`, non-object `paths` → `SchemaParse` naming the missing member - [ ] `WsTimeouts` redundant `Default` deleted (or justified+allowed) - [ ] `cargo llvm-cov --all-features`: named arms no longer dark; `cargo test --all-features`, clippy, fmt pass ## References - docs/reviews/002-post-remediation-review.md (Part G' test-gaps; post-bulk re-analysis finding list) - src/adapters/forward.rs:1085-1090 (stream build-error arm), src/client/http_client.rs:484-545 (read-failure arms), src/websocket/byte_adapter.rs:789-798 (over-cap poll_write) - tests/client_tls.rs (the PEM harness: parse-failure tests at :226-448 are the pattern to mirror for read-failures) - tasks/infra/review-002-cov-deployment-knobs.md (the pre-bulk coverage task these extend) - tasks/infra/review-002-cov13-dead-code.md (the dead-code convention for the WsTimeouts Default) ## Notes Tests-only + one deletion. The streaming-build-error test's load-bearing assertion is the *single envelope then end* semantics — mirror `oversized_upstream_sse_line_terminates_with_one_internal_envelope`'s assert structure but trigger pre-send instead (invalid input, no responder needed for the reject case — wire it against a live-responder anyway if the harness makes it cheap; the zero-upstream-contact assert is the point). The `spawn_responder`-family harnesses in forward.rs/from_jsonschema.rs cover the seam.