feat(dispatch): land the parse-only dispatch + the fail-closed decline core (w2-dispatch-decline-core)

- src/dispatch.rs: the ExecRoute decision, the skeleton exec-parse
  probe (service prefix + path shape; wave 3 replaces with the full
  grammar), the wire-reason shapers (AdministrativelyProhibited for
  unfeatured/never-feature opens, ConnectFailed for the tunnel seam
  stub), 7 probe unit rows
- src/handler.rs: channel_open_* gates per the feature graph (session
  ∨ git; tunnel; x11/auth-agent never), parse-only exec_request
  dispatch before any allocation, session-row routes, request-level
  declines (subsystem all-names with the sftp OQ-13 and git named
  classes, env per OQ-09, x11/agent, the four listen requests), the
  named wave-3 seam stubs; decline-first-alloc-never structural;
  module docs carry the decline-core contract incl. the inherited
  auth-agent-open upstream note
- tests/dispatch_core.rs: 26 Contract-stamped decline-matrix rows per
  feature build driven through the loopback harness (none / git-only /
  session-only / tunnel-only / all-features + shared never-feature
  rows)
- tests/common/harness.rs: extended helper vocabulary (tunnel/x11
  opens, pty/env/x11/agent requests, listen requests) — discharges
  the w2-loopback-harness live-rows note
- tests/loopback_harness.rs: the skeleton decline-only session-open
  row cfg-pinned to the featureless builds it describes

Verification: cargo test (91 rows, default build); per-feature
dispatch_core rows (git 11 / session 10 / tunnel 4 / all 8 / sftp 3);
cargo test --all-features; cargo clippy --all-targets -- -D warnings
(and --all-features); cargo fmt --check; cargo doc --no-deps
This commit is contained in:
glm-5.3-flash committed 2026-10-28 09:04:42 +00:00
1 parent 3b77193e85
commit 68d3723f6a
7 files changed
+1543 -12

No files matched your search

