docs: README, AGENTS.md ADR renumbering, restore readme field
- Create README.md with quick-start examples for producer, consumer, channels, and from_call patterns - Update AGENTS.md Architecture Context: replace all alknet-source ADR references (064, 071, 093, etc.) with alkcall ADR numbers (001..047) - Update AGENTS.md convention references to use alkcall ADR numbers - Restore readme = "README.md" in Cargo.toml Verification: - 542 tests, 0 failed - cargo clippy --all-targets -- -D warnings: clean - cargo fmt --check: clean - cargo doc --no-deps: clean (0 warnings) - cargo publish --dry-run --allow-dirty: succeeds
This commit is contained in:
1 parent
04c64c30e6
commit
92cd6c7080
3 files changed
+175
-52
No files matched your search
@@ -32,8 +32,8 @@ Exceptions — **do not** commit or push without asking:
|
||||
- The user is actively reviewing the diff and may ask for changes
|
||||
- The change touches published wire formats or semver-relevant public API
|
||||
(this crate will be on crates.io; the `EventEnvelope` shape and the
|
||||
channels 8-byte chunk header are wire-format-stable — see ADR-064 and
|
||||
ADR-071 in the alknet source docs, to be renumbered as alkcall ADRs)
|
||||
channels 8-byte chunk header are wire-format-stable — see ADR-014 and
|
||||
ADR-034)
|
||||
- You'd be force-pushing, amending a published commit, creating an empty
|
||||
commit, or skipping hooks
|
||||
|
||||
@@ -84,8 +84,7 @@ session, not just spawned implementation agents.
|
||||
`OperationContext.metadata`. Outbound credentials flow through
|
||||
`Capabilities` injected at the assembly layer →
|
||||
`HandlerRegistration.capabilities` → `OperationContext.capabilities`
|
||||
→ handler. See the no-env-vars invariant below and ADR-014 (alknet
|
||||
source).
|
||||
→ handler. See the no-env-vars invariant below and ADR-010.
|
||||
|
||||
5. **No-env-vars invariant** — no handler reads outbound credentials
|
||||
from any source other than `OperationContext.capabilities`. The
|
||||
@@ -100,8 +99,7 @@ session, not just spawned implementation agents.
|
||||
peer-keyed composition). Making `OperationEnv` concrete or hardcoding
|
||||
the global registry into the dispatch path would close the
|
||||
session-overlay and connection-overlay patterns. This is the same
|
||||
integration-point pattern as `IdentityProvider`. See ADR-024, ADR-029
|
||||
(alknet source).
|
||||
integration-point pattern as `IdentityProvider`. See ADR-019, ADR-024
|
||||
|
||||
7. **Wire formats are stable** — two wire formats live in this crate:
|
||||
- **`EventEnvelope`** (`{ type, id, payload }` with length-prefixed
|
||||
@@ -110,13 +108,12 @@ session, not just spawned implementation agents.
|
||||
and the five event types (`call.requested`, `call.responded`,
|
||||
`call.completed`, `call.aborted`, `call.error`) are stable. New
|
||||
event types may be added; existing ones must not change shape. See
|
||||
ADR-064 (alknet source).
|
||||
ADR-014.
|
||||
- **Channels 8-byte chunk header** (`[channel_id:u32 BE][length:u32
|
||||
BE][payload]`) — the channels wire format. This is a one-way door:
|
||||
changing the header format breaks all peers. The channels layer has
|
||||
no `stream_type` concept — the handler owns its sub-stream
|
||||
multiplexing on the `BiStream` it receives. See ADR-071, ADR-093
|
||||
(alknet source).
|
||||
multiplexing on the `BiStream` it receives. See ADR-034, ADR-035
|
||||
|
||||
8. **Producer/consumer, not server/client** — both sides of a call or
|
||||
channels connection can initiate. A producer exposes operations
|
||||
@@ -125,8 +122,8 @@ session, not just spawned implementation agents.
|
||||
direction (who opened it) is independent of call/channel direction
|
||||
(who calls/opens). Avoid "server" and "client" framing in docs and
|
||||
API names; use "producer" and "consumer," or "accept side" / "connect
|
||||
side" for the connection-establishment half specifically. See ADR-017,
|
||||
ADR-073 §direction semantics (alknet source).
|
||||
side" for the connection-establishment half specifically. See ADR-022,
|
||||
ADR-037 §direction semantics.
|
||||
|
||||
9. **Vendored core types** — the types formerly in `alknet-core`
|
||||
(`Connection`, `ProtocolHandler`, `BiStream`, `BidiStreamSource`,
|
||||
@@ -137,7 +134,7 @@ session, not just spawned implementation agents.
|
||||
is reworked, it will consume alkcall's versions. Keep these types
|
||||
lean (no TLS, no transport coupling, no endpoint/accept-loop); the
|
||||
dial and the TLS config are concerns of the consumer, not of this
|
||||
crate. See ADR-065, ADR-070, ADR-092 (alknet source).
|
||||
crate. See ADR-007, ADR-008, ADR-009.
|
||||
|
||||
10. **`alktype` dependency** — use `alktype` for binary layout (the
|
||||
channels chunk header, future binary payload schemas) and JSON
|
||||
@@ -173,7 +170,7 @@ session, not just spawned implementation agents.
|
||||
opt-in for long-running work. The abort policy is set on
|
||||
`OperationContext` and propagated through `OperationEnv::invoke()`
|
||||
— the composing handler decides the child's policy, not the wire
|
||||
caller. See ADR-016 (alknet source).
|
||||
caller. See ADR-020.
|
||||
|
||||
15. **Peer authorization via `AccessControl`** — a remote peer's call is
|
||||
authorized by `AccessControl::check(peer_identity)` against the op's
|
||||
@@ -182,7 +179,7 @@ session, not just spawned implementation agents.
|
||||
`AccessControl::default()` is callable by any peer; an op with
|
||||
`required_scopes` is callable only by peers whose `Identity.scopes`
|
||||
satisfy them; an op with `Visibility::Internal` is never callable
|
||||
from the wire. See ADR-029 (alknet source).
|
||||
from the wire. See ADR-024.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
@@ -202,79 +199,76 @@ If feature flags are added, also run `cargo test --all-features` and
|
||||
## Architecture Context
|
||||
|
||||
- `docs/architecture/` — the authoritative spec. Read it before
|
||||
non-trivial changes. ADRs are numbered; OQs (open questions) track
|
||||
resolved/deferred decisions.
|
||||
non-trivial changes. ADRs are numbered 001..047; OQs (open questions)
|
||||
track resolved/deferred decisions.
|
||||
- This crate unifies `alknet-call` and `alknet-channels` from the
|
||||
alknet mono-repo (`/workspace/@alkdev/alknet`). The source
|
||||
architecture docs are at
|
||||
`/workspace/@alkdev/alknet/docs/architecture/crates/call/` and
|
||||
`/workspace/@alkdev/alknet/docs/architecture/crates/channels/`. They
|
||||
will be ported into `docs/architecture/` here and renumbered as
|
||||
alkcall ADRs (starting at ADR-001).
|
||||
- The source ADRs are in
|
||||
`/workspace/@alkdev/alknet/docs/architecture/decisions/`. Key ADRs
|
||||
that inform this crate's design:
|
||||
architecture docs were ported from
|
||||
`/workspace/@alkdev/alknet/docs/architecture/` and renumbered as
|
||||
alkcall ADRs (001..047). The ALPN strings (`alknet/call`,
|
||||
`alknet/channels`) are wire-stable and unchanged.
|
||||
- Key ADRs that inform this crate's design:
|
||||
|
||||
**Call protocol:**
|
||||
- ADR-064 — hand-rolled `EventEnvelope` framing (irpc never
|
||||
integrated; supersedes ADR-005)
|
||||
- ADR-012 — call protocol stream model (bidirectional streams,
|
||||
- ADR-014 — hand-rolled `EventEnvelope` framing (irpc never
|
||||
integrated; supersedes ADR-013)
|
||||
- ADR-015 — call protocol stream model (bidirectional streams,
|
||||
ID-based correlation)
|
||||
- ADR-017 — call protocol client and adapter contract (`CallClient`
|
||||
- ADR-022 — call protocol client and adapter contract (`CallClient`
|
||||
`spawn_dispatch` transport-agnostic; `from_call` imports remote ops;
|
||||
connection direction independent of call direction)
|
||||
- ADR-024 — operation registry layering (curated + session +
|
||||
- ADR-019 — operation registry layering (curated + session +
|
||||
connection overlays; `OperationEnv` as trait-object integration
|
||||
point)
|
||||
- ADR-029 — peer-graph routing model (peer-keyed overlays +
|
||||
- ADR-024 — peer-graph routing model (peer-keyed overlays +
|
||||
`PeerRef` routing; `AccessControl`-based peer authorization)
|
||||
- ADR-014 — secret material flow and capability injection (no secret
|
||||
- ADR-010 — secret material flow and capability injection (no secret
|
||||
material on the wire; capabilities injected at assembly layer)
|
||||
- ADR-015 — privilege model and authority context (`internal` =
|
||||
- ADR-017 — privilege model and authority context (`internal` =
|
||||
authority switch not ACL skip; External/Internal visibility)
|
||||
- ADR-016 — abort cascade for nested calls (default
|
||||
- ADR-020 — abort cascade for nested calls (default
|
||||
`abort-dependents`, `continue-running` opt-in)
|
||||
- ADR-022 — handler registration, provenance, and composition
|
||||
- ADR-018 — handler registration, provenance, and composition
|
||||
authority
|
||||
- ADR-023 — operation error schemas (typed `details` in `call.error`)
|
||||
- ADR-049 — streaming handler for subscriptions
|
||||
- ADR-016 — operation error schemas (typed `details` in `call.error`)
|
||||
- ADR-021 — streaming handler for subscriptions
|
||||
(`StreamingHandler` type, `invoke_streaming()` dispatch path)
|
||||
- ADR-032 — forwarded-for identity (metadata only, never used by
|
||||
- ADR-026 — forwarded-for identity (metadata only, never used by
|
||||
`AccessControl::check`)
|
||||
|
||||
**Channels:**
|
||||
- ADR-071 — channels wire format (8-byte chunk header; one-way door)
|
||||
- ADR-093 — channels pure channel multiplexing (no `stream_type`,
|
||||
- ADR-034 — channels wire format (8-byte chunk header; one-way door)
|
||||
- ADR-035 — channels pure channel multiplexing (no `stream_type`,
|
||||
`BiStream`-only, handler owns sub-stream multiplexing)
|
||||
- ADR-072 — channel 0 pre-negotiated as `alknet/call`
|
||||
- ADR-073 — channel lifecycle operations (`channel/open`/`close`/
|
||||
- ADR-036 — channel 0 pre-negotiated as `alknet/call`
|
||||
- ADR-037 — channel lifecycle operations (`channel/open`/`close`/
|
||||
`control`/`resources/subscribe` on channel 0's call registry)
|
||||
- ADR-075 — `ChannelsAdapter` and `ChannelManager` (substrate-agnostic
|
||||
- ADR-039 — `ChannelsAdapter` and `ChannelManager` (substrate-agnostic
|
||||
demux loop; `ChannelManager` is ALPN-blind, auth-blind,
|
||||
transport-blind)
|
||||
- ADR-076 — backpressure, channel limits, ID reuse (bounded-buffer,
|
||||
- ADR-040 — backpressure, channel limits, ID reuse (bounded-buffer,
|
||||
256-channel per-connection memory bound, monotonic IDs)
|
||||
- ADR-079 — hub relay (translate channel 0, byte-forward data
|
||||
- ADR-042 — hub relay (translate channel 0, byte-forward data
|
||||
channels with ID rewrite)
|
||||
- ADR-080 — `ChannelClient` (transport-agnostic
|
||||
- ADR-043 — `ChannelClient` (transport-agnostic
|
||||
`from_connection` primary; dial lives in the consumer)
|
||||
- ADR-094 — per-identity channel cap (256 per `PeerId` via
|
||||
- ADR-041 — per-identity channel cap (256 per `PeerId` via
|
||||
`ChannelLifecyclePolicy`)
|
||||
|
||||
**Shared (vendored core types):**
|
||||
- ADR-001 — ALPN-based protocol dispatch
|
||||
- ADR-002 — `ProtocolHandler` trait
|
||||
- ADR-004 — auth as shared core (`IdentityProvider` in core,
|
||||
- ADR-003 — auth as shared core (`IdentityProvider` in core,
|
||||
handlers extract credentials)
|
||||
- ADR-006 — ALPN string convention (`alknet/` prefix, one ALPN per
|
||||
- ADR-004 — ALPN string convention (`alknet/` prefix, one ALPN per
|
||||
connection)
|
||||
- ADR-007 — `BiStream` type definition (handlers receive
|
||||
- ADR-005 — `BiStream` type definition (handlers receive
|
||||
`Connection`, not `BiStream`)
|
||||
- ADR-065 — `Connection::from_stream` (generic single-stream
|
||||
- ADR-007 — `Connection::from_stream` (generic single-stream
|
||||
connections — unblocks TCP+TLS, SSH, WebTransport, wasm)
|
||||
- ADR-070 — `BidiStreamSource` trait (the `Connection` extension
|
||||
- ADR-008 — `BidiStreamSource` trait (the `Connection` extension
|
||||
point `ChannelBidiStreamSource` implements)
|
||||
- ADR-092 — `BiStream` as the handler leaf (`accept_bi` returns
|
||||
- ADR-009 — `BiStream` as the handler leaf (`accept_bi` returns
|
||||
`BiStream`)
|
||||
|
||||
- If a TODO references a "Phase B" or a design direction that an ADR
|
||||
|
||||
@@ -5,6 +5,7 @@ edition = "2021"
|
||||
rust-version = "1.85"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Call + channels RPC: structured JSON operations, streaming subscriptions, service discovery, and N-channel multiplexing over one transport stream"
|
||||
readme = "README.md"
|
||||
repository = "https://git.alk.dev/alkdev/alkcall"
|
||||
keywords = ["rpc", "json-rpc", "multiplexing", "wire-format", "alpn"]
|
||||
categories = ["network-programming", "asynchronous", "encoding"]
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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 mut 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"alknet/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"alknet/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;
|
||||
```
|
||||
|
||||
## 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 | 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 `alknet/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
|
||||
Reference in new issue
Block a user