- 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.
126 lines
6.3 KiB
Markdown
126 lines
6.3 KiB
Markdown
---
|
|
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: completed
|
|
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
|
|
|
|
- [x] SSE stream ends after an error event (test); keep-alive present
|
|
- [x] `INVALID_OPERATION_TYPE` maps to the same status on `/call`, `/batch`, `/publish`; http-server.md table updated
|
|
- [x] Once-op invokes enforce the 30 s deadline (test with a hung handler)
|
|
- [x] Retryable errors carry `Retry-After` on all live error paths (test)
|
|
- [x] GW-14 docs fixed (module doc; AGENTS.md gateway-endpoint count if touched)
|
|
- [x] `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).
|
|
|
|
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_stream` reframed with a
|
|
`scan`-based projection — the first `Err` envelope's `event:error`
|
|
frame is emitted, then the stream terminates (the scan flags done and
|
|
subsequent polls return `None`). New test
|
|
`subscribe_stream_is_terminal_after_an_error_event` proves a
|
|
post-error envelope never reaches the wire. Matches the wire
|
|
dispatcher's `call.error`-is-terminal semantics.
|
|
- **GW-03**: no code change needed beyond d7ee302's mapper — verified
|
|
consistent `422`/`401` across `/call`/`/batch`/`/publish` via the
|
|
existing `error_streaming_handler`-style tests plus the mapper's unit
|
|
tests; http-server.md error table now documents
|
|
`INVALID_OPERATION_TYPE` explicitly (422 with token / 401 without).
|
|
- **GW-05**: enforced, not removed. `GatewayDispatch::invoke` wraps the
|
|
registry invoke in `tokio::time::timeout(DEFAULT_TIMEOUT)`; a hung
|
|
handler yields a `TIMEOUT` error envelope (`504`,
|
|
`retryable: true`) instead of holding the request open. Streaming and
|
|
sink invokes stay unbounded (ADR-021 sets `deadline: None` for
|
|
subscriptions; a `/publish` body is upload-bounded). Tests: hung
|
|
handler (120 s sleep) returns well under 60 s with `TIMEOUT`; fast
|
|
handler unaffected. The `deadline:` `OperationContext` metadata stays
|
|
(it feeds registry-side bookkeeping).
|
|
- **GW-07**: all live gateway error paths now route through the shared
|
|
mapper — `envelope_to_response` and `forbidden_response` call the new
|
|
`call_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 retryable `HTTP_503` handler error on
|
|
`/call` carries `Retry-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_handler` returns
|
|
`Sse::new(stream).keep_alive(KeepAlive…15 s)` with a `retry:` 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 a `retry` hint). Interval is the
|
|
`SSE_KEEP_ALIVE_INTERVAL` constant (15 s), documented in http-server.md.
|
|
- **GW-14**: routes.rs module doc rewritten (6 endpoints, `/publish` in
|
|
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. |