Files
alkssh/tests/dispatch_core.rs
T
glm-5.3-flash b1e569d1a8 feat(session): land the session mapping — the translation bridge + the exit derivation (w3-tty-session)
The biggest single-row surface of the spine: pty/shell/pipe-exec serve
through the published alktty seam (drive_session_pre_negotiated) over a
translation bridge, re-landing POC-3 stage D as contract:

- src/tty_map.rs (session feature): the bridge (SSH data → Chunk::stdin
  through a bounded mpsc + framing task; SSH EOF → the zero-length
  stdin sentinel — verified against alktty's pump semantics; stdout →
  data; stderr → extended_data(1); exit chunk → the derivation
  contract), the locally-built negotiation (OQ-08: shell argv verbatim,
  wrapper ++ [cmd], cwd/env session-side, ttymodes → the reserved {}
  modes JSON), and the spawned serving path.
- serve_tty_row: driven chunks→SSH inline so the exit report is the
  path's final act (status before close, ADR-006 decision 4); two
  cancel signals (per-channel watch fired by channel_close, connection
  teardown fired by SessionHandler::Drop) abort the alktty session
  task — the ADR-056 kill-on-drop contract is the cancelled session's
  cleanup.
- Derivation contract rules 1–6 in derive_exit + the crate-local
  signum→name table: −N → exit-signal(name), −1 → exit-status(255),
  core_dumped never, unmapped negatives → 255, the PTY-path rule-5
  fidelity limit documented; wire rows pin the shapes.
- Negotiation-error frames (alktty's 0x00-length-prefixed error
  framing) → negotiation-failed serving decline + exit-status(255) +
  close — the one could-not-start claim this door makes (nothing ran).
- Control rows: window_change/signal → the per-channel BridgeSurface
  (bounded mpsc, try_send, never awaited on the packet loop); signal
  names verbatim (no decline shape — fire-and-forget); pre-serve
  resizes update the stored pty params (final size wins).
- Unset-declines (OQ-08) keep the git row's serving-path shape
  (failure then close, spawned): shell-unconfigured /
  wrapper-unconfigured / seam-unconfigured; pipe-less shell SERVES
  pipe-mode (the recorded vanilla-sshd posture); env_request decline
  unchanged (OQ-09).
- tests: the hand-rolled TtyBackend shim + ownership witness doubles;
  serving rows through the harness, the unset-field decline matrix,
  derivation rows on the wire, negotiation-failure rows, the identity
  ride asserted live at the seam, the kill witness; the real-tool
  smoke — OpenSSH -tt against a real PTY via dev-dep alktty local
  (test graph only), skip-with-reason posture. Wave-2 stub rows
  migrated to the new decline reasons (dispatch_core, observe).

Verification: cargo test (113) / --features session (149) / git (142) /
tunnel (115) / --all-features (161) — all green; cargo clippy
--all-targets -- -D warnings ×5 feature configs; cargo fmt --check;
cargo doc --no-deps ×3 builds clean; taskgraph validate.
2026-10-28 20:29:59 +00:00

747 lines
29 KiB
Rust

