alkcall 0.8.0

Call + channels RPC: structured JSON operations, streaming subscriptions, service discovery, and N-channel multiplexing over one transport stream
Documentation
# alkcall

Call + channels RPC: structured JSON operations, streaming subscriptions,
service discovery, and N-channel multiplexing over one transport stream.

This crate unifies the call protocol and the channels protocol, plus the
vendored core types formerly in `alknet-core`. It is a pure protocol crate
— no networking, no transport dependencies. Downstream crates (alktty,
alktunnels, alktrader) compose on top of it.

## Quick start

### Producer — register an operation and run a dispatcher

```rust
use std::sync::Arc;
use alkcall::core::{Capabilities, IdentityProvider};
use alkcall::protocol::{CallAdapter, connection::CallConnection};
use alkcall::registry::{
    registration::{HandlerRegistration, HandlerKind, OperationRegistry, make_handler},
    spec::{OperationSpec, OperationType, Visibility, AccessControl},
};

let registry = OperationRegistry::new();
registry.register(HandlerRegistration::new(
    OperationSpec::new(
        "echo/run",
        OperationType::Query,
        Visibility::External,
        serde_json::json!({}),
        serde_json::json!({}),
        vec![],
        AccessControl::default(),
        None,
    ),
    HandlerKind::Once(make_handler(|input, ctx| async move {
        alkcall::protocol::wire::ResponseEnvelope::ok(ctx.request_id, input)
    })),
    alkcall::registry::registration::OperationProvenance::Local,
    None,
    None,
    Capabilities::new(),
)).unwrap();

let registry = Arc::new(registry);
let provider: Arc<dyn IdentityProvider> = /* your identity provider */;

let adapter = CallAdapter::new(registry, provider);
// adapter implements ProtocolHandler — call adapter.handle(connection, &auth).await
```

### Consumer — call an operation

```rust
use alkcall::core::Connection;
use alkcall::protocol::connection::CallConnection;

let connection = Connection::from_bidi(
    transport_stream,
    b"alk/call".to_vec(),
    Some(remote_addr),
);
let conn = CallConnection::new(connection);

let response = conn.call("echo/run", serde_json::json!({"msg": "hello"})).await;
assert!(response.result.is_ok());
```

### Channels — open a channel and call through channel 0

```rust
use alkcall::channels::client::ChannelClient;
use alkcall::core::Connection;

let connection = Connection::from_bidi(
    transport_stream,
    b"alk/channels".to_vec(),
    Some(remote_addr),
);
let client = ChannelClient::from_connection(connection).await?;

let response = client.call_open_op(
    "echo/run",
    serde_json::json!({"msg": "hello"}),
).await;
```

### from_call — discover and import remote operations

```rust
use alkcall::client::{from_call, FromCallConfig};

let registrations = from_call(&conn, FromCallConfig::new()).await?;
for reg in registrations {
    conn.register_imported(reg);
}
// now call remote ops as if they were local
let response = conn.call("remote/status", serde_json::json!({})).await;
```

### Serving your own ops as a connected consumer

The call protocol is symmetric — both sides of a connection can serve
ops. A `ChannelClient` built with `from_connection` is a pure consumer
(inbound `call.requested` frames are dropped); pass a `ServingConfig`
to also serve your registry to the peer, and use `op/register` to
announce which ops you serve:

```rust
use std::sync::Arc;
use alkcall::channels::client::{ChannelClient, ServingConfig};
use alkcall::registry::discovery::install_bootstrap_discovery;

let registry = Arc::new(OperationRegistry::new());
// ... register your ops on the registry, then:
install_bootstrap_discovery(&registry)?;

let client = ChannelClient::from_connection_with_serving(
    connection,
    Some(ServingConfig {
        registry: Arc::clone(&registry),
        identity_provider: provider,
        identity: None, // peer identity: transport `Connection::set_identity` propagates
    }),
).await?;
// peer-callable ops resolve against `registry` on channel 0;
// `client.call_open_op` still works — both directions share the pump
```

## Architecture

alkcall is a pure protocol crate — no networking, no transport
dependencies. It provides the call and channels protocols as a library.
Downstream crates compose on top of it in a layered dependency chain.

| Role | Call protocol | Channels protocol |
|------|---------------|-------------------|
| **Producer** | Registers ops on an `OperationRegistry`, runs a `Dispatcher` | Runs a `ChannelsAdapter`, registers openable ALPNs via `ChannelCore::register_openable` |
| **Consumer** | Uses `CallConnection` to call ops, uses `from_call` to discover/import remote ops; may also serve its own ops (`from_connection_with_serving`) | Uses `ChannelClient` to open channels via `call_open_op` + `open_channel` |
| **Hub** | Both: runs a `Dispatcher` for ops it produces, holds `CallConnection`s to spokes for ops it consumes | Both: runs a `ChannelsAdapter` for inbound connections, holds `ChannelClient`s to spokes |
| **Spoke / Worker** | Both: produces ops (its own services), consumes hub ops | Both: produces channels (TTY, tunnel), may consume hub channels |

A single process can be a producer of some ops, a consumer of others, a
channel opener for TTY, and a channel acceptor for tunnels — all on the
same `alk/channels` connection.

## Documentation

- [Architecture docs]docs/architecture/README.md — the authoritative
  spec: ADRs, wire formats, protocol contracts, and composition patterns.
- [API docs]https://docs.rs/alkcall — full crate documentation on docs.rs.
- [Open questions]docs/architecture/open-questions.md — tracked
  deferred decisions and feature gaps.

## License

MIT OR Apache-2.0