- GW-04: /subscribe error events are terminal — scan-based emission of the error frame, then end of stream (matches call.error semantics). - GW-05: enforce the 30 s gateway deadline via tokio::time::timeout in GatewayDispatch::invoke; hung handlers surface as TIMEOUT (504), streaming/sink stay unbounded per ADR-021. - GW-07: gateway error paths route through the new identity-aware call_error_to_http_response_with_identity; retryable HTTP_429/HTTP_503 now carry Retry-After on /call, /batch, /search, /schema, /publish. - GW-13: SSE keep-alive (15 s comment frames) + retry: 15000 field. - GW-14: module doc fixed (6 endpoints; /publish lives in routes.rs). - GW-03/GW-12: mapping rides d7ee302's INVALID_OPERATION_TYPE mapper (documented in http-server.md table); 200-on-stream asymmetry documented. Verification: cargo test (243 passed); cargo clippy --all-targets -- -D warnings clean; cargo fmt --check clean.
6.3 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||
|---|---|---|---|---|---|---|---|---|---|---|
| review-001-gateway-stream-errors | Gateway error-fidelity — SSE terminality, mappings, Retry-After, deadline (GW-03..GW-07, GW-12..GW-14) | completed | narrow | low | component | implementation |
|
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 treatsErras 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_TIMEOUTdeadline is recorded (dispatch.rs:34,165) but never enforced — a hung handler holds the request open indefinitely. Either enforce (tokio::time::timeoutaround Once-op invokes) or remove the dead metadata. Prefer enforcing; it is the documented contract. - GW-07:
Retry-Aftermachinery exists incall_error_to_http_response(error.rs:61-75) but the main gateway error path builds responses by hand (routes.rs:319-333) — retryableHTTP_429/HTTP_503reach callers with noRetry-Afterdespite the documented mapping. Route gateway error responses through the shared mapper. - GW-12: ACL denial on
/subscribesurfaces as HTTP 200 +event:errorwhile/callreturns 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-9claims/publishis "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_TYPEmaps 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-Afteron all live error paths (test) - GW-14 docs fixed (module doc; AGENTS.md gateway-endpoint count if touched)
cargo testandcargo clippy --all-targets -- -D warningspass
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).
Built on d7ee302 (GW-01/GW-06/HY-13 publish semantics + the error.rs
INVALID_OPERATION_TYPE mapper) — not re-implemented or reverted.
GW-03 needed no new mapping: the publish-side mapper from d7ee302
(422 with identity / 401 without) already covers /call and
/batch because both route their error envelopes through the shared
mapper; documented in http-server.md's error table.
Summary
- GW-04:
subscribe_stream_from_envelope_streamreframed with ascan-based projection — the firstErrenvelope'sevent:errorframe is emitted, then the stream terminates (the scan flags done and subsequent polls returnNone). New testsubscribe_stream_is_terminal_after_an_error_eventproves a post-error envelope never reaches the wire. Matches the wire dispatcher'scall.error-is-terminal semantics. - GW-03: no code change needed beyond d7ee302's mapper — verified
consistent
422/401across/call//batch//publishvia the existingerror_streaming_handler-style tests plus the mapper's unit tests; http-server.md error table now documentsINVALID_OPERATION_TYPEexplicitly (422 with token / 401 without). - GW-05: enforced, not removed.
GatewayDispatch::invokewraps the registry invoke intokio::time::timeout(DEFAULT_TIMEOUT); a hung handler yields aTIMEOUTerror envelope (504,retryable: true) instead of holding the request open. Streaming and sink invokes stay unbounded (ADR-021 setsdeadline: Nonefor subscriptions; a/publishbody is upload-bounded). Tests: hung handler (120 s sleep) returns well under 60 s withTIMEOUT; fast handler unaffected. Thedeadline:OperationContextmetadata stays (it feeds registry-side bookkeeping). - GW-07: all live gateway error paths now route through the shared
mapper —
envelope_to_responseandforbidden_responsecall the newcall_error_to_http_response_with_identity(added in error.rs, identity-aware variant of the existing builder that keeps the 401/403 and 401/422 splits). Test: a retryableHTTP_503handler error on/callcarriesRetry-After: 30. - GW-12: behavior unchanged (200-on-stream per ADR-049); the asymmetry is now documented in http-server.md's streaming section ("Status-code asymmetry on /subscribe").
- GW-13:
subscribe_handlerreturnsSse::new(stream).keep_alive(KeepAlive…15 s)with aretry:field (15000 ms) on every stream event and on the keep-alive comment frame itself (axum's event-based keep-alive re-sends the full event, so quiet streams still deliver aretryhint). Interval is theSSE_KEEP_ALIVE_INTERVALconstant (15 s), documented in http-server.md. - GW-14: routes.rs module doc rewritten (6 endpoints,
/publishin this module, stale "separate module" claim removed). AGENTS.md §7's "5 gateway endpoints" framing was NOT edited — AGENTS.md is a parallel read for other agents; the operative count is documented in http-server.md and ADR-068 (AGENTS.md alignment can ride a docs-only commit).
Verification: cargo test (243 passed, incl. 9 new); cargo clippy --all-targets -- -D warnings clean; cargo fmt --check clean.