chore: prepublish review — bump to 0.3.0, changelog, doc alignment
0.2.0 is already on crates.io (2026-08-31, c16b069); the review
004/005 remediation work is unreleased on top of it and lands as
0.3.0.
- Bump version 0.2.0 -> 0.3.0. Two OperationRegistry methods changed
borrowed returns to owned (registration, list_operations) —
source-breaking for annotated call sites, minor bump per 0.x
semver rules. cargo semver-checks passes (196 checks) against the
published baseline; the return-type changes were caught by manual
diff review.
- CHANGELOG 0.3.0: connect-side serving (from_connection_with_serving
+ ServingConfig), OperationRegistry::fork + builder from_registry,
registry::op_register (bootstrap op, collision policy,
ALREADY_EXISTS), install_bootstrap_discovery, spec_to_json_pub +
resource_id_path round-trip, overlay accessors, concurrent serving
loops, &self registration.
- README: serving-as-consumer section, consumer role table update,
drop the stale `mut` on the registry example.
- AGENTS.md: ADR range 001..047 -> 001..048.
- Fix rustdoc private-intra-doc-link warning on StartedDispatch.
Verification: 589 default / 606 all-features tests, clippy
(all-targets, all-features, wasm32) clean, fmt clean, doc clean,
publish dry-run OK.
This commit is contained in:
1 parent
d5b2661b38
commit
1e20bb77d0
6 files changed
+133
-7
No files matched your search
@@ -215,13 +215,14 @@ cargo semver-checks check-release # before a release
|
||||
## Architecture Context
|
||||
|
||||
- `docs/architecture/` — the authoritative spec. Read it before
|
||||
non-trivial changes. ADRs are numbered 001..047; OQs (open questions)
|
||||
non-trivial changes. ADRs are numbered 001..048; 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 were ported from
|
||||
`/workspace/@alkdev/alknet/docs/architecture/` and renumbered as
|
||||
alkcall ADRs (001..047). The ALPN strings (`alk/call`,
|
||||
alkcall ADRs (001..048; ADR-048 was authored in this crate). The ALPN
|
||||
strings (`alk/call`,
|
||||
`alk/channels`) are wire-stable going forward (renamed from
|
||||
`alknet/` to `alk/` in v0.1.1, before the first published consumer).
|
||||
- Key ADRs that inform this crate's design:
|
||||
|
||||
@@ -4,6 +4,102 @@ All notable changes to this crate are documented here. The format is
|
||||
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||||
this crate adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [0.3.0] - 2026-09-04
|
||||
|
||||
The connect side can serve (two-way ops over one channels connection),
|
||||
per-session fork registries, and the `op/register` bootstrap op — the
|
||||
remediation of reviews 004 and 005
|
||||
(`docs/reviews/004-*.md`, `docs/reviews/005-*.md`). Two
|
||||
`OperationRegistry` methods changed their return types from borrowed to
|
||||
owned (source-breaking for annotated call sites; minor bump per 0.x
|
||||
semver rules) — verified with `cargo semver-checks` (196 checks pass
|
||||
against the published 0.2.0 baseline; the return-type changes were
|
||||
caught by manual diff review, as the tool has no lint for that
|
||||
pattern).
|
||||
|
||||
### Added
|
||||
|
||||
- **`OperationRegistry::fork`** — deep-copies a registry (handlers,
|
||||
provenance, composition authority, capabilities, cached
|
||||
publish-schema validators) into an independently-mutable copy. The
|
||||
per-session shape: fork the deployment's base registry, register the
|
||||
session's ops on the fork, dispatch the session over the fork
|
||||
(ADR-047 §4 amendment, 2026-09-03). Internally mutable, so a fork
|
||||
shared as an `Arc` can receive registrations after the dispatcher
|
||||
was built — `install_bootstrap_discovery` relies on this.
|
||||
- **`OperationRegistryBuilder::from_registry`** — seeds a builder from
|
||||
an existing registry's registrations (the fork surface expressed
|
||||
through the builder; registration order is not preserved — HashMap
|
||||
iteration).
|
||||
- **Connect-side serving (two-way ops over one connection).** The
|
||||
channels connect side's channel-0 read pump previously resolved
|
||||
outbound responses only and silently dropped inbound
|
||||
`call.requested` frames. New `ChannelClient::from_connection_with_serving`
|
||||
takes `Option<ServingConfig>` (`registry` + `identity_provider`);
|
||||
with `Some(..)` the read pump becomes the full-duplex serving loop
|
||||
(`Dispatcher::serve_single_stream`), so a connected peer can call the
|
||||
consumer's ops. `from_connection` keeps the resolution-only pump
|
||||
(pure-consumer default). Serving is opt-in: the protocol is
|
||||
symmetric, the API is explicit (ADR-022 amendment, 2026-09-03).
|
||||
- **`Dispatcher::serve_single_stream` and `Dispatcher::dispatch_start`
|
||||
+ `StartedDispatch`** — the shared serving-loop machinery. Both
|
||||
single-stream loops (accept side and connect side) now run the same
|
||||
concurrency model: Once invocations, Sub pumps, and sink response
|
||||
writers are spawned, so same-connection nested composition resolves
|
||||
concurrently instead of deadlocking until the 30 s sweeper (review
|
||||
005 G-01). Handles are tracked and aborted at loop exit; no lock is
|
||||
held across an await.
|
||||
- **`registry::op_register` module — the `op/register` bootstrap op**
|
||||
(review 004 F-05). A connected peer announces the ops it serves over
|
||||
the wire: `OP_REGISTER_NAME` (`op/register`), `OpRegisterRequest`
|
||||
(wire round-trip via `to_json`/`from_json`), `op_register_spec` /
|
||||
`op_register_handler`. Announcements land in the connection overlay
|
||||
and are served through forwarding handlers, so hub→consumer import
|
||||
(`from_call`) and consumer→hub announce are symmetric. **Collision
|
||||
policy** (review 005 G-03): a peer-announced op may replace other
|
||||
peer-announced ops (when `replace` is set) but never the serving
|
||||
side's own registrations — a name present on the serving registry
|
||||
rejects with the new `CallError::already_exists` (`ALREADY_EXISTS`,
|
||||
non-retryable), regardless of `replace`. The collision gate checks
|
||||
the serving registry, the session fork, and the connection overlay
|
||||
itself.
|
||||
- **`install_bootstrap_discovery`** (review 004 F-06) — registers
|
||||
`services/list`, `services/list-peers`, and `services/schema`
|
||||
against a registry with handlers closed over that same `Arc`, so
|
||||
per-session forks are the discovery source for their own openables
|
||||
(handlers see every op the registry serves at call time). ACL
|
||||
filtering stays per-caller. The bootstrap-op set on channel 0 is
|
||||
closed and now includes `services/list-peers` (review 005 G-05 —
|
||||
doc alignment; the code had installed it since the amendment landed)
|
||||
plus `op/register` (ADR-022 amendment).
|
||||
- **`spec_to_json_pub`** — public serialization of an `OperationSpec`
|
||||
into the `services/schema` wire shape (the shape `op/register`
|
||||
announces with and `services/schema` serves). **`resource_id_path`
|
||||
now survives the spec wire round-trip** (review 005 G-04): both the
|
||||
serializer and the `op/register` parser carry the field.
|
||||
- **`CallConnection::overlay_contains` /
|
||||
`CallConnection::overlay_registration`** — read access to the
|
||||
connection overlay, for `op/register` replace semantics and
|
||||
composition reachability checks.
|
||||
|
||||
### Changed
|
||||
|
||||
- **`OperationRegistry::registration` returns
|
||||
`Option<HandlerRegistration>`** (was `Option<&HandlerRegistration>`)
|
||||
and **`list_operations` returns `Vec<OperationSpec>`** (was
|
||||
`Vec<&OperationSpec>`). Source-breaking for call sites that annotate
|
||||
the borrowed types — clone-at-the-boundary instead. Minor bump per
|
||||
0.x semver rules.
|
||||
- **Registration is `&self` throughout** — `OperationRegistry::register`,
|
||||
`ChannelOperations::register_on`, and `ChannelCore::register_openable`
|
||||
take `&OperationRegistry` (was `&mut`). Source-compatible for
|
||||
callers (reborrow); this is what makes post-construction
|
||||
registration on a forked, `Arc`-shared registry possible.
|
||||
- **`from_call` composition reachability** — the forwarding-handler
|
||||
path declares reachable namespaces on the composing handler's
|
||||
`scoped_env` (empty is deny-by-default) and the connection overlay
|
||||
is attached only for identity-carrying connections (ADR-030 §5).
|
||||
|
||||
## [0.2.0] - 2026-08-31
|
||||
|
||||
Consumer-findings remediation (CF-001..004 from
|
||||
@@ -137,6 +233,7 @@ Vendored core types (`Connection`, `ProtocolHandler`, `BiStream`,
|
||||
(ADR-046), the channels protocol with openable-ALPNs-as-operations
|
||||
(ADR-047), and the `ChannelClient` transport-agnostic client.
|
||||
|
||||
[0.3.0]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.3.0
|
||||
[0.2.0]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.2.0
|
||||
[0.1.1]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.1.1
|
||||
[0.1.0]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.1.0
|
||||
Generated
+1
-1
@@ -27,7 +27,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alkcall"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "alkcall"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.85"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -21,7 +21,7 @@ use alkcall::registry::{
|
||||
spec::{OperationSpec, OperationType, Visibility, AccessControl},
|
||||
};
|
||||
|
||||
let mut registry = OperationRegistry::new();
|
||||
let registry = OperationRegistry::new();
|
||||
registry.register(HandlerRegistration::new(
|
||||
OperationSpec::new(
|
||||
"echo/run",
|
||||
@@ -98,6 +98,34 @@ for reg in registrations {
|
||||
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(®istry)?;
|
||||
|
||||
let client = ChannelClient::from_connection_with_serving(
|
||||
connection,
|
||||
Some(ServingConfig {
|
||||
registry: Arc::clone(®istry),
|
||||
identity_provider: provider,
|
||||
}),
|
||||
).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
|
||||
@@ -107,7 +135,7 @@ 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` |
|
||||
| **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 |
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ impl std::fmt::Debug for DispatchResult {
|
||||
}
|
||||
|
||||
/// The started half of a dispatched `call.requested` event, from
|
||||
/// [`Dispatcher::dispatch_start`]. `Once`/`Stream` are returned ready
|
||||
/// `Dispatcher::dispatch_start`. `Once`/`Stream` are returned ready
|
||||
/// for the caller to spawn (review 005 G-01); `Sink` was started
|
||||
/// inline (its `chunk_tx` must be registered before the next
|
||||
/// `call.published` frame is read) and carries the started handler
|
||||
|
||||
Reference in new issue
Block a user