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:
1 parent
3b77193e85
commit
68d3723f6a
7 files changed
+1543
-12
No files matched your search
@@ -0,0 +1,730 @@
|
||||
//! 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 (stub at
|
||||
// the seam); declines pty/shell, plain exec, all other opens
|
||||
// (mapping.md §Decline matrix — git row).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[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 — the seam stub
|
||||
/// declines at the family-seam boundary with the serving-path
|
||||
/// shape: failure then close).
|
||||
#[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 route off
|
||||
/// the git row — the skeleton probe's honest surface: they take
|
||||
/// the pipe-row route, not the git row's close-shape).
|
||||
#[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) and serves its rows only to the seam stubs; 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:* ADR-006 point 3 (parse-only: non-git exec
|
||||
/// routes to the session pipe row — the seam stub declines at
|
||||
/// the family-seam boundary: failure then close).
|
||||
#[tokio::test]
|
||||
async fn plain_exec_routes_to_the_pipe_row_stub() {
|
||||
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 pipe row serves its seam stub").await;
|
||||
session.unmount().await;
|
||||
}
|
||||
|
||||
/// - *Contract stamp:* mapping.md §Channel-type rows
|
||||
/// (`session` → `pty_request` + `shell_request` is a `session`
|
||||
/// row; the stub decline is the seam shape).
|
||||
#[tokio::test]
|
||||
async fn pty_and_shell_route_to_their_row_stubs() {
|
||||
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 pty row serves its seam stub").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 shell row serves its seam stub").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 —
|
||||
// every serving path is still a seam stub wave-3 material, and the
|
||||
// decline classes hold.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[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:* mapping.md §Session-channel dispatch
|
||||
/// (non-git exec routes to the session pipe row in the full
|
||||
/// build).
|
||||
#[tokio::test]
|
||||
async fn plain_exec_routes_to_the_pipe_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, "uptime")
|
||||
.await
|
||||
.expect("the exec request rides the wire");
|
||||
expect_failure_then_close(&mut channel, "the pipe row serves its seam stub").await;
|
||||
session.unmount().await;
|
||||
}
|
||||
|
||||
/// - *Contract stamp:* mapping.md §Channel-type rows (the
|
||||
/// pty/shell rows route within the session channel).
|
||||
#[tokio::test]
|
||||
async fn pty_and_shell_route_to_their_stubs_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 pty row serves its seam stub").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 shell row serves its seam stub").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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user