The #1 gap in alknet-call: the outbound connection opener. Every downstream consumer (runner, container service, bilateral exchange, NAPI, agent cross-node dispatch) is blocked on it. Shared dispatch loop (ADR-017 §1 — the architectural commitment that keeps CallClient from becoming a parallel protocol implementation): - Extracts the accept-path dispatch (sweeper, accept_bi loop, handle_stream, dispatch_requested, build_root_context, compose_root_env, fail_all on close) out of CallAdapter into a new protocol/dispatch.rs Dispatcher struct. Both CallAdapter::handle and CallClient::connect produce a CallConnection and hand it to Dispatcher::run_loop — the loop is genuinely shared (refactored, not duplicated). - CallAdapter keeps its public API and test-facing wrappers (pub(crate), #[cfg(test)]-gated) that delegate to the Dispatcher. Peer-scoped default-deny (ADR-028 — the one-way-door security dimension): - RemoteFilter { trusted_peer: bool } on the Dispatcher. In default-deny mode (CallClient::new), an incoming call to an op with remote_safe: false returns NOT_FOUND *before* any capability material reaches the handler — a remote peer's call must not populate OperationContext.capabilities from the local registration bundle unless the op is explicitly remote-safe (ADR-028 Context). Trusted-peer mode (CallClient::trusted_peer, explicit opt-in) bypasses the filter. - The accept path (CallAdapter) uses RemoteFilter::trusted() by convention: a direct QUIC client is not a filtered CallClient peer in the ADR-028 sense. - OperationRegistry::list_operations_peer_scoped(trusted_peer) + services_list_handler_peer_scoped for the CallClient's services/list serving path (ADR-028 Assumption 2: a peer should not see ops it cannot call, so discovery and dispatch filters agree). CallClient (src/client/call_client.rs): - CallClient { registry, identity_provider, trusted_peer: bool }. - new() default-deny; trusted_peer() explicit opt-in (ADR-028 §3). - connect(addr, CallCredentials) dials QUIC on ALPN alknet/call (quinn feature), spawns Dispatcher::run_loop, returns a live CallConnection. - spawn_dispatch(connection) shared path for connect + tests. - CallCredentials { tls_identity, auth_token, remote_identity } — all from Capabilities (ADR-014), never env vars (no-env-vars invariant). v1 connects without client-auth TLS identity (server uses AcceptAnyCertVerifier); RawKey client-auth is a two-way-door remainder. - RemoteIdentity { fingerprint } — concrete shape is a two-way door (OQ-25 remainder); the one-way constraint is it comes from Capabilities. - ClientError { Transport, TlsSetup, ConnectionClosed }. - CallConnection is now Clone (shares the inner Arcs) so connect can hand the caller a live clone while the dispatcher task keeps its clone. Tests (199 lib + 1 integration): - Unit: default-deny NOT_FOUND for non-remote-safe; remote_safe dispatches; trusted-peer dispatches all External; default-deny does NOT populate capabilities (the load-bearing security assertion — verified by a handler that inspects context.capabilities and the fact that the handler is never reached for non-remote-safe ops); remote_safe op populates capabilities; services/list peer-scoped hide/trusted variants; CallClient constructors; CallCredentials builder; Send+Sync. - Integration (tests/two_node_call.rs): real QUIC loopback — CallAdapter server (self-signed cert via rcgen) accepts, CallClient connects, client.call() round-trips to server/echo. Proves the connect path + shared dispatch loop work end-to-end. clippy + fmt + test all green. Refs: tasks/call/client/call-client.md Refs: docs/architecture/decisions/017-call-protocol-client-and-adapter-contract.md §1, §2, §7 Refs: docs/architecture/decisions/028-callclient-peer-scoped-registry-filtering.md Refs: docs/architecture/crates/call/client-and-adapters.md
Alknet
Status: Pre-alpha — This project is undergoing a major architectural pivot to an ALPN-as-service model. The previous implementation has been archived and a greenfield rebuild is in progress.
A self-hostable networking toolkit built on QUIC+TLS with ALPN-based protocol dispatch. Each protocol handler (SSH, SFTP, Git, HTTP, DNS, messaging, call protocol) registers an ALPN string on a shared endpoint. The ALPN negotiation during the TLS/QUIC handshake routes connections to the correct handler before any application bytes are read.
Core Insight
A service IS an ALPN. One endpoint, one port, many protocols — dispatched by the TLS handshake, not by application-level peeking or separate listeners.
Crates
| Crate | Status | Description |
|---|---|---|
alknet-vault |
stable | Local key vault: BIP39/SLIP-0010/AES-GCM key derivation and encryption |
alknet-core |
planned | ProtocolHandler trait, ALPN router, auth/identity, config |
alknet-ssh |
planned | SSH handler (russh), SOCKS5, port forwarding |
alknet-call |
planned | JSON-RPC call protocol (EventEnvelope framing) |
alknet-fs |
planned | Content-addressed file storage (iroh-blobs backend) |
alknet-sftp |
planned | SFTP handler (russh-sftp protocol core) |
alknet-git |
planned | Git smart protocol handler (gix) |
alknet-http |
planned | HTTP handler (axum, REST API, MCP) |
alknet-dns |
planned | DNS handler (hickory-proto, pkarr) |
alknet-msg |
planned | E2E encrypted messaging, mixnet support |
alknet |
planned | CLI binary (assembles and registers handlers) |
Documentation
- ALPN-as-service architecture — pivot proposal
- Cleanup plan — greenfield transition plan
- SDD process — spec-driven development process
- Research references — iroh, russh, russh-sftp deep dives
Reference implementation (previous architecture) is preserved at /workspace/@alkdev/alknet-main/.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.