feat(tests): the in-process alkcall harness — the serving assembly and the client side over one duplex pair (wave 7)

- 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
This commit is contained in:
glm-5.3-flash committed 2026-10-13 00:04:09 +00:00
1 parent 6feaa2ac72
commit bc6da37e35
3 files changed
+813 -8

No files matched your search

+95 -8
View File
@@ -1,7 +1,7 @@
---
id: ops-call-harness
name: the in-process alkcall harness — the serving assembly and the client side over one duplex pair
status: pending
status: completed
depends_on: [ops-foundation]
scope: narrow
risk: low
@@ -45,17 +45,17 @@ surface is testable server-less.
## Acceptance Criteria
- [ ] The harness lives in the ops module's test tree
- [x] The harness lives in the ops module's test tree
(`#[cfg(test)]` under feature `ops`), parameterized over the
assembled registry (ops register; the harness serves)
- [ ] Server + client halves over one duplex pair; the dispatch run
- [x] 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
- [x] The identity/ownership wiring exercisable: a stub op carrying
an `AccessControl` denies an ungranted identity and admits a
granted one (the ACL path proven end-to-end pre-handler)
- [ ] The channel path proven: a stub `channel_open` op establishes
- [x] The channel path proven: a stub `channel_open` op establishes
(the `Establishment` phase) and bytes flow both directions
- [ ] `cargo test --features ops` green; the default build unaffected
- [x] `cargo test --features ops` green; the default build unaffected
(the harness is feature-gated test code);
`cargo clippy --all-targets -- -D warnings` and
`cargo fmt --check` green on both shapes
@@ -78,8 +78,95 @@ surface is testable server-less.
## Notes
> To be filled by implementation agent
> Landed as a `#[cfg(test)] mod harness` appended to
> `src/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-level `alk/call` shape needs — `open_bi` hands out pre-wired
> stream pairs (one `tokio::io::duplex` pair per stream, joined via
> the public `BiStream::from_joined`), `open_bi` at exhaustion answers
> `StreamClosed`, `accept_bi` at idle parks (an open connection with
> no inbound streams), `close` drains. This was the one place the
> task text needed a ruling: **`spawn_dispatch` + stream-per-request
> cannot complete a call over a single `from_bidi` duplex end**
> (the accept loop consumes the one stream and `call()`'s `open_bi`
> then fails; alkcall's own e2e shape is the same pre-wired
> `BidiStreamSource` stand-in, `pub(crate)` there — reimplemented
> here as the documented downstream seam, N streams wide so the
> client can call repeatedly). The channel shape uses
> `Connection::from_bidi` halves directly (the channels protocol
> multiplexes internally).
> - **The serving assembly**: `OperationRegistryBuilder` receiving the
> stub ops family's spec/handler pairs (`with_local`) →
> `CallAdapter::new(...).with_ownership_provider(...)` →
> `ProtocolHandler::handle` (the dispatch run loop) over its
> `from_source` half; client side
> `CallClient::new(...).spawn_dispatch(...)` over the other.
> `GatewayDispatch` (alkcall's `gateway`-gated transport-neutral
> spine) was not wired — no gateway front-end rides the duplex; the
> wire-serving stack is the `CallAdapter` half.
> - **The channel assembly**: `ChannelsAdapter::new(install_hook,\ NoCap)`
> — the install hook (the ADR-036 amendment shape): sets the
> transport-authenticated identity on channel 0's `Connection`
> (`Connection::set_identity`, the CF-005 (b) path), forks the
> assembled registry (ADR-047 §4's per-connection registration), and
> registers the stub openables through
> `ChannelCore::register_openable_with_establisher` — the awaited
> **Establishment phase** (establisher records its invocation,
> admits `stub-ns` only, returns `Establishment::default()`; refusal
> → `channel:open_failed`/`unknown_resource`) — then
> `Dispatcher::run_loop_single_stream` over
> `CallConnection::new_single_stream`. Client side:
> `ChannelClient::from_connection`, `open_channel` for the binary
> data plane, `call_open_op` for the refusal arms.
> - **The identity/ACL wiring** (`TestIdentityProvider` — token →
> identity — and `TestOwnership` — namespace → identities → granted
> actions, closed by default): the call shape exercises the
> `auth_token` → `IdentityProvider` route (payload `auth_token` via
> `call_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's `AccessControl::check` with the ownership provider
> in context — the ops surface's namespace-as-resource mapping
> (`blob-namespace`, action `read`, `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
> the `alk/stub` data-plane ALPN, whose pump reads one chunk, records
> it, and echoes `b"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"` (the
> `BidiStreamSource` impl's annotation; cargo dedupes to alkcall's
> version). The lib dependency tree is unchanged.
## Summary
> To be filled on completion
> 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 through
> `OperationRegistryBuilder`; serving side `CallAdapter` with the
> identity + ownership providers wired; client side `CallClient` +
> `spawn_dispatch` over the loopback pair's stream-per-request source)
> and the channel shape (stub openable registered per-connection through
> `ChannelCore::register_openable_with_establisher` — the awaited
> Establishment phase; serving side `ChannelsAdapter`'s install hook
> forking the assembled registry and running `run_loop_single_stream`;
> client side `ChannelClient::from_connection` + `open_channel`).
> Proven by the stub ops: the JSON stub round-trips call → response
> envelope; the ACL stub (namespace = `blob-namespace` resource) 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 answers
> `channel:open_failed`/`unknown_resource` through the typed
> `ChannelOpenError`. 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 warnings` on both
> shapes, `cargo fmt --check`, `cargo doc --no-deps` clean. The
> handler tasks (ops-json-ops, ops-fetch, ops-put) now register their
> spec/handler pairs through `OpsHarness` instead of inventing wiring.