- the harness lands in src/ops.rs's test tree (#\[cfg(test)] under feature ops): serving assembly (OperationRegistryBuilder + CallAdapter run loop) and client side (CallClient + spawn_dispatch; ChannelClient) - the loopback connection source: Connection::from_source with a pre-wired multi-stream BidiStreamSource — the QUIC-shaped in-memory stand-in stream-per-request needs (a single from_bidi half cannot serve spawn_dispatch's call path) - the channel shape: ChannelsAdapter install hook forks the assembled registry, registers the stub openable through register_openable_with_establisher (the awaited Establishment phase), dispatches via run_loop_single_stream - the identity/ACL wiring: TestIdentityProvider (token → identity) + TestOwnership (namespace → identities → granted actions, closed-by-default per OQ-09); call shape rides auth_token resolution, channel shape rides the transport-identity seam - proven with the stub ops: JSON round-trip call → envelope; the ACL stub denies anonymous/ungranted and admits granted; the channel stub establishes and bytes flow both directions; the refusal arms prove ACL-before-establishment and the channel:open_failed/unknown_resource reply shape - dev-dep: async-trait 0.1 (the BidiStreamSource impl); lib tree unchanged verified: cargo test --features ops (88/19/3 green), cargo test (default, harness compiled out — 73/19/3 green), cargo clippy --all-targets -- -D warnings on both shapes, cargo fmt --check, cargo doc --no-deps clean
9.2 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ops-call-harness | the in-process alkcall harness — the serving assembly and the client side over one duplex pair | completed |
|
narrow | low | component | implementation |
|
Description
The test harness every ops handler task exercises its handlers
through: an in-process alkcall serving side + client side over one
tokio::io::duplex pair — the embedder's "dials and hands an
established Connection in" shape (ops-surface.md §Why alkcall;
alkcall is a pure protocol crate), realized in-process so the ops
surface is testable server-less.
- The serving assembly (the embedder-assembly pattern realized in
test):
OperationRegistryBuilderreceiving the ops family's spec/handler pairs → the dispatch serving stack (CallAdapter/GatewayDispatch, plusChannelsAdapterfor the binary ops'channel_openpath) → the dispatch run loop over aConnection::from_bidihalf (alkcall ADR-009's public stream constructor). The assembly's shape is the module's documented embedder entry — the "module exports spec/handler pairs; the embedder assembles" placement (ops-surface.md's placement note, the alkgitgit/repo/*pattern). - The client side:
CallClient+spawn_dispatchover the duplex pair's other half;ChannelClient(from_connection,open_channel/call_open_op) for the channel ops. - The identity/ACL wiring: a test
IdentityProvider+OwnershipProvider— the embedder-registry stand-in (namespace → identities → granted actions) — so ACL behavior is exercisable from the first handler task onward, including the closed-by-default denial path (OQ-09). - Proven with a stub op: a trivial stub op registered through the same path round-trips (call → response envelope), and the channel path opens with bytes flowing — the pipe proven before any real op rides it. The stub lives in the test tree; it registers nothing into the shipped module surface.
Acceptance Criteria
- The harness lives in the ops module's test tree
(
#[cfg(test)]under featureops), parameterized over the assembled registry (ops register; the harness serves) - Server + client halves over one duplex pair; the dispatch run loop serves; the client calls and receives envelopes
- The identity/ownership wiring exercisable: a stub op carrying
an
AccessControldenies an ungranted identity and admits a granted one (the ACL path proven end-to-end pre-handler) - The channel path proven: a stub
channel_openop establishes (theEstablishmentphase) and bytes flow both directions cargo test --features opsgreen; the default build unaffected (the harness is feature-gated test code);cargo clippy --all-targets -- -D warningsandcargo fmt --checkgreen on both shapes
References
- docs/architecture/ops-surface.md (§Why alkcall — the Connection hand-in; the placement note)
- docs/architecture/decisions/001-substrate-posture-and-ops-placement.md §2 (the machinery inventory this harness assembles)
- alkcall (upstream checkout
/workspace/@alkdev/alkcall):src/registry/registration.rs(OperationRegistryBuilder, the handler kinds),src/protocol/adapter.rs(CallAdapter),src/protocol/connection.rs(Connection::from_bidi),src/channels/adapter.rs(ChannelsAdapter),src/channels/client.rs(ChannelClient), ADR-047/049 (the channel_open marker + establishment) - tasks/ops-foundation.md (the context the harness's assembled handlers capture)
Notes
Landed as a
#[cfg(test)] mod harnessappended tosrc/ops.rs(the ops module's test tree — feature-gated by construction, the default build compiles it out; the shipped module surface is untouched). All wiring is alkcall's public API:
- The loopback connection source (
LoopbackSource+Connection::from_source, the ADR-008 extension point): the QUIC-shaped in-memory stand-in the top-levelalk/callshape needs —open_bihands out pre-wired stream pairs (onetokio::io::duplexpair per stream, joined via the publicBiStream::from_joined),open_biat exhaustion answersStreamClosed,accept_biat idle parks (an open connection with no inbound streams),closedrains. This was the one place the task text needed a ruling:spawn_dispatch+ stream-per-request cannot complete a call over a singlefrom_bididuplex end (the accept loop consumes the one stream andcall()'sopen_bithen fails; alkcall's own e2e shape is the same pre-wiredBidiStreamSourcestand-in,pub(crate)there — reimplemented here as the documented downstream seam, N streams wide so the client can call repeatedly). The channel shape usesConnection::from_bidihalves directly (the channels protocol multiplexes internally).- The serving assembly:
OperationRegistryBuilderreceiving the stub ops family's spec/handler pairs (with_local) →CallAdapter::new(...).with_ownership_provider(...)→ProtocolHandler::handle(the dispatch run loop) over itsfrom_sourcehalf; client sideCallClient::new(...).spawn_dispatch(...)over the other.GatewayDispatch(alkcall'sgateway-gated transport-neutral spine) was not wired — no gateway front-end rides the duplex; the wire-serving stack is theCallAdapterhalf.- The channel assembly:
ChannelsAdapter::new(install_hook,\ NoCap)— the install hook (the ADR-036 amendment shape): sets the transport-authenticated identity on channel 0'sConnection(Connection::set_identity, the CF-005 (b) path), forks the assembled registry (ADR-047 §4's per-connection registration), and registers the stub openables throughChannelCore::register_openable_with_establisher— the awaited Establishment phase (establisher records its invocation, admitsstub-nsonly, returnsEstablishment::default(); refusal →channel:open_failed/unknown_resource) — thenDispatcher::run_loop_single_streamoverCallConnection::new_single_stream. Client side:ChannelClient::from_connection,open_channelfor the binary data plane,call_open_opfor the refusal arms.- The identity/ACL wiring (
TestIdentityProvider— token → identity — andTestOwnership— namespace → identities → granted actions, closed by default): the call shape exercises theauth_token→IdentityProviderroute (payloadauth_tokenviacall_with_payload, alkcall ADR-017 §7's precedence); the channel shape exercises the transport-identity route (identity set on channel 0's connection in the install hook). Both resolve through the dispatch'sAccessControl::checkwith the ownership provider in context — the ops surface's namespace-as-resource mapping (blob-namespace, actionread,resource_id_path: "$.namespace"), never a re-implemented check (convention 6).- The stubs:
blobs/stub-echo(no ACL, echo),blobs/stub-ns-probe(the ACL op above),channels/stub/sub(the channel_open op, over thealk/stubdata-plane ALPN, whose pump reads one chunk, records it, and echoesb"pong"— the R-02 duty in one act). All live in the test tree; nothing registers into the shipped surface.One dev-dependency entered:
async-trait = "0.1"(theBidiStreamSourceimpl's annotation; cargo dedupes to alkcall's version). The lib dependency tree is unchanged.
Summary
Completed 2026-10-13 (wave 7). The in-process alkcall harness landed in
src/ops.rs's test tree: two wire shapes over in-memory connections — the call shape (stub JSON ops registered throughOperationRegistryBuilder; serving sideCallAdapterwith the identity + ownership providers wired; client sideCallClient+spawn_dispatchover the loopback pair's stream-per-request source) and the channel shape (stub openable registered per-connection throughChannelCore::register_openable_with_establisher— the awaited Establishment phase; serving sideChannelsAdapter's install hook forking the assembled registry and runningrun_loop_single_stream; client sideChannelClient::from_connection+open_channel). Proven by the stub ops: the JSON stub round-trips call → response envelope; the ACL stub (namespace =blob-namespaceresource) denies anonymous and ungranted identities (closed-by-default, OQ-09) and admits the granted one; the channel stub establishes and bytes flow both directions (ping/pong); the refusals arms prove the ACL gate runs before the establisher and that an establisher refusal answerschannel:open_failed/unknown_resourcethrough the typedChannelOpenError. Four harness tests green (lib 88 with--features ops, 73 default; all conformance/interop columns unchanged). Gates green:cargo test --features ops,cargo test(default, harness compiled out),cargo clippy --all-targets -- -D warningson both shapes,cargo fmt --check,cargo doc --no-depsclean. The handler tasks (ops-json-ops, ops-fetch, ops-put) now register their spec/handler pairs throughOpsHarnessinstead of inventing wiring.