//! The wave-2 dispatch/decline-core contract tests
//! (`w2-dispatch-decline-core`) — the skeleton rows of the decline
//! matrix (`mapping.md` §Decline matrix), driven per feature build
//! through the loopback harness. This task tests the rows it lands;
//! the full per-build matrix suite is wave-5's.
//!
//! Feature builds are compile-time, so the rows are feature-gated to
//! the build whose matrix row they pin: the shared rows
//! (never-feature `x11`, the `tcpip-forward` listen decline) run in
//! every build; the capability rows run in the builds that open their
//! subject (a session channel needs `session` or `git`); the
//! per-build matrix rows are pinned to exactly one feature combination
//! (`not(...)` guards) so `--all-features` runs neither a
//! git-only row nor a session-only row by accident. Every feature
//! build is exercised by its own verification run
//! (`cargo test [--features …]`), not by cfg tricks.
#![allow(dead_code)]
mod common;
use std::sync::Arc;
use common::harness::{
LoopbackSession, adapter, authenticate_publickey, await_close, drive, next_message,
open_session_channel, open_x11_channel, tcpip_forward,
};
use common::{MemHostKeyPolicy, MemIdentityProvider, fixture, identity};
use russh::client::Msg;
use russh::{Channel, ChannelOpenFailure, Error};
use alkssh::Limits;
/// Mounts an authenticated loopback session over the doubles — the
/// dispatch rows' driver: fixture `alpha` resolves through
/// `MemIdentityProvider`, host keys `bravo`, the permissive host-key
/// posture (the explicit test choice).
async fn authenticated() -> LoopbackSession {
let provider = Arc::new(MemIdentityProvider::new());
provider.register(
fixture("alpha").fingerprint,
identity("dispatch-core", &["ssh"]),
);
let adapter = adapter(provider, &[fixture("bravo")]).await;
let policy = Arc::new(MemHostKeyPolicy::with_default(true));
let mut session = drive(adapter, Arc::clone(&policy), Limits::default())
.await
.expect("the loopback mounts");
let result =
authenticate_publickey(&mut session.client, "dispatch-core", fixture("alpha")).await;
assert!(
result.success(),
"the fixture identity authenticates before every dispatch row"
);
session
}
/// Matches an open failure's SSH reason — the wire decline reason the
/// open-gate rows assert.
fn open_failure(err: Error, expected: ChannelOpenFailure, context: &str) {
assert!(
matches!(
&err,
Error::ChannelOpenFailure(reason) if *reason == expected
),
"{context}: expected open decline with {expected:?}, got {err:?}"
);
}
/// The request-level decline observation: the channel answers Failure.
/// A capability decline's channel stays open afterwards — the POC-1 §5
/// session-survives-declines posture the channel-alive rows lean on.
async fn expect_failure(channel: &mut Channel<Msg>, context: &str) {
match next_message(channel).await {
Some(russh::ChannelMsg::Failure) => {}
other => panic!("{context}: expected a channel failure, got {other:?}"),
}
}
/// The serving-path (family-seam stub) decline observation: Failure
/// then Close — the vanilla-clean shape for a row whose serving path
/// would own the channel.
async fn expect_failure_then_close(channel: &mut Channel<Msg>, context: &str) {
expect_failure(channel, context).await;
assert!(
await_close(channel).await,
"{context}: the seam stub closes the channel after the failure"
);
}
/// Proves a declined channel is still usable — a follow-up request
/// reaches the dispatch again instead of the channel being dead.
async fn expect_channel_alive(channel: &mut Channel<Msg>) {
common::harness::request_subsystem(channel, "sftp")
.await
.expect("the still-open channel carries another request");
expect_failure(channel, "the surviving channel still dispatches").await;
}
// ---------------------------------------------------------------------------
// Shared rows — run in every feature build.
// ---------------------------------------------------------------------------
/// - *Contract stamp:* ADR-003 §5 (x11 is a never-feature — declined
/// in every build, never feature-gated).
#[tokio::test]
async fn x11_channel_opens_decline_in_every_build() {
let session = authenticated().await;
let err = open_x11_channel(&session.client, "127.0.0.1", 0)
.await
.expect_err("x11 opens decline");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"the never-feature x11 open",
);
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Global-request rows (the `-R`
/// listen flow is a `tunnel` row declined when the flow cannot
/// serve — this wave: every build).
#[tokio::test]
async fn tcpip_forward_listen_requests_decline_in_every_build() {
let session = authenticated().await;
let err = tcpip_forward(&session.client, "127.0.0.1", 8080)
.await
.expect_err("the listen request declines");
assert!(
matches!(err, Error::RequestDenied),
"expected the declined listen's RequestDenied, got {err:?}"
);
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Decline matrix (git-build row,
/// request side: `x11` / `auth-agent` requests decline; ADR-003 §5).
#[cfg(any(feature = "git", feature = "session"))]
#[tokio::test]
async fn x11_and_agent_requests_decline_wherever_sessions_open() {
use common::harness::{request_agent_forward, request_x11};
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens where a session-serving feature exists");
request_x11(&channel)
.await
.expect("the x11-req rides the wire");
expect_failure(&mut channel, "the never-feature x11 request declines").await;
request_agent_forward(&channel)
.await
.expect("the agent-forward request rides the wire");
expect_failure(&mut channel, "the never-feature agent request declines").await;
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
// ---------------------------------------------------------------------------
// Default build (no features): everything declines (mapping.md
// §Decline matrix — "none (default): declines everything").
// ---------------------------------------------------------------------------
#[cfg(not(any(feature = "session", feature = "git", feature = "tunnel")))]
mod default_build {
use super::*;
use common::harness::{open_direct_streamlocal, open_direct_tcpip};
/// - *Contract stamp:* mapping.md §Decline matrix (none-build
/// row: every channel type declines at open — session channels
/// as unfeatured, tunnel rows as unfeatured — plus the
/// never-feature x11).
#[tokio::test]
async fn every_channel_open_declines_in_a_default_build() {
let session = authenticated().await;
let err = open_session_channel(&session.client)
.await
.expect_err("session channels decline unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"the unfeatured session open",
);
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
.await
.expect_err("direct-tcpip declines unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"the unfeatured direct-tcpip open",
);
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
.await
.expect_err("direct-streamlocal declines unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"the unfeatured direct-streamlocal open",
);
session.unmount().await;
}
}
// ---------------------------------------------------------------------------
// git-only build: routes git-parseable exec to the git row; declines
// pty/shell, plain exec, all other opens (mapping.md §Decline matrix —
// git row). The harness builds no seam, so the git row's decline is
// the config-driven one (`w3-git-mapping`: seam-unconfigured — the
// unset-declines contract, serving-path shape); the serving rows ride
// `tests/git_mapping.rs`.
// ---------------------------------------------------------------------------
#[cfg(all(feature = "git", not(feature = "session"), not(feature = "tunnel")))]
mod git_build {
use super::*;
use common::harness::{
open_direct_streamlocal, open_direct_tcpip, request_env, request_exec, request_shell,
request_subsystem,
};
/// - *Contract stamp:* the task's channel-open gate (session
/// channels open where a session-serving feature exists — `git`
/// qualifies, the parse path lives inside session channels).
#[tokio::test]
async fn session_channels_open_in_a_git_build() {
let session = authenticated().await;
open_session_channel(&session.client)
.await
.expect("session channels open under the git feature");
session.unmount().await;
}
/// - *Contract stamp:* ADR-006 point 3 (parse-only: a
/// git-parseable exec routes to the git row; this build
/// configures no seam, so the row declines at its config gate —
/// the serving-path shape, failure then close). The row's
/// serving surface (seam configured, real dispatch) is
/// `tests/git_mapping.rs`'s.
#[tokio::test]
async fn git_parseable_exec_routes_to_the_git_row_stub() {
let session = authenticated().await;
for command in [
"git-upload-pack '/owner/demo'",
"git-receive-pack '/owner/demo'",
] {
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, command)
.await
.expect("the exec request rides the wire");
expect_failure_then_close(
&mut channel,
&format!("the git row serves its seam stub for {command}"),
)
.await;
}
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Decline matrix (git-build row:
/// plain exec declines; the parse-then-fail shape of POC-3
/// stage A — the channel survives the decline).
#[tokio::test]
async fn plain_exec_declines_and_keeps_the_channel_in_a_git_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, "uptime")
.await
.expect("the exec request rides the wire");
expect_failure(&mut channel, "the unfeatured pipe row declines").await;
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
/// - *Contract stamp:* POC-3 §2 (unknown git services take the
/// honest decline-with-reason surface — `w3-git-mapping`'s
/// decided class, off the pipe row too; the reply vocabulary is
/// shared with parse-then-fail: failure, channel alive).
#[tokio::test]
async fn unknown_git_services_route_to_the_pipe_row() {
let session = authenticated().await;
for command in ["git-send-pack '/r'", "git-upload-archive '/r'"] {
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, command)
.await
.expect("the exec request rides the wire");
expect_failure(
&mut channel,
&format!("an unknown service does not reach the git row: {command}"),
)
.await;
expect_channel_alive(&mut channel).await;
}
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Decline matrix (git-build row:
/// pty/shell and all session requests decline).
#[tokio::test]
async fn pty_shell_and_capability_requests_decline_in_a_git_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_shell(&channel)
.await
.expect("the shell request rides the wire");
expect_failure(&mut channel, "the shell row declines unfeatured").await;
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Request-level rows (subsystem:
/// `sftp` is the OQ-13 placeholder decline; git service names
/// carry no repository id — ADR-006 decision 2).
#[tokio::test]
async fn subsystems_decline_with_their_named_reasons_in_a_git_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
for name in ["sftp", "git-upload-pack"] {
request_subsystem(&channel, name)
.await
.expect("the subsystem request rides the wire");
expect_failure(
&mut channel,
&format!("the subsystem {name} declines with reason"),
)
.await;
}
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
/// - *Contract stamp:* OQ-09 (mapping.md §Request-level rows:
/// `env_request` declines unconditionally — no allowlist ships).
#[tokio::test]
async fn env_requests_decline_in_a_git_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_env(&channel, "GIT_DIR", "/srv/git")
.await
.expect("the env request rides the wire");
expect_failure(&mut channel, "the env request declines").await;
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Decline matrix (git-build
/// row: all other channel types decline — tunnel unfeatured).
#[tokio::test]
async fn tunnel_opens_decline_unfeatured_in_a_git_build() {
let session = authenticated().await;
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
.await
.expect_err("direct-tcpip declines unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"direct-tcpip",
);
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
.await
.expect_err("direct-streamlocal declines unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"direct-streamlocal",
);
session.unmount().await;
}
}
// ---------------------------------------------------------------------------
// session-only build: declines git-parseable exec (the git parse is
// its gate); with no seam/config this build's session rows decline at
// their config gates (the unset-declines contract, serving-path
// shape: `w3-tty-session`); declines tunnel opens (mapping.md
// §Decline matrix — session row).
// ---------------------------------------------------------------------------
#[cfg(all(feature = "session", not(feature = "git"), not(feature = "tunnel")))]
mod session_build {
use super::*;
use common::harness::{
open_direct_streamlocal, open_direct_tcpip, request_env, request_exec, request_pty,
request_shell, request_subsystem,
};
/// - *Contract stamp:* the task's channel-open gate (session
/// channels open where a session-serving feature exists).
#[tokio::test]
async fn session_channels_open_in_a_session_build() {
let session = authenticated().await;
open_session_channel(&session.client)
.await
.expect("session channels open under the session feature");
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Decline matrix (session row:
/// git-parseable exec declines — the `git` parse is its gate;
/// the channel survives the decline, POC-1 §5).
#[tokio::test]
async fn git_parseable_exec_declines_in_a_session_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, "git-upload-pack '/owner/demo'")
.await
.expect("the exec request rides the wire");
expect_failure(
&mut channel,
"the git parse gates the row off in a session build",
)
.await;
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
/// - *Contract stamp:* OQ-08 + the recorded wire shape
/// (`w3-tty-session`: `exec_wrapper_argv` unset → the pipe row
/// declines — the serving-path shape, failure then close, never
/// a fallback). The row's serving surface (everything
/// configured) is `tests/tty_mapping.rs`'s.
#[tokio::test]
async fn plain_exec_declines_wrapper_unconfigured_in_a_session_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, "uptime")
.await
.expect("the exec request rides the wire");
expect_failure_then_close(&mut channel, "the unset-wrapper pipe row").await;
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Channel-type rows + OQ-08 (the
/// `session` feature alone configures nothing: `session_seam`
/// unset → the pty/shell rows decline `seam-unconfigured`, the
/// serving-path shape this build pins).
#[tokio::test]
async fn pty_and_shell_decline_seam_unconfigured_in_a_session_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_pty(&channel)
.await
.expect("the pty request rides the wire");
expect_failure_then_close(&mut channel, "the unset-seam pty row").await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_shell(&channel)
.await
.expect("the shell request rides the wire");
expect_failure_then_close(&mut channel, "the unset-seam shell row").await;
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Request-level rows (subsystem:
/// the OQ-13 placeholder and git service names decline-with-
/// reason in every session-serving build).
#[tokio::test]
async fn subsystems_decline_with_their_named_reasons_in_a_session_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
for name in ["sftp", "git-upload-pack"] {
request_subsystem(&channel, name)
.await
.expect("the subsystem request rides the wire");
expect_failure(
&mut channel,
&format!("the subsystem {name} declines with reason"),
)
.await;
}
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
/// - *Contract stamp:* OQ-09 (mapping.md §Request-level rows:
/// `env_request` declines unconditionally).
#[tokio::test]
async fn env_requests_decline_in_a_session_build() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_env(&channel, "GIT_DIR", "/srv/git")
.await
.expect("the env request rides the wire");
expect_failure(&mut channel, "the env request declines").await;
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Decline matrix (session row:
/// other channel types decline — tunnel unfeatured).
#[tokio::test]
async fn tunnel_opens_decline_unfeatured_in_a_session_build() {
let session = authenticated().await;
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
.await
.expect_err("direct-tcpip declines unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"direct-tcpip",
);
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
.await
.expect_err("direct-streamlocal declines unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"direct-streamlocal",
);
session.unmount().await;
}
}
// ---------------------------------------------------------------------------
// tunnel-only build: session channels decline (auto-reject); the
// tunnel rows route to the tunnel seam stub even with the feature on
// (the route-1 bridge is wave 3) — mapping.md §Decline matrix (tunnel
// row, as landed this wave).
// ---------------------------------------------------------------------------
#[cfg(all(feature = "tunnel", not(feature = "git"), not(feature = "session")))]
mod tunnel_build {
use super::*;
use common::harness::{open_direct_streamlocal, open_direct_tcpip};
/// - *Contract stamp:* ADR-006 point 3 as amended (parse-only
/// dispatch: a request-less/deserving-less session channel
/// declines as unfeatured — a tunnel-only build has no
/// session-serving feature, so the open declines).
#[tokio::test]
async fn session_channels_decline_in_a_tunnel_build() {
let session = authenticated().await;
let err = open_session_channel(&session.client)
.await
.expect_err("session channels decline unfeatured");
open_failure(
err,
ChannelOpenFailure::AdministrativelyProhibited,
"the unfeatured session open in a tunnel build",
);
session.unmount().await;
}
/// - *Contract stamp:* the task's channel-open gate (featured
/// tunnel rows route to the seam stub; the stub's decline is
/// the explicit `ConnectFailed` reject ADR-006 decision 1 pins
/// for failed fabric opens).
#[tokio::test]
async fn tunnel_rows_route_to_the_seam_stub_in_a_featured_build() {
let session = authenticated().await;
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
.await
.expect_err("the featured direct-tcpip row is a declining seam stub");
open_failure(
err,
ChannelOpenFailure::ConnectFailed,
"the direct-tcpip seam stub",
);
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
.await
.expect_err("the featured direct-streamlocal row is a declining seam stub");
open_failure(
err,
ChannelOpenFailure::ConnectFailed,
"the direct-streamlocal seam stub",
);
session.unmount().await;
}
}
// ---------------------------------------------------------------------------
// All features: the routing decisions hold with the full graph on —
// the git row is still its config-driven decline in this binary (the
// serving rows ride tests/git_mapping.rs), and the session rows are
// still their config-driven declines (the serving rows ride
// tests/tty_mapping.rs).
// ---------------------------------------------------------------------------
#[cfg(all(feature = "git", feature = "session", feature = "tunnel"))]
mod all_features {
use super::*;
use common::harness::{
open_direct_streamlocal, open_direct_tcpip, request_env, request_exec, request_pty,
request_shell, request_subsystem,
};
/// - *Contract stamp:* mapping.md §Session-channel dispatch (the
/// git-parseable exec routes to the git row in the full build).
#[tokio::test]
async fn git_exec_routes_to_the_git_row_stub_with_all_features() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, "git-upload-pack '/owner/demo'")
.await
.expect("the exec request rides the wire");
expect_failure_then_close(&mut channel, "the git row serves its seam stub").await;
session.unmount().await;
}
/// - *Contract stamp:* OQ-08 + the recorded wire shape (the
/// unset-wrapper pipe row declines — the serving-path shape;
/// `w3-tty-session`). The serving surface rides
/// `tests/tty_mapping.rs`.
#[tokio::test]
async fn plain_exec_declines_wrapper_unconfigured_with_all_features() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, "uptime")
.await
.expect("the exec request rides the wire");
expect_failure_then_close(&mut channel, "the unset-wrapper pipe row").await;
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Channel-type rows + OQ-08 (the
/// unset-seam pty/shell declines with the full graph on — the
/// feature alone configures nothing; the serving surface rides
/// `tests/tty_mapping.rs`).
#[tokio::test]
async fn pty_and_shell_decline_seam_unconfigured_with_all_features() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_pty(&channel)
.await
.expect("the pty request rides the wire");
expect_failure_then_close(&mut channel, "the unset-seam pty row").await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_shell(&channel)
.await
.expect("the shell request rides the wire");
expect_failure_then_close(&mut channel, "the unset-seam shell row").await;
session.unmount().await;
}
/// - *Contract stamp:* the task's channel-open gate (the tunnel
/// rows are seam stubs even with the feature on — the route-1
/// bridge is wave 3; `ConnectFailed` is the stub's class).
#[tokio::test]
async fn tunnel_rows_route_to_the_seam_stub_with_all_features() {
let session = authenticated().await;
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
.await
.expect_err("the full-featured direct-tcpip row is a declining seam stub");
open_failure(
err,
ChannelOpenFailure::ConnectFailed,
"the direct-tcpip seam stub",
);
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
.await
.expect_err("the full-featured direct-streamlocal row is a declining seam stub");
open_failure(
err,
ChannelOpenFailure::ConnectFailed,
"the direct-streamlocal seam stub",
);
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Request-level rows (the
/// capability declines — sftp placeholder, git subsystem
/// names, env — hold with everything on).
#[tokio::test]
async fn capability_declines_hold_with_all_features() {
let session = authenticated().await;
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_subsystem(&channel, "sftp")
.await
.expect("the subsystem request rides the wire");
expect_failure(&mut channel, "the sftp placeholder declines").await;
request_subsystem(&channel, "git-upload-pack")
.await
.expect("the subsystem request rides the wire");
expect_failure(&mut channel, "the git subsystem declines").await;
request_env(&channel, "GIT_DIR", "/srv/git")
.await
.expect("the env request rides the wire");
expect_failure(&mut channel, "the env request declines").await;
expect_channel_alive(&mut channel).await;
session.unmount().await;
}
}