docs(arch): remove channels from hub spec — channels are research-phase, not specced
The channels concept (docs/research/alknet-channels/phase-0-findings.md) was developed in a separate session and is research-phase, not architecture. The hub spec was inadvertently built assuming a channel model that doesn't exist yet. Changes: - Remove all channels references from hub README (subtitle, channel model section, channel management bullet, channel proxying section, 'does NOT do' bullet, references) - Remove OQ-55 (channel/open operation) — channels research has its own question tracking (OQ-CH-01 through OQ-CH-07) - Hub spec now describes what it actually provides: peer lifecycle, aggregated operation env, service discovery. One QUIC connection per peer carrying the call protocol. No channels, no future multiplexing, no research references.
This commit is contained in:
3 files changed
+22
-217
No files matched your search
@@ -6,9 +6,8 @@ last_updated: 2026-07-09
|
|||||||
# alknet-hub
|
# alknet-hub
|
||||||
|
|
||||||
The hub pattern as a reusable crate: peer lifecycle management, connection
|
The hub pattern as a reusable crate: peer lifecycle management, connection
|
||||||
supervision, operation aggregation, and channel management across connected
|
supervision, operation aggregation, and service discovery across connected
|
||||||
peers. One QUIC connection per peer; channel 0 carries the call protocol as
|
peers. One QUIC connection per peer carrying the call protocol.
|
||||||
the universal control plane; channels 1..N carry any ALPN as data planes.
|
|
||||||
|
|
||||||
## What
|
## What
|
||||||
|
|
||||||
@@ -18,52 +17,17 @@ not introduce new types — it wires the existing `PeerCompositeEnv`,
|
|||||||
`CallClient`, `CallAdapter`, `from_call`, and `Dispatcher` types into a
|
`CallClient`, `CallAdapter`, `from_call`, and `Dispatcher` types into a
|
||||||
coherent hub runtime.
|
coherent hub runtime.
|
||||||
|
|
||||||
### The channel model
|
|
||||||
|
|
||||||
A hub-worker connection is a single QUIC connection. QUIC multiplexes
|
|
||||||
multiple bidirectional streams over one connection. The hub uses this
|
|
||||||
multiplexing to carry multiple protocols over one connection:
|
|
||||||
|
|
||||||
- **Channel 0 — call protocol (control plane).** Always `alknet/call`. This is
|
|
||||||
the bootstrap channel. It handles operation discovery (`from_call`),
|
|
||||||
operation routing (`invoke_peer`), service discovery (`services/list`,
|
|
||||||
`services/list-peers`), and channel negotiation (`channel/open`,
|
|
||||||
`channel/close`). Every hub-worker connection has channel 0.
|
|
||||||
|
|
||||||
- **Channels 1..N — any ALPN (data planes).** Opened via `channel/open` on
|
|
||||||
the call protocol. Each channel is a bidirectional QUIC stream, wrapped as
|
|
||||||
a `Connection` via `Connection::from_bidi` (ADR-065), and handed to the
|
|
||||||
same `ProtocolHandler::handle(connection, auth)` that handles dedicated
|
|
||||||
connections. The handler does not know it's on a multiplexed channel — it
|
|
||||||
sees a `Connection` and dispatches normally.
|
|
||||||
|
|
||||||
Channel negotiation is symmetric — either side can open a channel. A hub can
|
|
||||||
open a tty channel to a worker (hub wants a terminal on the worker), or a
|
|
||||||
worker can open a tty channel to the hub (worker exposes a terminal to the
|
|
||||||
hub). Same pattern as `from_call`: bidirectional, symmetric, negotiated over
|
|
||||||
the call protocol.
|
|
||||||
|
|
||||||
The hub's role for channels 1..N is **channel management** — tracking which
|
|
||||||
channels are open on which peer, answering `channel/list`, and rejecting
|
|
||||||
duplicate `channel/open` requests. The hub does not interpret the protocol
|
|
||||||
running on the channel and does not proxy streams. What the caller does with
|
|
||||||
the resulting channel is the assembly layer's business.
|
|
||||||
|
|
||||||
### What the hub provides
|
### What the hub provides
|
||||||
|
|
||||||
1. **Peer lifecycle** — dial, accept, disconnect, reconnect with backoff.
|
1. **Peer lifecycle** — dial, accept, disconnect, reconnect with backoff.
|
||||||
Identity resolution via `IdentityProvider`. One connection per peer.
|
Identity resolution via `IdentityProvider`. One connection per peer.
|
||||||
|
|
||||||
2. **Aggregated operation env** — a shared `PeerCompositeEnv` across all
|
2. **Aggregated operation env** — a shared `PeerCompositeEnv` across all
|
||||||
calls (ADR-067). Operations discovered via `from_call` on channel 0 are
|
calls (ADR-067). Operations discovered via `from_call` are registered in
|
||||||
registered in each peer's connection overlay and aggregated into the
|
each peer's connection overlay and aggregated into the shared env.
|
||||||
shared env. `invoke_peer` routes operation calls to the right peer.
|
`invoke_peer` routes operation calls to the right peer.
|
||||||
|
|
||||||
3. **Channel management** — tracking which channels are open on which peer.
|
3. **Service discovery** — `services/list-peers` returns each connected
|
||||||
`channel/open` and `channel/close` operations on the call protocol.
|
|
||||||
Channel tracking for channels 1..N.
|
|
||||||
|
|
||||||
4. **Service discovery** — `services/list-peers` returns each connected
|
|
||||||
worker's operation list via `PeerCompositeEnv::peer_operations` (ADR-068).
|
worker's operation list via `PeerCompositeEnv::peer_operations` (ADR-068).
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
@@ -92,7 +56,7 @@ independently, `alknet-hub` provides it once, as a reusable crate.
|
|||||||
### Hub struct
|
### Hub struct
|
||||||
|
|
||||||
The `Hub` is the central type. It owns the aggregated `PeerCompositeEnv`, the
|
The `Hub` is the central type. It owns the aggregated `PeerCompositeEnv`, the
|
||||||
`OperationRegistry`, the `Dispatcher`, and channel tracking:
|
`OperationRegistry`, and the `Dispatcher`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub struct Hub {
|
pub struct Hub {
|
||||||
@@ -100,13 +64,6 @@ pub struct Hub {
|
|||||||
aggregated_env: Arc<RwLock<PeerCompositeEnv>>,
|
aggregated_env: Arc<RwLock<PeerCompositeEnv>>,
|
||||||
dispatcher: Dispatcher,
|
dispatcher: Dispatcher,
|
||||||
identity_provider: Arc<dyn IdentityProvider>,
|
identity_provider: Arc<dyn IdentityProvider>,
|
||||||
/// Open channels per peer: PeerId → set of channel numbers.
|
|
||||||
/// Channel 0 (call protocol) is tracked implicitly via the
|
|
||||||
/// CallConnection; channels 1..N are tracked here. The hub tracks
|
|
||||||
/// which channels exist so it can answer channel/list and reject
|
|
||||||
/// duplicate channel/open requests. The hub does not hold the
|
|
||||||
/// channel's Connection — the ProtocolHandler owns it.
|
|
||||||
channels: Arc<RwLock<HashMap<PeerId, HashSet<u32>>>>,
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -129,7 +86,6 @@ impl Hub {
|
|||||||
aggregated_env,
|
aggregated_env,
|
||||||
dispatcher,
|
dispatcher,
|
||||||
identity_provider,
|
identity_provider,
|
||||||
channels: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,12 +101,11 @@ impl Hub {
|
|||||||
The `Hub` exposes builder methods for optional hooks (`with_session_source`,
|
The `Hub` exposes builder methods for optional hooks (`with_session_source`,
|
||||||
`with_ownership_provider`, `with_timeout`) that delegate to the `Dispatcher`.
|
`with_ownership_provider`, `with_timeout`) that delegate to the `Dispatcher`.
|
||||||
|
|
||||||
### Peer lifecycle (channel 0 bootstrap)
|
### Peer lifecycle
|
||||||
|
|
||||||
Every hub-worker connection starts with channel 0 — the call protocol. The
|
Every hub-worker connection carries the call protocol. The hub establishes the
|
||||||
hub establishes channel 0, runs `from_call` to discover the peer's operations,
|
connection, runs `from_call` to discover the peer's operations, and attaches
|
||||||
and attaches the peer to the aggregated env. After channel 0 is established,
|
the peer to the aggregated env.
|
||||||
either side can open additional channels via `channel/open`.
|
|
||||||
|
|
||||||
#### Inbound workers (workers dial the hub)
|
#### Inbound workers (workers dial the hub)
|
||||||
|
|
||||||
@@ -163,8 +118,8 @@ registers the bundles, and attaches the peer to the aggregated env:
|
|||||||
/// Callback invoked by CallAdapter::handle when a worker connects inbound.
|
/// Callback invoked by CallAdapter::handle when a worker connects inbound.
|
||||||
/// Fires after identity resolution and before the dispatch loop starts.
|
/// Fires after identity resolution and before the dispatch loop starts.
|
||||||
/// Carries both on_connected (runs from_call, registers discovered ops,
|
/// Carries both on_connected (runs from_call, registers discovered ops,
|
||||||
/// attaches peer to aggregated env) and on_disconnected (detaches peer,
|
/// attaches peer to aggregated env) and on_disconnected (detaches peer
|
||||||
/// drops channel tracking on run_loop exit).
|
/// on run_loop exit).
|
||||||
pub struct WorkerConnectedCallback {
|
pub struct WorkerConnectedCallback {
|
||||||
hub: Arc<Hub>,
|
hub: Arc<Hub>,
|
||||||
config: FromCallConfig,
|
config: FromCallConfig,
|
||||||
@@ -204,10 +159,6 @@ The callback is wired into `CallAdapter` via a new builder method:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl CallAdapter {
|
impl CallAdapter {
|
||||||
/// Register a callback invoked when a worker connects inbound.
|
|
||||||
/// Fires after identity resolution and before the dispatch loop.
|
|
||||||
/// The callback runs from_call, registers discovered operations,
|
|
||||||
/// and attaches the peer to the aggregated env.
|
|
||||||
pub fn with_worker_connected_callback(
|
pub fn with_worker_connected_callback(
|
||||||
mut self,
|
mut self,
|
||||||
callback: WorkerConnectedCallback,
|
callback: WorkerConnectedCallback,
|
||||||
@@ -226,7 +177,7 @@ The `CallAdapter::handle` flow becomes:
|
|||||||
bundles, attaches peer to aggregated env.
|
bundles, attaches peer to aggregated env.
|
||||||
4. Run the dispatch loop (`run_loop`).
|
4. Run the dispatch loop (`run_loop`).
|
||||||
5. On `run_loop` exit, invoke `on_disconnected` on the callback — detaches
|
5. On `run_loop` exit, invoke `on_disconnected` on the callback — detaches
|
||||||
peer from aggregated env, drops channel tracking.
|
peer from aggregated env.
|
||||||
|
|
||||||
The assembly layer constructs the callback and passes it to `CallAdapter`:
|
The assembly layer constructs the callback and passes it to `CallAdapter`:
|
||||||
|
|
||||||
@@ -237,11 +188,10 @@ let adapter = CallAdapter::new(registry, identity_provider)
|
|||||||
.with_worker_connected_callback(callback);
|
.with_worker_connected_callback(callback);
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note on OQ-54**: The callback approach replaces the earlier "explicit
|
> **Note on OQ-54**: The callback fires inside `handle()` because `handle()`
|
||||||
> post-hoc call" design. The callback fires inside `handle()` because
|
> blocks until disconnect — there is no "after handle accepts" point for the
|
||||||
> `handle()` blocks until disconnect — there is no "after handle accepts"
|
> assembly layer to hook into. The callback is the committed design; OQ-54 is
|
||||||
> point for the assembly layer to hook into. The callback is the committed
|
> resolved.
|
||||||
> design; OQ-54 is resolved.
|
|
||||||
|
|
||||||
#### Outbound workers (hub dials workers)
|
#### Outbound workers (hub dials workers)
|
||||||
|
|
||||||
@@ -252,9 +202,9 @@ configurable backoff:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Hub {
|
impl Hub {
|
||||||
/// Dial a worker, establish channel 0 (call protocol), discover its
|
/// Dial a worker, establish the call-protocol connection, discover its
|
||||||
/// operations, and attach it to the aggregated env. Returns the
|
/// operations, and attach it to the aggregated env. Returns the
|
||||||
/// worker's PeerId and the live CallConnection (channel 0).
|
/// worker's PeerId and the live CallConnection.
|
||||||
pub async fn dial_worker(
|
pub async fn dial_worker(
|
||||||
&self,
|
&self,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
@@ -282,17 +232,13 @@ impl Hub {
|
|||||||
Ok((peer_id, connection))
|
Ok((peer_id, connection))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detach a peer from the aggregated env and drop all channel tracking.
|
/// Detach a peer from the aggregated env. Called on disconnect
|
||||||
/// Called on disconnect (both inbound and outbound).
|
/// (both inbound and outbound).
|
||||||
fn on_worker_disconnected(&self, peer_id: &PeerId) {
|
fn on_worker_disconnected(&self, peer_id: &PeerId) {
|
||||||
self.aggregated_env
|
self.aggregated_env
|
||||||
.write()
|
.write()
|
||||||
.expect("aggregated env lock poisoned")
|
.expect("aggregated env lock poisoned")
|
||||||
.detach_peer(peer_id);
|
.detach_peer(peer_id);
|
||||||
self.channels
|
|
||||||
.write()
|
|
||||||
.expect("channels lock poisoned")
|
|
||||||
.remove(peer_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supervise an outbound worker: dial, discover, attach. On disconnect,
|
/// Supervise an outbound worker: dial, discover, attach. On disconnect,
|
||||||
@@ -345,73 +291,6 @@ impl Hub {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Channel negotiation (channels 1..N)
|
|
||||||
|
|
||||||
After channel 0 is established, either side can open additional channels via
|
|
||||||
the call protocol. The hub registers `channel/open`, `channel/close`, and
|
|
||||||
`channel/list` as built-in operations on its curated registry.
|
|
||||||
|
|
||||||
#### channel/open
|
|
||||||
|
|
||||||
Opens a new bidirectional stream on the QUIC connection, wraps it as a
|
|
||||||
`Connection` via `Connection::from_bidi`, and hands it to the
|
|
||||||
`HandlerRegistry` for the requested ALPN. The handler runs on a spawned task;
|
|
||||||
the channel is tracked until closed.
|
|
||||||
|
|
||||||
```
|
|
||||||
Request: { "alpn": "alknet/tty", "channel": 1 }
|
|
||||||
Response: { "channel": 1, "status": "open" }
|
|
||||||
```
|
|
||||||
|
|
||||||
The `channel` field is a hint — the caller suggests a channel number. The
|
|
||||||
receiver may accept it or assign a different number. Channel 0 is reserved
|
|
||||||
for the call protocol.
|
|
||||||
|
|
||||||
The operation is symmetric: either side can call it. When the hub calls
|
|
||||||
`channel/open` on a worker, the worker opens the stream and dispatches to its
|
|
||||||
handler. When a worker calls `channel/open` on the hub, the hub opens the
|
|
||||||
stream and dispatches to its handler.
|
|
||||||
|
|
||||||
#### channel/close
|
|
||||||
|
|
||||||
Closes a previously opened channel. Drops the `Connection`, which cancels the
|
|
||||||
handler task and releases resources.
|
|
||||||
|
|
||||||
```
|
|
||||||
Request: { "channel": 1 }
|
|
||||||
Response: { "channel": 1, "status": "closed" }
|
|
||||||
```
|
|
||||||
|
|
||||||
#### channel/list
|
|
||||||
|
|
||||||
Lists open channels on this connection. Channel 0 (call protocol) is always
|
|
||||||
present and always listed.
|
|
||||||
|
|
||||||
```
|
|
||||||
Response: { "channels": [{ "channel": 0, "alpn": "alknet/call" }, { "channel": 1, "alpn": "alknet/tty" }] }
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Channel usage
|
|
||||||
|
|
||||||
`channel/open` is a call-protocol operation. The hub provides it. What the
|
|
||||||
caller does with the resulting channel is the caller's business — the hub
|
|
||||||
crate does not prescribe a specific proxying pattern.
|
|
||||||
|
|
||||||
The general mechanism: when `channel/open` is invoked, the receiver opens a
|
|
||||||
new QUIC stream on the connection, wraps it as `Connection::from_bidi(stream,
|
|
||||||
alpn, ...)`, and hands it to the `HandlerRegistry` for the requested ALPN.
|
|
||||||
The handler runs on the receiver side. The caller gets back a channel number
|
|
||||||
and can interact with the handler through the call protocol (e.g. by invoking
|
|
||||||
operations that the handler registered, or by opening a reciprocal channel).
|
|
||||||
|
|
||||||
A common pattern (but not the only one): a hub client wants a tty session on
|
|
||||||
a worker. The hub calls `channel/open { alpn: "alknet/tty" }` on the worker.
|
|
||||||
The worker opens the stream, wraps it, and hands it to `TtyAdapter::handle()`.
|
|
||||||
The hub now has a tty session running on the worker. The hub can proxy the
|
|
||||||
raw stream to the client, or expose tty operations through the call protocol,
|
|
||||||
or both. The hub crate provides the `channel/open` mechanism; the assembly
|
|
||||||
layer decides the proxying pattern.
|
|
||||||
|
|
||||||
### Backoff configuration
|
### Backoff configuration
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -460,12 +339,6 @@ pub enum HubError {
|
|||||||
Discovery(#[from] AdapterError),
|
Discovery(#[from] AdapterError),
|
||||||
#[error("call client error: {0}")]
|
#[error("call client error: {0}")]
|
||||||
Client(#[from] ClientError),
|
Client(#[from] ClientError),
|
||||||
#[error("channel {channel} already open on peer {peer}")]
|
|
||||||
ChannelAlreadyOpen { peer: PeerId, channel: u32 },
|
|
||||||
#[error("channel {channel} not found on peer {peer}")]
|
|
||||||
ChannelNotFound { peer: PeerId, channel: u32 },
|
|
||||||
#[error("channel {channel} open failed: {reason}")]
|
|
||||||
ChannelOpenFailed { channel: u32, reason: String },
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -486,10 +359,6 @@ pub enum HubError {
|
|||||||
- **Worker provisioning.** The hub does not spawn workers, configure them, or
|
- **Worker provisioning.** The hub does not spawn workers, configure them, or
|
||||||
manage their lifecycle beyond connection supervision. Worker provisioning
|
manage their lifecycle beyond connection supervision. Worker provisioning
|
||||||
is an assembly-layer concern.
|
is an assembly-layer concern.
|
||||||
- **Protocol interpretation for channels 1..N.** The hub manages channels
|
|
||||||
(tracking, listing, open/close). It does not interpret the protocol
|
|
||||||
running on the channel and does not proxy streams. What the caller does
|
|
||||||
with the resulting channel is the assembly layer's business.
|
|
||||||
|
|
||||||
## Crate dependencies
|
## Crate dependencies
|
||||||
|
|
||||||
@@ -515,9 +384,6 @@ A downstream hub (alkapi) uses `alknet-hub` like this:
|
|||||||
let registry = OperationRegistryBuilder::new()
|
let registry = OperationRegistryBuilder::new()
|
||||||
.with_local(agent_chat_spec(), agent_chat_handler, ...)
|
.with_local(agent_chat_spec(), agent_chat_handler, ...)
|
||||||
.with_local(services_list_spec(), services_list_handler, ...)
|
.with_local(services_list_spec(), services_list_handler, ...)
|
||||||
.with_local(channel_open_spec(), channel_open_handler, ...)
|
|
||||||
.with_local(channel_close_spec(), channel_close_handler, ...)
|
|
||||||
.with_local(channel_list_spec(), channel_list_handler, ...)
|
|
||||||
// ... other curated ops
|
// ... other curated ops
|
||||||
.build();
|
.build();
|
||||||
let registry = Arc::new(registry);
|
let registry = Arc::new(registry);
|
||||||
@@ -559,7 +425,6 @@ so the assembly layer can wire it into `CallAdapter::with_aggregated_env`.
|
|||||||
| from_call is manual | [ADR-069](../../decisions/069-from-call-manual-free-function.md) | `from_call` is a free function; the hub calls it after connect |
|
| from_call is manual | [ADR-069](../../decisions/069-from-call-manual-free-function.md) | `from_call` is a free function; the hub calls it after connect |
|
||||||
| Peer-graph routing model | [ADR-029](../../decisions/029-peer-graph-routing-model.md) | Peer-keyed overlays, `PeerRef` routing, `AccessControl`-based peer auth |
|
| Peer-graph routing model | [ADR-029](../../decisions/029-peer-graph-routing-model.md) | Peer-keyed overlays, `PeerRef` routing, `AccessControl`-based peer auth |
|
||||||
| PeerEntry and Identity.id | [ADR-030](../../decisions/030-peerentry-and-identity-id-decoupling.md) | `PeerId` = `Identity.id` = `PeerEntry.peer_id` (stable) |
|
| PeerEntry and Identity.id | [ADR-030](../../decisions/030-peerentry-and-identity-id-decoupling.md) | `PeerId` = `Identity.id` = `PeerEntry.peer_id` (stable) |
|
||||||
| Connection::from_stream / from_bidi | [ADR-065](../../decisions/065-connection-from-stream-generic-single-stream.md) | Channel streams wrap as `Connection`; any `ProtocolHandler` consumes them unchanged |
|
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
@@ -577,12 +442,6 @@ See [open-questions.md](../../open-questions.md) for full details.
|
|||||||
start. `handle()` blocks until disconnect, so there is no "after handle
|
start. `handle()` blocks until disconnect, so there is no "after handle
|
||||||
accepts" point for the assembly layer to hook into. The
|
accepts" point for the assembly layer to hook into. The
|
||||||
`WorkerConnectedCallback` is the committed design.
|
`WorkerConnectedCallback` is the committed design.
|
||||||
- **OQ-55** (open): `channel/open` operation — this is a new call-protocol
|
|
||||||
operation. It needs a spec in `alknet-call` (the operation shape, the
|
|
||||||
stream-opening mechanism, the handler dispatch). The hub registers it as a
|
|
||||||
built-in operation; the implementation lives in `alknet-call`. The channel
|
|
||||||
model is committed; the operation spec is deferred to the call-protocol
|
|
||||||
implementation phase.
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
@@ -593,7 +452,6 @@ See [open-questions.md](../../open-questions.md) for full details.
|
|||||||
- [operation-registry.md](../call/operation-registry.md) — `OperationRegistry`,
|
- [operation-registry.md](../call/operation-registry.md) — `OperationRegistry`,
|
||||||
`OperationRegistryBuilder`, `OperationEnv`
|
`OperationRegistryBuilder`, `OperationEnv`
|
||||||
- ADR-029: Peer-Graph Routing Model
|
- ADR-029: Peer-Graph Routing Model
|
||||||
- ADR-065: `Connection::from_stream` — Generic Single-Stream Connections
|
|
||||||
- ADR-067: Aggregated Peer-Environment Wiring
|
- ADR-067: Aggregated Peer-Environment Wiring
|
||||||
- ADR-068: PeerCompositeEnv::peer_operations Override
|
- ADR-068: PeerCompositeEnv::peer_operations Override
|
||||||
- ADR-069: from_call Is a Manual Free Function
|
- ADR-069: from_call Is a Manual Free Function
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ Door type is separate from whether a decision is made. A two-way door is a decis
|
|||||||
| [OQ-52](questions/052-callconnection-wait-for-close.md) | CallConnection::wait_for_close() for supervision loop | open | two | med |
|
| [OQ-52](questions/052-callconnection-wait-for-close.md) | CallConnection::wait_for_close() for supervision loop | open | two | med |
|
||||||
| [OQ-53](questions/053-backoff-config-defaults.md) | BackoffConfig default policy | open | two | low |
|
| [OQ-53](questions/053-backoff-config-defaults.md) | BackoffConfig default policy | open | two | low |
|
||||||
| [OQ-54](questions/054-inbound-worker-hook-placement.md) | Inbound worker on_worker_connected hook placement | resolved | two | low |
|
| [OQ-54](questions/054-inbound-worker-hook-placement.md) | Inbound worker on_worker_connected hook placement | resolved | two | low |
|
||||||
| [OQ-55](questions/055-channel-open-operation.md) | channel/open call-protocol operation | open | two | med |
|
|
||||||
|
|
||||||
## Deferred / Blocked
|
## Deferred / Blocked
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
# OQ-55: channel/open Call-Protocol Operation
|
|
||||||
|
|
||||||
- **Origin**: [crates/hub/README.md](crates/hub/README.md)
|
|
||||||
- **Status**: open
|
|
||||||
- **Door type**: Two-way
|
|
||||||
- **Priority**: medium
|
|
||||||
- **Resolution**: Not yet decided.
|
|
||||||
|
|
||||||
The hub's channel model requires a `channel/open` operation on the call
|
|
||||||
protocol. This operation opens a new bidirectional QUIC stream on the
|
|
||||||
existing connection, wraps it as a `Connection` via
|
|
||||||
`Connection::from_bidi` (ADR-065), and hands it to the `HandlerRegistry`
|
|
||||||
for the requested ALPN. The operation is symmetric — either side can call it.
|
|
||||||
|
|
||||||
The operation shape is committed:
|
|
||||||
|
|
||||||
```
|
|
||||||
Request: { "alpn": "alknet/tty", "channel": 1 }
|
|
||||||
Response: { "channel": 1, "status": "open" }
|
|
||||||
```
|
|
||||||
|
|
||||||
What is not yet decided:
|
|
||||||
|
|
||||||
- **Where the operation lives.** The `channel/open` handler needs access to
|
|
||||||
the underlying QUIC connection to open a new stream. This is a
|
|
||||||
`CallConnection` concern — the handler needs a reference to the connection
|
|
||||||
it's running on. Options: (a) the handler is registered with a closure
|
|
||||||
capturing the `CallConnection`, (b) the `OperationContext` carries a
|
|
||||||
reference to the connection, (c) a new `ConnectionHandle` type that the
|
|
||||||
handler can use to open streams.
|
|
||||||
|
|
||||||
- **Channel number assignment.** The caller suggests a channel number; the
|
|
||||||
receiver may accept or reassign. The reassignment policy (first-available,
|
|
||||||
caller-always-wins, receiver-always-wins) is not decided.
|
|
||||||
|
|
||||||
- **Handler dispatch on the receiving side.** When a `channel/open` request
|
|
||||||
arrives, the receiver opens the stream, wraps it as `Connection::from_bidi`,
|
|
||||||
and dispatches to the `HandlerRegistry` for the requested ALPN. The
|
|
||||||
dispatch mechanism (spawn a task, register in the endpoint's accept loop,
|
|
||||||
or a separate channel dispatch path) is not decided.
|
|
||||||
|
|
||||||
- **`channel/close` and `channel/list`.** These are companion operations.
|
|
||||||
`channel/close` drops the `Connection` and cancels the handler task.
|
|
||||||
`channel/list` returns the set of open channels. Their shapes are
|
|
||||||
straightforward; the implementation depends on the `channel/open`
|
|
||||||
mechanism.
|
|
||||||
|
|
||||||
The channel model is committed. The operation spec is deferred to the
|
|
||||||
call-protocol implementation phase — it depends on the `channel/open`
|
|
||||||
mechanism decision.
|
|
||||||
|
|
||||||
- **Cross-references**: ADR-065, [crates/hub/README.md](crates/hub/README.md)
|
|
||||||
Reference in new issue
Block a user