+169
View File
@@ -0,0 +1,169 @@
//! The parse-only dispatch core (`w2-dispatch-decline-core`) — the
//! routing decision and the wire-decline reason shaping the
//! per-connection handler hooks speak ([`crate::handler`]).
//!
//! **Parse-only** (ADR-006 point 3 as amended): an `exec_request`
//! parses *first* — a git-parseable command routes to the git row,
//! anything else routes to the session pipe row; `pty_request` /
//! `shell_request` route to the session rows. Parsing happens before
//! dispatch and before any resource allocation — **decline first,
//! alloc never** (a leaked channel alloc is a capability leak, the
//! POC-3 stage-A rule). There is no request-less serving mode: a
//! session channel that never receives a request is never served (the
//! SOCKS-detect retired with `dynamic-forward`, ADR-003 §1a), so a
//! request-less session channel declines as unfeatured by
//! construction.
//!
//! ## The skeleton exec parse probe
//!
//! [`classify_exec`] is deliberately **skeleton-level** (service
//! prefix + path shape): it recognizes the two serving git services
//! (`git-upload-pack`, `git-receive-pack`) followed by a space and a
//! path-shaped argument (leading `'` — the vanilla-git quoted shape —
//! or `/` — unquoted absolute). Everything else routes to the session
//! pipe row, which in every build that lacks the `session` feature
//! declines: the POC-3 stage-A contract (unknown repo → collapsed
//! generic denial before any protocol byte; non-git command →
//! parse-then-fail). The full grammar (quoted/unquoted paths,
//! per-length limits from [`crate::Limits::max_exec_command_len`],
//! service-only → decline-with-reason, `git-send-pack` /
//! `git-upload-archive` → unknown-service decline) is wave-3's git
//! task, which replaces this probe wholesale — the [`ExecRoute`] rows
//! and the routing decisions around them are what it must keep.
use russh::ChannelOpenFailure;
/// Which dispatch row an `exec_request` command routes to (the
/// parse-only decision, ADR-006 point 3 as amended).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ExecRoute {
/// A git-parseable command — routes to the git row (the `git`
/// feature gate is the row's other half; the parse is the session
/// build's gate — a session-only build declines here).
Git,
/// Everything else — routes to the session pipe row (the
/// `session` feature gate is the row's other half; unfeatured
/// builds decline here).
Pipe,
}
/// The skeleton exec parse probe — see the module docs for the exact
/// shape it recognizes and what wave 3 replaces it with.
pub(crate) fn classify_exec(command: &str) -> ExecRoute {
if is_serving_git_command(command) {
ExecRoute::Git
} else {
ExecRoute::Pipe
}
}
/// The skeleton probe's git recognition: exact serving-service prefix,
/// single separating space, path-shaped argument.
fn is_serving_git_command(command: &str) -> bool {
for service in ["git-upload-pack ", "git-receive-pack "] {
if let Some(argument) = command.strip_prefix(service)
&& is_path_shaped(argument)
{
return true;
}
}
false
}
/// The path shape the skeleton probe accepts: a path-shaped argument
/// starts with `'` (quoted — the vanilla-git wire shape) or `/`
/// (unquoted absolute).
fn is_path_shaped(argument: &str) -> bool {
argument.starts_with('\'') || argument.starts_with('/')
}
/// The wire decline reason for a **featureless** row — a known channel
/// type this build serves nothing of (unfeatured or never-feature):
/// `administratively prohibited`, the vanilla-observable policy
/// refusal (POC-1 §5 — channel-scoped, auth intact, no hangs). A
/// never-feature (`x11`, auth-agent) and an unfeatured feature row
/// share this class honestly: without the feature there is no
/// capability to configure, so nothing distinguishable exists.
pub(crate) fn unfeatured_open_decline() -> ChannelOpenFailure {
ChannelOpenFailure::AdministrativelyProhibited
}
/// The wire decline reason for the **tunnel seam stub** — the row is
/// feature-gated `tunnel` but the route-1 bridge (wave 3) does not
/// exist yet, so the fabric open never happened: `connect failed`, the
/// explicit reject shape ADR-006 decision 1 pins (accept only after
/// the fabric open succeeds; failure maps to an explicit SSH
/// `ConnectFailed`). Wave 3 replaces the stub with the real
/// accept-after-open flow.
pub(crate) fn tunnel_seam_stub_decline() -> ChannelOpenFailure {
ChannelOpenFailure::ConnectFailed
}
#[cfg(test)]
mod tests {
use super::{ExecRoute, classify_exec};
fn route(command: &str) -> bool {
classify_exec(command) == ExecRoute::Git
}
/// - *Contract stamp:* ADR-006 decision 2 (the exec-only git parse
/// — the vanilla wire shape `git-upload-pack '/owner/demo'`).
#[test]
fn vanilla_quoted_upload_pack_parse_is_git() {
assert!(route("git-upload-pack '/owner/demo'"));
}
/// - *Contract stamp:* ADR-006 decision 2 (the receive-pack half
/// of the exec-only git parse).
#[test]
fn receive_pack_parse_is_git() {
assert!(route("git-receive-pack '/owner/demo'"));
}
/// - *Contract stamp:* POC-3 §2 (the unquoted-absolute row of the
/// grammar table: `git-upload-pack /r`).
#[test]
fn unquoted_absolute_pack_parse_is_git() {
assert!(route("git-upload-pack /r"));
}
/// - *Contract stamp:* POC-3 §2 (service-only → declined — SSH
/// exec must carry the repo; the skeleton routes it to the pipe
/// row, which declines in every git-less build; wave 3 gives the
/// decline its own reason).
#[test]
fn service_only_parse_is_not_git() {
assert!(!route("git-upload-pack"));
assert!(!route("git-receive-pack "));
}
/// - *Contract stamp:* POC-3 §2 (unknown service → declined
/// honestly; the skeleton routes it off the git row).
#[test]
fn unknown_services_parse_is_not_git() {
assert!(!route("git-send-pack '/r'"));
assert!(!route("git-upload-archive '/r'"));
}
/// - *Contract stamp:* mapping.md §Session-channel dispatch
/// (non-git → parse-then-fail, the pipe row).
#[test]
fn non_git_commands_parse_to_the_pipe_row() {
assert!(!route("uptime"));
assert!(!route(""));
assert!(!route("git-upload-packrel"));
assert!(!route("git-upload-pack rel"));
assert!(!route("GIT-UPLOAD-PACK '/r'"));
assert!(!route("git-upload-pack$ '/r'"));
assert!(!route("git-upload-pack '/r'"));
}
/// The probe is total: it returns a route for any input, so the
/// dispatch never allocates on a parse failure.
#[test]
fn probe_is_total_over_arbitrary_input() {
assert!(!route("\u{0}\u{1}"));
assert!(route("git-upload-pack '/\u{e9}'"));
}
}
+436 -4
View File
@@ -1,7 +1,7 @@
//! The per-connection server handler — the ADR-004 dispositional auth
//! bridge, with the channel/request dispatch surface still standing at
//! its inherited fail-closed russh defaults (`w2-dispatch-decline-core`
//! fills those; `w2-auth-bridge` filled the auth hooks).
//! bridge, plus the parse-only dispatch and the fail-closed decline
//! core (`w2-dispatch-decline-core`; `w2-auth-bridge` filled the auth
//! hooks).
//!
//! ## The auth bridge (ADR-004, decisions 1–5)
//!
@@ -33,6 +33,76 @@
//! Auth declines are *dispositions* (`Auth::Reject`), not errors — the
//! error model stays key-source-only.
//!
//! ## The parse-only dispatch + the fail-closed decline core
//!
//! (ADR-006 point 3 as amended, ADR-003; the routing decision and the
//! exec-parse skeleton probe live in [`crate::dispatch`].) Every
//! `channel_open_*` hook gates per the feature graph: `session`
//! channels open only where a session-serving feature exists (`session`
//! ∨ `git` — the parse path lives inside session channels);
//! `direct-tcpip` / `forwarded-tcpip` / `direct-streamlocal` gate on
//! the `tunnel` feature; **`x11` always declines** — never
//! feature-gated (ADR-003 §5). Unfeatured and never-feature rows
//! decline **at the open** with a clean wire reason
//! ([`crate::dispatch::unfeatured_open_decline`]) — never a
//! half-open channel; the `tunnel`-featured rows decline at the
//! family-seam stub ([`crate::dispatch::tunnel_seam_stub_decline`])
//! until wave 3's route-1 bridge replaces it. The bare
//! `auth-agent@openssh.com` channel *type*
//! (`forwarded-streamlocal@openssh.com` likewise) has no russh handler
//! hook at this pin — russh rejects those opens inline
//! (administratively prohibited), which is the decline the never-
//! feature contract needs, but it is an inherited posture: a pin bump
//! that changed it would surface here as an accepted-then-dropped
//! channel (third-party-upstream note; file upstream first).
//!
//! The session rows are **parse-only**: `exec_request` parses first
//! (a git-parseable command routes to the git row, anything else to
//! the session pipe row — [`crate::dispatch::classify_exec`]);
//! `pty_request` / `shell_request` route to the session rows. Nothing
//! is served before a request exists — no request, no serving, so a
//! request-less session channel declines as unfeatured by
//! construction. **Decline first, alloc never**: every decline here
//! precedes any channel/resource allocation (a leaked channel alloc
//! is a capability leak — the POC-3 stage-A rule). The channel handle
//! a confirmed `session` open produces is *not* consumed this wave —
//! the store-and-take handoff (`channels.remove(&id)` → `into_stream`
//! in the spawned serving task) is wave-3 mapping material; the
//! routing decisions around it are already this wave's, because the
//! routing stubs decline at the seam.
//!
//! Request-level declines: `subsystem_request` declines every name —
//! `sftp` is the OQ-13 placeholder (the feature exists, the route
//! does not until alkfs lands); git service names carry only a name,
//! never a repository id, so git can never dispatch as a subsystem
//! (ADR-006 decision 2); everything else is unimplemented. `env_request`
//! declines unconditionally (OQ-09 — no allowlist ships: wire env is
//! not family config). `x11_request` and `agent_request` decline
//! (ADR-003 §5). The global listen requests (`tcpip_forward`
//! / `streamlocal_forward` / the cancel forms) decline — they are
//! `tunnel` rows whose serving flow is wave-3 material, so featured
//! builds decline them too this wave.
//!
//! ## Wire decline reasons (the wave-1 error-model reservation)
//!
//! Two decline classes, chosen for vanilla-client observability (POC-1
//! §5: channel-scoped decline, auth intact, no hangs):
//!
//! - **Open-level declines** — channel-open failure codes (`x11`
//! never-feature, unfeatured feature rows → administratively
//! prohibited; the tunnel seam stub → connect failed). These happen
//! before the channel exists, so the SSH open-failure code is the
//! honest, richest reason surface.
//! - **Request-level declines** — [`Session::channel_failure`]: the
//! SSH request-reply vocabulary has no failure text, so a request
//! decline's "reason" is the routing decision itself (documented
//! above and per feature build in `mapping.md`'s decline matrix).
//! A *capability* decline answers failure and leaves the channel
//! usable (the POC-1 §5 session-survives-declines posture); a
//! *serving-path* decline (the wave-3 seam stubs) answers failure
//! and closes the channel — the vanilla-clean shape for a row that
//! will spawn its serving path next wave.
//!
//! ## Test surface
//!
//! The handler type is deliberately constructible outside this module
@@ -48,9 +118,11 @@ use std::sync::Arc;
use alkcall::core::auth::Identity;
use russh::keys::Algorithm;
use russh::server::{Auth, PublicKeyOffer, VerifiedPublicKey};
use russh::server::{Auth, ChannelOpenHandle, Msg, PublicKeyOffer, Session, VerifiedPublicKey};
use russh::{Channel, ChannelId, Pty};
use crate::config::ServeConfig;
use crate::dispatch;
use crate::error::AdapterError;
use crate::fingerprint::fingerprint;
@@ -116,6 +188,33 @@ impl SessionHandler {
.identity_provider
.resolve_from_fingerprint(&fingerprint(public_key))
}
/// The `session` channel-open gate (ADR-003 feature graph):
/// session channels open only where a session-serving feature
/// exists — `session` ∨ `git`, because git's parse path lives
/// inside session channels.
fn serves_session_channels(&self) -> bool {
cfg!(feature = "session") || cfg!(feature = "git")
}
/// The session-row gate (`pty`/`shell`/pipe-exec): only the
/// `session` feature serves these rows.
fn serves_session_rows(&self) -> bool {
cfg!(feature = "session")
}
/// The git-row gate (git-parseable exec): only the `git` feature
/// serves this row.
fn serves_git_row(&self) -> bool {
cfg!(feature = "git")
}
/// The tunnel-row gate (`direct-tcpip` / `forwarded-tcpip` /
/// `direct-streamlocal` opens and the listen flow): only the
/// `tunnel` feature serves these rows.
fn serves_tunnels(&self) -> bool {
cfg!(feature = "tunnel")
}
}
impl russh::server::Handler for SessionHandler {
@@ -186,4 +285,337 @@ impl russh::server::Handler for SessionHandler {
) -> Result<Auth, Self::Error> {
Ok(Auth::reject())
}
/// The session-channel open gate (ADR-003 feature graph,
/// `session` ∨ `git`): unfeatured builds decline at the open —
/// never a half-open channel. A confirmed open accepts the
/// channel; the parse-only dispatch serves it through its
/// requests (nothing serves request-less — ADR-006 as amended).
async fn channel_open_session(
&mut self,
_channel: Channel<Msg>,
reply: ChannelOpenHandle,
_session: &mut Session,
) -> Result<(), Self::Error> {
if self.serves_session_channels() {
reply.accept().await;
} else {
reply.reject(dispatch::unfeatured_open_decline()).await;
}
Ok(())
}
/// The x11 channel open: **always declines** — a never-feature
/// (ADR-003 §5), declined in every build, never feature-gated.
async fn channel_open_x11(
&mut self,
_channel: Channel<Msg>,
_originator_address: &str,
_originator_port: u32,
reply: ChannelOpenHandle,
_session: &mut Session,
) -> Result<(), Self::Error> {
reply.reject(dispatch::unfeatured_open_decline()).await;
Ok(())
}
/// The `direct-tcpip` open gate (`tunnel`): unfeatured declines
/// at the open; featured routes to the tunnel seam stub — the
/// route-1 bridge is wave-3 material, and the stub's decline is
/// the explicit `ConnectFailed` reject shape ADR-006 decision 1
/// pins (accept only after the fabric open succeeds; the skeleton
/// never opens the fabric).
async fn channel_open_direct_tcpip(
&mut self,
_channel: Channel<Msg>,
_host_to_connect: &str,
_port_to_connect: u32,
_originator_address: &str,
_originator_port: u32,
reply: ChannelOpenHandle,
_session: &mut Session,
) -> Result<(), Self::Error> {
if self.serves_tunnels() {
tunnel_row_stub(reply).await;
} else {
reply.reject(dispatch::unfeatured_open_decline()).await;
}
Ok(())
}
/// The `forwarded-tcpip` open gate (`tunnel` — the `-R` data
/// leg): the same gate/stub split as `direct-tcpip`. The
/// harness's russh client cannot initiate this channel type (the
/// server opens it toward the client), so its wire rows are
/// wave-3 acceptor material; the gate itself is straight-line
/// identical to the other tunnel rows.
async fn channel_open_forwarded_tcpip(
&mut self,
_channel: Channel<Msg>,
_host_to_connect: &str,
_port_to_connect: u32,
_originator_address: &str,
_originator_port: u32,
reply: ChannelOpenHandle,
_session: &mut Session,
) -> Result<(), Self::Error> {
if self.serves_tunnels() {
tunnel_row_stub(reply).await;
} else {
reply.reject(dispatch::unfeatured_open_decline()).await;
}
Ok(())
}
/// The `direct-streamlocal` open gate (`tunnel`): the same
/// gate/stub split as the tcp rows. The runtime-Unix gate (OQ-11)
/// gates *serving* on Unix; this wave the stub declines
/// everywhere, so the runtime gate has nothing to ride yet.
async fn channel_open_direct_streamlocal(
&mut self,
_channel: Channel<Msg>,
_socket_path: &str,
reply: ChannelOpenHandle,
_session: &mut Session,
) -> Result<(), Self::Error> {
if self.serves_tunnels() {
tunnel_row_stub(reply).await;
} else {
reply.reject(dispatch::unfeatured_open_decline()).await;
}
Ok(())
}
/// The parse-only exec dispatch (ADR-006 point 3 as amended):
/// parse first ([`crate::dispatch::classify_exec`]) — before any
/// allocation, decline first, alloc never — then route on the
/// *feature graph* the route carries: a git-parseable command to
/// the git row (a session-only build declines here — the git
/// parse is its gate), anything else to the session pipe row.
/// Invalid UTF-8 parses to the pipe row (parse-then-fail).
async fn exec_request(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
let command = std::str::from_utf8(data).unwrap_or("");
match dispatch::classify_exec(command) {
dispatch::ExecRoute::Git => {
if self.serves_git_row() {
git_row_stub(channel, session).await;
} else {
decline_unfeatured_request(channel, session).await;
}
}
dispatch::ExecRoute::Pipe => {
if self.serves_session_rows() {
pipe_row_stub(channel, session).await;
} else {
decline_unfeatured_request(channel, session).await;
}
}
}
Ok(())
}
/// The `pty_request` row route (`session`): featured routes to
/// the session-row seam stub; unfeatured declines the capability.
async fn pty_request(
&mut self,
channel: ChannelId,
_term: &str,
_col_width: u32,
_row_height: u32,
_pix_width: u32,
_pix_height: u32,
_modes: &[(Pty, u32)],
session: &mut Session,
) -> Result<(), Self::Error> {
if self.serves_session_rows() {
pty_row_stub(channel, session).await;
} else {
decline_unfeatured_request(channel, session).await;
}
Ok(())
}
/// The `shell_request` row route (`session`): the same shape as
/// `pty_request`. The OQ-08 unset-shell check is the serving
/// path's consumption (wave 3): this wave the stub declines the
/// row regardless, so the unset-declines posture already holds.
async fn shell_request(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
if self.serves_session_rows() {
shell_row_stub(channel, session).await;
} else {
decline_unfeatured_request(channel, session).await;
}
Ok(())
}
/// Every subsystem name declines (unimplemented on this door):
/// `subsystem("sftp")` is the OQ-13 placeholder — the feature
/// exists, the route does not until alkfs lands; git service
/// names carry only a name, never a repository id, so git can
/// never dispatch as a subsystem (ADR-006 decision 2, POC-3
/// §A.3's forced decline); everything else is unimplemented. Wire
/// shape: `channel_failure`, the channel left usable (the
/// POC-1 §5 session-survives-declines posture).
async fn subsystem_request(
&mut self,
channel: ChannelId,
_name: &str,
session: &mut Session,
) -> Result<(), Self::Error> {
decline_unfeatured_request(channel, session).await;
Ok(())
}
/// `env_request` declines unconditionally (OQ-09: no allowlist
/// ships — wire env is not family config; the assembly supplies
/// the session env through [`crate::ServeConfig::session_env`]).
async fn env_request(
&mut self,
channel: ChannelId,
_variable_name: &str,
_variable_value: &str,
session: &mut Session,
) -> Result<(), Self::Error> {
decline_unfeatured_request(channel, session).await;
Ok(())
}
/// The `x11-req` request on a session channel: always declines
/// (never-feature, ADR-003 §5).
async fn x11_request(
&mut self,
channel: ChannelId,
_single_connection: bool,
_x11_auth_protocol: &str,
_x11_auth_cookie: &str,
_x11_screen_number: u32,
session: &mut Session,
) -> Result<(), Self::Error> {
decline_unfeatured_request(channel, session).await;
Ok(())
}
/// Agent forwarding on a session session-channel: always declines
/// (never-feature, ADR-003 §5 — agent forwarding is the
/// anti-pattern of the alkvault custody model). `false` is the
/// decline; russh replies the channel failure.
async fn agent_request(
&mut self,
_channel: ChannelId,
_session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// The `tcpip-forward` listen request (`-R` ingress): a `tunnel`
/// row whose serving flow is wave-3 material — the gate exists
/// ([`Self::serves_tunnels`]) but even a featured build declines
/// this wave (never advertise a listen that cannot serve). The
/// bound-port `port = 0` picking rule rides the wave-3 flow.
async fn tcpip_forward(
&mut self,
_address: &str,
_port: &mut u32,
_session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// Nothing is ever registered this wave — the cancel form
/// declines accordingly.
async fn cancel_tcpip_forward(
&mut self,
_address: &str,
_port: u32,
_session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// The `streamlocal-forward` listen request: the same decline as
/// `tcpip-forward`, plus its runtime-Unix gate (OQ-11) riding the
/// wave-3 serving flow it will gate.
async fn streamlocal_forward(
&mut self,
_socket_path: &str,
_session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// Nothing is ever registered this wave — the cancel form
/// declines accordingly.
async fn cancel_streamlocal_forward(
&mut self,
_socket_path: &str,
_session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
}
/// The request-level decline shape for an unfeatured/unimplemented
/// capability: fail the request and leave the channel usable (POC-1
/// §5 — session survives declines, no state poisoning).
async fn decline_unfeatured_request(channel: ChannelId, session: &mut Session) {
let _ = session.channel_failure(channel);
}
/// The family-seam decline shape for a **routed-but-not-yet-served**
/// row: fail the request and close the channel — the vanilla-clean
/// shape for a serving path that would have owned the channel (wave 3
/// replaces these stubs with the spawned serving paths; a declined
/// seam never leaks a half-served channel, since the decline IS the
/// row this wave). Decline-first-alloc-never holds structurally:
/// nothing is allocated before the failure lands.
async fn seam_stub_decline(channel: ChannelId, session: &mut Session) {
let _ = session.channel_failure(channel);
let _ = session.close(channel);
}
/// The git row's seam stub — wave 3 lands the alkgit seam here
/// (`parse command → Service + repo → serve_duplex_resolved`), spawned
/// per ADR-006 decision 4; the skeleton declines at the seam.
async fn git_row_stub(channel: ChannelId, session: &mut Session) {
seam_stub_decline(channel, session).await;
}
/// The session pipe row's seam stub — wave 3 lands the alktty pipe
/// seam here (`wrapper ++ [cmd]`, the OQ-08 argv append), spawned per
/// ADR-006 decision 4; the skeleton declines at the seam.
async fn pipe_row_stub(channel: ChannelId, session: &mut Session) {
seam_stub_decline(channel, session).await;
}
/// The session pty row's seam stub — wave 3 lands the alktty
/// `NegotiateRequest.tty` construction here
/// (`drive_session_pre_negotiated`), spawned per ADR-006 decision 4;
/// the skeleton declines at the seam.
async fn pty_row_stub(channel: ChannelId, session: &mut Session) {
seam_stub_decline(channel, session).await;
}
/// The session shell row's seam stub — wave 3 lands the alktty
/// `NegotiateRequest.cmd` = serve-config shell argv here (OQ-08, no
/// crate default), spawned per ADR-006 decision 4; the skeleton
/// declines at the seam.
async fn shell_row_stub(channel: ChannelId, session: &mut Session) {
seam_stub_decline(channel, session).await;
}
/// The tunnel row's seam stub: the explicit `ConnectFailed` reject
/// ([`crate::dispatch::tunnel_seam_stub_decline`]) — wave 3 lands the
/// route-1 bridge here (`TunnelSession::open_direct` → accept only
/// after the fabric open succeeds → `take_halves` pump).
async fn tunnel_row_stub(reply: ChannelOpenHandle) {
reply.reject(dispatch::tunnel_seam_stub_decline()).await;
}
+1
View File
@@ -26,6 +26,7 @@
mod adapter;
mod config;
mod dispatch;
mod error;
mod fingerprint;
mod handler;