--- id: review-001-gateway-stream-errors name: Gateway error-fidelity — SSE terminality, mappings, Retry-After, deadline (GW-03..GW-07, GW-12..GW-14) status: pending depends_on: [] scope: narrow risk: low impact: component level: implementation tags: [gateway, review-001] --- ## Description Review 001 gateway error-fidelity findings (`src/gateway/routes.rs`, `error.rs`, `dispatch.rs`) — the response a caller sees must match the documented contract and match across transports: - **GW-04**: SSE error events are not terminal — the stream continues after `Err` (`routes.rs:299-313`) while the wire dispatcher treats `Err` as terminal; two transports disagree about stream semantics (http-server.md:219-223 documents terminal). Emit the error frame and end the stream (`take_while`). - **GW-03**: `INVALID_OPERATION_TYPE` → 500 on `/call`//`/batch` (client fault reported as server fault; pollutes alerting) but 400 on `/publish`. Map consistently (400 or 422) and document in http-server.md. - **GW-05**: the 30 s `DEFAULT_TIMEOUT` deadline is recorded (`dispatch.rs:34,165`) but never enforced — a hung handler holds the request open indefinitely. Either enforce (`tokio::time::timeout` around Once-op invokes) or remove the dead metadata. Prefer enforcing; it is the documented contract. - **GW-07**: `Retry-After` machinery exists in `call_error_to_http_response` (`error.rs:61-75`) but the main gateway error path builds responses by hand (`routes.rs:319-333`) — retryable `HTTP_429`/`HTTP_503` reach callers with no `Retry-After` despite the documented mapping. Route gateway error responses through the shared mapper. - **GW-12**: ACL denial on `/subscribe` surfaces as HTTP 200 + `event:error` while `/call` returns 401/403. ADR-049 makes 200-on-stream defensible, but the doc must call out the asymmetry — coordinate with review-001-output-projection (PRJ-05) which documents it. - **GW-13**: no SSE keep-alive/heartbeat (`routes.rs:172`) — quiet-but- alive streams (the normal state for subscriptions) die at LB/proxy idle timeouts. Add keep-alive + `retry:` field. - **GW-14**: stale module doc (`routes.rs:1-9` claims `/publish` is "a separate module"; AGENTS.md §7's "5 gateway endpoints" framing is similarly stale — ADR-068 made it 6). ## Acceptance Criteria - [ ] SSE stream ends after an error event (test); keep-alive present - [ ] `INVALID_OPERATION_TYPE` maps to the same status on `/call`, `/batch`, `/publish`; http-server.md table updated - [ ] Once-op invokes enforce the 30 s deadline (test with a hung handler) - [ ] Retryable errors carry `Retry-After` on all live error paths (test) - [ ] GW-14 docs fixed (module doc; AGENTS.md gateway-endpoint count if touched) - [ ] `cargo test` and `cargo clippy --all-targets -- -D warnings` pass ## References - docs/reviews/001-initial-implementation-review.md (Part C, GW-03..GW-07, GW-12..GW-14) - docs/architecture/decisions/049-streaming-handler-for-subscriptions.md - docs/architecture/decisions/023-operation-error-schemas.md ## Notes > Agent fills during implementation. Independent of > review-001-gateway-publish-semantics (both touch routes.rs — > sequence or coordinate to avoid churn). ## Summary > Filled on completion.