Decompose the ADR-049 streaming handler work into 8 dependency-ordered tasks: - call/registry/streaming-handler-handlerkind (foundation: StreamingHandler, HandlerKind, ResponseStream, INVALID_OPERATION_TYPE, migrate all sites) - call/registry/invoke-streaming (OperationRegistry::invoke_streaming) - call/protocol/dispatch-streaming-branch (server-side op_type branch) - call/client/from-call-streaming-forwarding (Subscription → subscribe()) - http/gateway/invoke-streaming (GatewayDispatch::invoke_streaming) - http/server/subscribe-sse-streaming (/subscribe pipes BoxStream to SSE) - http/adapters/from-openapi-sse-streaming (SSE → StreamingHandler) - review-streaming-impl (phase review checkpoint) Validated with taskgraph: 86 tasks, no cycles. Also ignore .worktrees/ so agents' worktree workspaces don't leak into git status.
11 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| review-streaming-impl | Review ADR-049 streaming handler implementation for spec conformance and end-to-end correctness | pending |
|
broad | low | phase | review |
Description
Review the ADR-049 streaming handler implementation across alknet-call and
alknet-http for spec conformance, end-to-end correctness, and pattern
consistency. This is the quality checkpoint after the streaming handler work —
the most significant cross-cutting change since the initial call/http
implementation. All five implementation tasks must be complete before this
review.
Review Checklist
-
Type surface conformance (operation-registry.md §Handler):
StreamingHandlertype alias matches spec (Arc<dyn Fn(Value, OperationContext) -> Pin<Box<dyn Stream<Item = ResponseEnvelope> + Send>> + Send + Sync>)ResponseStreamalias =Pin<Box<dyn Stream<Item = ResponseEnvelope> + Send>>(=futures::stream::BoxStream<'static, ResponseEnvelope>)HandlerKindenum withOnce(Handler)andStream(StreamingHandler)variantsmake_streaming_handler()helper (analogue ofmake_handler())HandlerRegistration.handlerisHandlerKind(notHandler)INVALID_OPERATION_TYPEis the sixth protocol-level code inCallError(retryable: false,details: None)
-
Registry conformance (operation-registry.md §OperationRegistry):
register()validatesHandlerKindmatchesspec.op_type(Once for Query/Mutation, Stream for Subscription) — mismatch is a startup errorinvoke()dispatchesHandlerKind::Once(existing path); returnsINVALID_OPERATION_TYPEforHandlerKind::Streaminvoke_streaming()dispatchesHandlerKind::Stream; returnsINVALID_OPERATION_TYPEforHandlerKind::Onceinvoke_streaming()pre-handler errors (not-found, forbidden, INVALID_OPERATION_TYPE) yield a single errorResponseEnvelopeand end the streaminvoke_streaming()visibility + ACL checks identical toinvoke()(same authority switch: internal → handler_identity, external → identity)OperationEnvis request/response-only — noinvoke_streaming()on the trait;invoke()on aSubscriptionreturnsINVALID_OPERATION_TYPE(viaLocalOperationEnv→registry.invoke()andOverlayOperationEnvdirect match)
-
Builder conformance (operation-registry.md §OperationRegistryBuilder):
with_local/with_leaf/with_leaf_provenancewrapHandlerinHandlerKind::Oncefor Query/Mutationwith_local_streaming/with_leaf_streamingwrapStreamingHandlerinHandlerKind::Streamfor Subscription- Builder validates
handlerkind matchesspec.op_type
-
Call-protocol dispatch conformance (call-protocol.md §CallAdapter Stream Handling):
Dispatcher::handle_streambranches onop_type:Subscription→invoke_streaming()→ pump stream;Query/Mutation→invoke()(existing)- Streaming pump: each
ResponseEnvelope→EventEnvelopeframe - Natural stream end →
call.completedframe Errenvelope →call.errorframe, stream ends after it (NOcall.completedafter an error)deadline: Nonefor subscriptions (unbounded)- Abort:
call.aborteddrops the stream (Drop releases resources; ADR-016)
-
from_call streaming forwarding (client-and-adapters.md §from_call):
build_bundlesbranches onop_type:Subscription→make_streaming_forwarding_handler(HandlerKind::Stream),Query/Mutation→make_forwarding_handler(HandlerKind::Once)- Streaming forwarding handler calls
CallConnection::subscribe_with_payload()(orsubscribe()with forwarded payload) forwarded_forpopulated fromcontext.identity(ADR-032)- Remote stream forwarded end-to-end (no truncation, no first-value fallback)
composition_authority: None,scoped_env: Nonefor FromCall streaming leaves
-
Gateway dispatch conformance (http-server.md §Streaming projection):
GatewayDispatch::invoke_streaming()exists, returnsBoxStream<ResponseEnvelope>- Security invariants identical to
invoke()(sharedbuild_root_context):internal: false,forwarded_for: None, same capabilities, same scoped_env, same ACL deadline: Nonefor the streaming pathto_mcpdoes NOT callinvoke_streaming()(MCP excludes Subscription)
-
/subscribe SSE conformance (http-server.md §Streaming projection):
subscribe_handlercallsGatewayDispatch::invoke_streaming()(notinvoke())- Each
Ok(value)→ SSEdata:frame Err→ SSE error event (event: error), stream ends after (terminal)- Natural stream end → SSE stream closes
- Internal op → single
NOT_FOUNDerror event (no leak) - Client disconnect → stream dropped (Drop; abort cascade)
- Placeholder helpers removed (
subscribe_stream_from_envelope,envelope_to_sse_stream)
-
from_openapi SSE conformance (http-adapters.md §Forwarding handler):
build_registrationbranches onop_type:Subscription→HandlerKind::Stream(streaming forward),Query/Mutation→HandlerKind::Once(existing)forward_stream()streams SSE chunks asResponseEnvelope::ok()items- HTTP error → single
ResponseEnvelope::error(), stream ends - SSE stream end →
ResponseStreamends parse_sse_framesreused (multi-event, partial, comments, BOM)stream_subscription()collect-all placeholder removedfrom_mcpunchanged (alwaysHandlerKind::Once)
-
ADR conformance:
- ADR-049: all 9 decisions implemented (StreamingHandler, HandlerKind, invoke_streaming, invoke errors on Subscription, OperationEnv request/response-only, server-side dispatch branch, GatewayDispatch::invoke_streaming, from_call stream forwarding, from_openapi SSE forwarding)
- ADR-023 amended: six protocol codes (INVALID_OPERATION_TYPE added)
- ADR-016: abort cascade on streaming (stream drop)
- ADR-032: forwarded_for on streaming forwarding handlers
-
End-to-end correctness:
- A
Subscriptionop registered with aStreamingHandlerstreamscall.respondedevents through: server dispatch → wire → HTTP/subscribeSSE (orfrom_callforwarding → remote stream, orfrom_openapiSSE forwarding) invoke()on aSubscriptionreturnsINVALID_OPERATION_TYPE(not silent truncation)invoke_streaming()on aQuery/MutationreturnsINVALID_OPERATION_TYPEOperationEnv::invoke()on aSubscriptionreturnsINVALID_OPERATION_TYPE(composition is request/response-only)
- A
-
Pattern consistency:
invoke()andinvoke_streaming()share visibility + ACL logic (security axis provably identical)GatewayDispatch::invoke()andinvoke_streaming()sharebuild_root_context(security axis provably identical)HandlerKindmakes the "one or the other, matching op_type" invariant type-level (not twoOptions validated at runtime)- Existing
Query/Mutationhandlers unchanged (wrapped inHandlerKind::Once, dispatch path identical)
-
Test coverage:
- Unit tests for
HandlerKindvalidation atregister()(both mismatch directions) - Unit tests for
invoke()/invoke_streaming()cross-kind errors (INVALID_OPERATION_TYPEboth directions) - Unit tests for
invoke_streaming()pre-handler errors (not-found, forbidden, internal-from-external) - Unit tests for
invoke_streaming()ACL authority switch (internal → handler_identity) - Unit test for
make_streaming_handler - Unit/integration tests for server-side streaming dispatch (multiple
call.responded+call.completed;Err→call.error, nocall.completedafter) - Unit/integration tests for
from_callstreaming forwarding - Unit/integration tests for
from_openapiSSE streaming forwarding - Unit tests for
/subscribeSSE (multipledata:frames;event:error;INVALID_OPERATION_TYPEforQueryop via/subscribe) - Unit tests for
GatewayDispatch::invoke_streaming()(all error paths)
- Unit tests for
Acceptance Criteria
- All type surface matches operation-registry.md §Handler
- All registry methods match operation-registry.md §OperationRegistry
- Builder wraps HandlerKind correctly per op_type
- Call-protocol dispatch branches on op_type correctly
- from_call streaming forwarding works end-to-end
- GatewayDispatch::invoke_streaming security invariants identical to invoke
- /subscribe SSE pipes BoxStream correctly
- from_openapi SSE streaming works (no truncation)
- from_mcp unchanged (always HandlerKind::Once)
- ADR-049 all 9 decisions implemented
- ADR-023 amended (six protocol codes)
- invoke() / invoke_streaming() / OperationEnv::invoke() cross-kind errors all return INVALID_OPERATION_TYPE
- Existing Query/Mutation handlers unchanged
- Test coverage adequate for all streaming functionality
cargo fmt --check -p alknet-call -p alknet-httppassescargo clippy -p alknet-call -p alknet-http --all-targetspasses with no warnings- All tests pass (
cargo test -p alknet-call -p alknet-http)
References
- docs/architecture/decisions/049-streaming-handler-for-subscriptions.md — ADR-049
- docs/architecture/crates/call/operation-registry.md — Handler, OperationRegistry, HandlerRegistration
- docs/architecture/crates/call/call-protocol.md — CallAdapter Stream Handling, call.error Payload
- docs/architecture/crates/call/client-and-adapters.md — from_call streaming forwarding
- docs/architecture/crates/http/http-server.md — Streaming projection (SSE)
- docs/architecture/crates/http/http-adapters.md — Forwarding handler (from_openapi)
- docs/architecture/crates/http/http-mcp.md — from_mcp always HandlerKind::Once
- docs/architecture/decisions/023-operation-error-schemas.md — ADR-023 (amended: six codes)
- docs/architecture/decisions/016-abort-cascade-for-nested-calls.md — ADR-016 (stream drop)
- docs/architecture/decisions/032-forwarded-for-identity.md — ADR-032 (forwarded_for)
Notes
This is the quality checkpoint for the ADR-049 streaming handler work — the most significant cross-cutting change since the initial call/http implementation. The review should verify the end-to-end streaming path works: a
Subscriptionop registered with aStreamingHandlerstreamscall.respondedevents through every projection (server dispatch → wire, HTTP/subscribeSSE,from_callforwarding,from_openapiSSE forwarding). The load-bearing invariants: (1)invoke()/invoke_streaming()/OperationEnv::invoke()cross-kind errors all returnINVALID_OPERATION_TYPE(no silent truncation), (2) the security axis is provably identical betweeninvoke()andinvoke_streaming()(sharedbuild_root_context+ shared visibility/ACL logic), (3)HandlerKindmakes the kind/op_type invariant type-level, (4) existingQuery/Mutationhandlers are unchanged. If deviations are found, document and fix before considering the streaming handler work complete.
Summary
To be filled on completion