Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
126fdec243 | ||
|
|
a9429dfb1d | ||
|
|
0b60b8d0be | ||
|
|
80d6ba0e5a |
@@ -6,6 +6,29 @@ this crate adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.0] — 2026-09-07
|
||||
|
||||
### Changed
|
||||
|
||||
- **`alkcall` dependency bumped to 0.6.0** (review 007's
|
||||
establishment follow-ups sweep). One consumer-visible change: the
|
||||
`OpenHandler` signature gained a plan parameter —
|
||||
`Fn(Value, Option<ChannelPlan>, Connection, AuthContext) ->
|
||||
JoinHandle<()>` — so `OpenableAlpn::new` callers' handler closures
|
||||
gain a `_plan` argument (mechanical). With an establisher attached
|
||||
(ADR-049), its `Establishment.plan` (a typed-opaque
|
||||
`Arc<dyn Any + Send + Sync>`) is threaded process-locally to the
|
||||
handler's plan parameter: ALPN crates dial/allocate in the
|
||||
establisher and deliver the live handle without a side-channel
|
||||
handoff. This crate's WS ferry passes the `Option<OpenEstablisher>`
|
||||
through unchanged and constructs no `Establishment` itself, so the
|
||||
ride is otherwise additive — alkcall 0.6.0's `channels::pump_bidi`
|
||||
helper (ADR-050) is available to data-plane handlers this crate
|
||||
ferries. Docs updated (websocket.md, `OpenableAlpn` field docs,
|
||||
adapter.rs).
|
||||
|
||||
## [0.2.0] — 2026-09-06
|
||||
|
||||
### Changed
|
||||
|
||||
- **`alkcall` dependency bumped to 0.2** with the `gateway` feature
|
||||
@@ -28,6 +51,21 @@ this crate adheres to [Semantic Versioning](https://semver.org/).
|
||||
validate before dispatch.
|
||||
- 0.4.1 — early-arrival chunks are parked for un-adopted
|
||||
channels, closing the open/first-data race on the WS overlay.
|
||||
- 0.5.0 — the channels establisher phase (ADR-049):
|
||||
`OpenableAlpn` gains `establisher: Option<OpenEstablisher>` and
|
||||
`establisher_timeout: Option<Duration>` (both default `None` via
|
||||
`OpenableAlpn::new`, so existing constructions compile
|
||||
unchanged) plus a `with_establisher(establisher, timeout)`
|
||||
builder; the WS ferry calls
|
||||
`register_openable_with_establisher`, threading both fields
|
||||
through. With an establisher attached, the open op awaits the
|
||||
bounded establishment phase before the reply and resolves
|
||||
`channel:open_failed` with `details.reason` on failure — the
|
||||
channel never exists consumer-side. Without one, behavior is
|
||||
the pre-0.5 shape. The alkcall 0.5.0 `open_channel` error-type
|
||||
change needs no alkhttp change (this crate never calls
|
||||
`open_channel`; `from_wss` uses `ChannelClient::from_connection`
|
||||
only).
|
||||
- **`/publish` chunk validation uses the registry-owned validator**
|
||||
(CF-003). The local compile-once `PublishSchemaCache`
|
||||
(`gateway/schema_cache.rs`) is removed — `publish_schema` is
|
||||
|
||||
Generated
+6
-6
@@ -27,9 +27,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alkcall"
|
||||
version = "0.4.1"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9badefe048a194c93eed09bc5326ebf092fc86817e561d88de2cd12b6302753a"
|
||||
checksum = "a44500900b82904d2a870e444051fc041a042e42ff7190803bbc5b3973f4feeb"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -48,7 +48,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alkhttp"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"alkcall",
|
||||
"arc-swap",
|
||||
@@ -1485,7 +1485,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1860,7 +1860,7 @@ dependencies = [
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2679,7 +2679,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "alkhttp"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.85"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -42,7 +42,7 @@ h2 = ["server", "dep:hyper", "hyper-util/http2", "hyper/http2"]
|
||||
http1 = ["server", "dep:hyper", "hyper-util/http1", "hyper/http1"]
|
||||
|
||||
[dependencies]
|
||||
alkcall = { version = "0.4", features = ["gateway"] }
|
||||
alkcall = { version = "0.6", features = ["gateway"] }
|
||||
arc-swap = { version = "1", optional = true }
|
||||
axum = { version = "0.8", optional = true, features = ["ws"] }
|
||||
bytes = "1"
|
||||
|
||||
@@ -161,7 +161,7 @@ A lean single-side build takes `default-features = false` plus the side
|
||||
it needs:
|
||||
|
||||
```toml
|
||||
alkhttp = { version = "0.1", default-features = false, features = ["server"] }
|
||||
alkhttp = { version = "0.3", default-features = false, features = ["server"] }
|
||||
```
|
||||
|
||||
## Security posture
|
||||
|
||||
@@ -275,9 +275,26 @@ that motivated the alknet WebTransport track) workable over WS: the
|
||||
SSH byte stream rides a data channel.
|
||||
|
||||
The openable set is declared with `HttpAdapter::with_ws_openable_alpns`
|
||||
(each `OpenableAlpn { spec, open_handler }`; the ALPN-specific handlers
|
||||
stay in the ALPN crates — alkhttp ferries the registrations), with the
|
||||
`OpenableAlpns` request-extension fallback for bare-registry/custom
|
||||
(each `OpenableAlpn { spec, open_handler, establisher,
|
||||
establisher_timeout }`; the ALPN-specific handlers and establishers
|
||||
stay in the ALPN crates — alkhttp ferries the registrations). The
|
||||
establisher (alkcall 0.5.0 / ADR-049) is the awaited establishment
|
||||
phase of the open op: `None` (the default) keeps the pre-0.5 shape —
|
||||
the open replies as soon as the pump handler spawns; `Some`
|
||||
semantically-validating hook dials/prepares the backend before the
|
||||
reply, and a bounded failure resolves `channel:open_failed` with
|
||||
`details.reason` (the channel never exists consumer-side). The
|
||||
per-registration timeout override bounds the establisher when the
|
||||
dispatch carries no deadline. The establisher's `Establishment.plan`
|
||||
payload (alkcall 0.6.0 / ADR-049 amendment 2 — a typed-opaque
|
||||
`ChannelPlan`) is threaded process-locally to the pump handler's
|
||||
second parameter: the establisher and the handler agree on the
|
||||
concrete type, so ALPN crates dial/allocate in the establisher and
|
||||
deliver the live handle to their handler without a side-channel
|
||||
handoff. Data-plane handlers that pump a channel stream against a
|
||||
peer's split halves can use alkcall's `channels::pump_bidi` helper
|
||||
(alkcall 0.6.0 / ADR-050). The `OpenableAlpns` request-extension
|
||||
fallback is available for bare-registry/custom
|
||||
upgrade routes. Cap policy is the `ChannelsPolicy` extension (one
|
||||
instance consulted by both the open wrappers and the demux teardown).
|
||||
Peer-announced ops (`op/register`) land in the connection-local
|
||||
|
||||
@@ -270,9 +270,13 @@ impl HttpAdapter {
|
||||
/// The openable-ALPN set for WS sessions (WS-22, review 006
|
||||
/// Unit 2): one [`OpenableAlpn`](crate::websocket::OpenableAlpn)
|
||||
/// per openable data-channel ALPN — the open-op spec (with the
|
||||
/// `channel_open` marker) and the ALPN-specific
|
||||
/// [`OpenHandler`](alkcall::channels::operations::OpenHandler).
|
||||
/// Each WS
|
||||
/// `channel_open` marker), the ALPN-specific
|
||||
/// [`OpenHandler`](alkcall::channels::operations::OpenHandler),
|
||||
/// and the optional establisher + per-registration timeout
|
||||
/// (ADR-049, threaded through with `None` defaults). With an
|
||||
/// establisher attached, its `Establishment.plan` (alkcall 0.6.0 /
|
||||
/// ADR-049 amendment 2) is threaded process-locally to the pump
|
||||
/// handler's second parameter. Each WS
|
||||
/// session's per-session fork registers the set (plus the generic
|
||||
/// channel ops, bootstrap discovery, and `op/register`), so a WS
|
||||
/// client can open data channels exactly as any channels consumer
|
||||
|
||||
+63
-10
@@ -46,7 +46,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use alkcall::channels::adapter::ChannelsAdapter;
|
||||
use alkcall::channels::operations::{ChannelCore, OpenHandler};
|
||||
use alkcall::channels::operations::{ChannelCore, OpenEstablisher, OpenHandler};
|
||||
use alkcall::channels::policy::{ChannelLifecyclePolicy, NoCap};
|
||||
use alkcall::core::auth::{AuthContext, Identity};
|
||||
use alkcall::core::types::{Connection, ProtocolHandler};
|
||||
@@ -257,11 +257,13 @@ impl WsSessions {
|
||||
|
||||
/// One deployment-declared openable ALPN for the WS path (WS-22):
|
||||
/// the per-ALPN open-op `OperationSpec` (with the `channel_open`
|
||||
/// marker set via `OperationSpec::with_channel_open`) and the
|
||||
/// marker set via `OperationSpec::with_channel_open`), the
|
||||
/// ALPN-specific [`OpenHandler`] the data-plane protocol runs on the
|
||||
/// allocated channel's `Connection`. The ALPN-specific handler stays
|
||||
/// in the ALPN crates (alktty et al.); this crate only ferries the
|
||||
/// registration onto each session's fork.
|
||||
/// allocated channel's `Connection`, and the optional establishment
|
||||
/// phase (ADR-049) the open-op wrapper awaits — bounded — before the
|
||||
/// reply. The ALPN-specific handler and establisher stay in the ALPN
|
||||
/// crates (alktty et al.); this crate only ferries the registration
|
||||
/// onto each session's fork.
|
||||
#[derive(Clone)]
|
||||
pub struct OpenableAlpn {
|
||||
/// The open-op spec (Query/Mutation/Sub with the `channel_open`
|
||||
@@ -269,14 +271,63 @@ pub struct OpenableAlpn {
|
||||
/// per the upstream C-08 stub until channel adoption lands).
|
||||
pub spec: OperationSpec,
|
||||
/// The data-plane protocol handler spawned on the allocated
|
||||
/// channel's `Connection`.
|
||||
/// channel's `Connection` — `Fn(Value, Option<ChannelPlan>,
|
||||
/// Connection, AuthContext) -> JoinHandle<()>`. With an
|
||||
/// establisher attached, the plan (alkcall 0.6.0 /
|
||||
/// ADR-049 amendment 2) is the establisher's `Establishment.plan`:
|
||||
/// the establisher and the handler agree on the concrete type
|
||||
/// (downcast in the ALPN crate); `None` when no establisher is
|
||||
/// registered or it returned `Establishment::default()`
|
||||
/// (`alkcall::channels::operations::Establishment::default`).
|
||||
pub open_handler: OpenHandler,
|
||||
/// The awaited establishment phase (ADR-049 §1): validate params
|
||||
/// semantically, consult ownership, prepare/dial the backend —
|
||||
/// before the open reply. `None` (the default) = an always-OK
|
||||
/// establisher (the pre-ADR-049 shape; existing registrations
|
||||
/// behave unchanged). A successful establisher returns
|
||||
/// `Establishment::new(plan)` (to deliver a channel plan to the
|
||||
/// pump handler) or `Establishment::default()` when it only
|
||||
/// validates — both in
|
||||
/// `alkcall::channels::operations`.
|
||||
pub establisher: Option<OpenEstablisher>,
|
||||
/// The per-registration bound on the establisher await
|
||||
/// (ADR-049 §2). `None` = [`ESTABLISHMENT_TIMEOUT`] (10s) when the
|
||||
/// dispatch carries no deadline; the effective bound is the
|
||||
/// earlier of the dispatch deadline and this override.
|
||||
///
|
||||
/// [`ESTABLISHMENT_TIMEOUT`]: alkcall::channels::operations::ESTABLISHMENT_TIMEOUT
|
||||
pub establisher_timeout: Option<std::time::Duration>,
|
||||
}
|
||||
|
||||
impl OpenableAlpn {
|
||||
/// Declare one openable ALPN.
|
||||
/// Declare one openable ALPN (no establisher — the pre-ADR-049
|
||||
/// shape; the open op replies as soon as the pump handler is
|
||||
/// spawned).
|
||||
pub fn new(spec: OperationSpec, open_handler: OpenHandler) -> Self {
|
||||
Self { spec, open_handler }
|
||||
Self {
|
||||
spec,
|
||||
open_handler,
|
||||
establisher: None,
|
||||
establisher_timeout: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach an establishment phase (ADR-049 §1): awaited by the
|
||||
/// open-op wrapper — bounded by the dispatch deadline, this
|
||||
/// crate's [`ESTABLISHMENT_TIMEOUT`] default, or
|
||||
/// `timeout` when set — before the reply; on failure the open op
|
||||
/// resolves `channel:open_failed` with `details.reason` and the
|
||||
/// channel never exists consumer-side.
|
||||
///
|
||||
/// [`ESTABLISHMENT_TIMEOUT`]: alkcall::channels::operations::ESTABLISHMENT_TIMEOUT
|
||||
pub fn with_establisher(
|
||||
mut self,
|
||||
establisher: OpenEstablisher,
|
||||
timeout: Option<std::time::Duration>,
|
||||
) -> Self {
|
||||
self.establisher = Some(establisher);
|
||||
self.establisher_timeout = timeout;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +424,7 @@ impl Drop for ConnectionGuard {
|
||||
/// per-session ops on the fork — the generic channel lifecycle ops
|
||||
/// (`ChannelOperations::register_on`: `channel/close`,
|
||||
/// `channel/control`, `channel/resources/subscribe`), the
|
||||
/// deployment's openable ALPNs (`ChannelCore::register_openable`),
|
||||
/// deployment's openable ALPNs (`ChannelCore::register_openable_with_establisher`),
|
||||
/// the bootstrap discovery set closed over the fork
|
||||
/// (`install_bootstrap_discovery`, so `services/list` sees the
|
||||
/// session's own openables — the F-06 shape), and `op/register`
|
||||
@@ -464,11 +515,13 @@ fn install_channel_zero(
|
||||
if let Some(openables) = openable_alpns.as_ref() {
|
||||
let core = ChannelCore::new(manager, Arc::clone(&policy));
|
||||
for openable in openables.iter() {
|
||||
core.register_openable(
|
||||
core.register_openable_with_establisher(
|
||||
openable.spec.clone(),
|
||||
openable.establisher.clone(),
|
||||
Arc::clone(&openable.open_handler),
|
||||
&fork,
|
||||
auth.clone(),
|
||||
openable.establisher_timeout,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1057,7 +1057,7 @@ use alkcall::channels::operations::OpenHandler;
|
||||
/// payload) chunks; the handler reads the channel's BiStream and
|
||||
/// writes the echo — the exact data-plane shape ADR-067 promises.
|
||||
fn echo_open_handler() -> OpenHandler {
|
||||
Arc::new(move |_input, channel_conn, _auth| {
|
||||
Arc::new(move |_input, _plan, channel_conn, _auth| {
|
||||
tokio::spawn(async move {
|
||||
let mut stream = match channel_conn.accept_bi().await {
|
||||
Ok(s) => s,
|
||||
|
||||
Reference in New Issue
Block a user