Files
alkssh/tests/git_mapping.rs
T
glm-5.3-flash 3d3bb5e9bf test(mapping): land the channel-not-held guard's wire rows on all three served rows (w4-channel-not-held-rows)
- git row: a second exec on a served git channel answers channel_failure
  inline — the serve rides on, no second serving path; race-free by
  construction (advertisement drained, serve parked at the gated list_refs)
- tty rows: the pipe row (second exec) and the shell row (second shell
  request) — the same inline decline shape, channel still usable
- the rows assert the decline event's exact request vocabulary (class
  request, reason channel-not-held, route git/pipe/absent, debug level,
  self-describing build) — the class's vocabulary coverage
- tests/common/event_capture.rs: the observe-suite's capture convention
  shared across the mapping binaries
- test-only: no production change; the guard held its documented shape
  in every featured build (verified default / git / session /
  session,git / tunnel / sftp / --all-features)

Verified: cargo test (+ per-feature + --all-features) all green ·
cargo clippy --all-targets -- -D warnings ✓ · cargo clippy
--all-features --all-targets -- -D warnings ✓ · cargo fmt --check ✓ ·
cargo doc --no-deps 0 warnings · taskgraph validate ✓ (35 tasks)
2026-10-29 07:27:59 +00:00

1094 lines
42 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The `w3-git-mapping` contract rows — the git branch of the spine
//! served for real: the exec grammar routes to the published alkgit
//! seam over the russh channel duplex (ADR-006 decision 2), the
//! process-mode dialect, the denial byte-order, the exit-status
//! mapping, and the identity ride, all observed through the loopback
//! harness against the hand-rolled in-memory alkgit doubles
//! (`tests/common/git_doubles.rs`). The decline-matrix rows *per
//! feature build* stay in `tests/dispatch_core.rs`; this binary adds
//! the git row's serving and grammar-decline surface (feature-gated —
//! in a non-git build the binary compiles empty, per the crates'
//! compile-time feature isolation).
#![allow(dead_code)]
mod common;
#[cfg(feature = "git")]
mod serving {
use std::sync::Arc;
use russh::client::Msg;
use russh::{Channel, ChannelMsg};
use super::common::event_capture as ec;
use super::common::git_doubles as gd;
use super::common::harness::{
DataGate, LoopbackSession, authenticate_publickey, await_close, drive,
drive_with_data_gate, next_message, open_session_channel, request_exec, request_subsystem,
serve_config as harness_serve_config,
};
use super::common::{
MemHostKeyPolicy, MemIdentityProvider, fixture as fixture_key, identity as ident,
};
use alkssh::{Limits, SshAdapter};
/// Constructs the git build's authenticated loopback session over
/// the doubles: the fixture identity resolves (`alpha` → the
/// grants-key id so alkgit's authorize evaluation sees the ride),
/// the seam carries the recording quartet.
pub(super) async fn git_session(doubles: &Arc<gd::MemGit>) -> LoopbackSession {
let provider = Arc::new(MemIdentityProvider::new());
provider.register(
fixture_key("alpha").fingerprint,
ident("git-rider", &["git"]),
);
let mut serve = harness_serve_config(provider, &[fixture_key("bravo")], Limits::default());
serve.git_seam = Some(gd::git_seam(doubles));
let adapter = SshAdapter::new(serve)
.await
.expect("the git build's adapter constructs");
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, "git-rider", fixture_key("alpha")).await;
assert!(
result.success(),
"the fixture identity authenticates before every git row"
);
session
}
/// Opens a session channel and routes one git exec — the rows'
/// serving driver. Asserts the routed exec's Success reply (the
/// routing decision, on the wire).
pub(super) async fn route_git(session: &LoopbackSession, command: &str) -> Channel<Msg> {
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens in a git build");
request_exec(&channel, command)
.await
.expect("the exec request rides the wire");
let reply = next_message(&mut channel)
.await
.expect("the routed exec answers");
assert!(
matches!(reply, ChannelMsg::Success),
"a routed git exec answers Success, got {reply:?}"
);
channel
}
/// Proves the channel survived a decline — a follow-up request
/// dispatches again (the POC-1 §5 session-survives-declines
/// posture; replicated from `dispatch_core.rs`).
pub(super) async fn still_alive(channel: &mut Channel<Msg>, context: &str) {
request_subsystem(channel, "sftp")
.await
.unwrap_or_else(|e| {
panic!("{context}: the still-open channel carries another request: {e:?}")
});
match next_message(channel).await {
Some(ChannelMsg::Failure) => {}
other => panic!("{context}: expected the surviving-channel failure, got {other:?}"),
}
}
// ------------------------------------------------------------------
// The pkt-line observation surface (test-local framer: channel
// Data messages buffer-decoded one pkt-line at a time).
// ------------------------------------------------------------------
#[derive(Debug)]
pub(super) enum Packet {
Flush,
Payload(Vec<u8>),
}
pub(super) struct PktFramer {
buf: Vec<u8>,
}
impl PktFramer {
pub(super) fn new() -> Self {
Self { buf: Vec::new() }
}
fn take(&mut self) -> Option<Packet> {
if self.buf.len() < 4 {
return None;
}
let hex = std::str::from_utf8(&self.buf[0..4]).ok()?;
let len = usize::from_str_radix(hex, 16).ok()?;
if len == 0 {
self.buf.drain(0..4);
return Some(Packet::Flush);
}
if self.buf.len() < len {
return None;
}
let payload = self.buf[4..len].to_vec();
self.buf.drain(0..len);
Some(Packet::Payload(payload))
}
fn push(&mut self, data: &[u8]) -> Option<Packet> {
self.buf.extend_from_slice(data);
self.take()
}
}
#[derive(Debug)]
pub(super) enum Observed {
Packet(Packet),
ExitStatus(u32),
Close,
Failure,
}
pub(super) async fn next_observed(
channel: &mut Channel<Msg>,
framer: &mut PktFramer,
) -> Option<Observed> {
loop {
if let Some(p) = framer.take() {
return Some(Observed::Packet(p));
}
match next_message(channel).await {
Some(ChannelMsg::Data { data }) => {
if let Some(p) = framer.push(&data) {
return Some(Observed::Packet(p));
}
}
Some(ChannelMsg::ExtendedData { data, .. }) => {
if let Some(p) = framer.push(&data) {
return Some(Observed::Packet(p));
}
}
Some(ChannelMsg::ExitStatus { exit_status }) => {
return Some(Observed::ExitStatus(exit_status));
}
Some(ChannelMsg::Close) => return Some(Observed::Close),
Some(ChannelMsg::Failure) => return Some(Observed::Failure),
Some(_) => continue,
None => return None,
}
}
}
pub(super) async fn next_packet(
channel: &mut Channel<Msg>,
framer: &mut PktFramer,
context: &str,
) -> Packet {
match next_observed(channel, framer).await {
Some(Observed::Packet(p)) => p,
other => panic!("{context}: expected a pkt-line, got {other:?}"),
}
}
/// Drains any buffered pkt-lines until the exit-status arrives;
/// the status-before-close ordering rows lean on the close
/// assertion that follows.
pub(super) async fn expect_exit_status(
channel: &mut Channel<Msg>,
framer: &mut PktFramer,
expected: u32,
context: &str,
) {
loop {
match next_observed(channel, framer).await {
Some(Observed::ExitStatus(code)) => {
assert_eq!(code, expected, "{context}: exit-status code");
return;
}
Some(Observed::Packet(Packet::Flush)) => continue,
other => panic!("{context}: expected exit-status({expected}), got {other:?}"),
}
}
}
pub(super) fn pkt(payload: &[u8]) -> Vec<u8> {
let total = payload.len() + 4;
let mut out = format!("{total:04x}").into_bytes();
out.extend_from_slice(payload);
out
}
pub(super) const FLUSH: &[u8] = b"0000";
const DELIM: &[u8] = b"0001";
/// The oid hex a command/advertisement line carries.
pub(super) fn hex(oid: gix_hash::ObjectId) -> String {
format!("{oid}")
}
#[tokio::test]
async fn advertisement_speaks_first_in_process_mode_byte_level() {
let doubles = Arc::new(gd::MemGit::new());
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![],
));
let session = git_session(&doubles).await;
let mut channel = route_git(&session, "git-upload-pack '/owner/demo'").await;
let mut framer = PktFramer::new();
let first = next_packet(&mut channel, &mut framer, "the first server byte").await;
let Packet::Payload(first_line) = first else {
panic!("the process-mode dialect writes a pkt-line first, not a flush");
};
assert_eq!(
std::str::from_utf8(&first_line).unwrap(),
"version 2\n",
"the advertisement speaks first (alkgit speaks first) and carries \
no '# service=' smart prefix — process mode, byte-level"
);
let mut caps: Vec<String> = Vec::new();
loop {
match next_packet(&mut channel, &mut framer, "the advertisement").await {
Packet::Flush => break,
Packet::Payload(line) => caps.push(String::from_utf8(line).unwrap()),
}
}
assert!(
caps.iter().any(|c| c.trim().starts_with("ls-refs=")),
"honest capability advertisement: {caps:?}"
);
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §the seams (the git row →
/// `serve_duplex_resolved`) + the exit_status row (clean serve
/// end → exit-status(0) **before** close, from the spawned task)
/// + POC-3 §A.5 (vanilla git exits 0 on success). The registry
/// resolves the handed-through id (`owner/demo`) — dispatch
/// fidelity through the doubles.
#[tokio::test]
async fn ls_refs_round_trips_and_clean_serve_ends_exit_status_zero_before_close() {
let doubles = Arc::new(gd::MemGit::new());
let ref_oid = gd::oid(0xaa);
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![gd::RefLine {
name: "refs/heads/main".into(),
oid: Some(ref_oid),
peeled: None,
symref_target: None,
}],
));
let session = git_session(&doubles).await;
let mut channel = route_git(&session, "git-upload-pack '/owner/demo'").await;
let mut framer = PktFramer::new();
let mut saw_advertisement = false;
loop {
match next_packet(&mut channel, &mut framer, "advertisement").await {
Packet::Flush => break,
Packet::Payload(line) => {
assert!(
saw_advertisement || is_version_two(&line),
"the advertisement leads (process mode)"
);
saw_advertisement = true;
}
}
}
let mut request = pkt(b"command=ls-refs\n");
request.extend_from_slice(DELIM);
request.extend_from_slice(pkt(b"ref-prefix refs/heads/\n").as_slice());
request.extend_from_slice(FLUSH);
channel
.data_bytes(request)
.await
.expect("the ls-refs request rides");
let line = match next_packet(&mut channel, &mut framer, "the ref line").await {
Packet::Payload(line) => line,
other => panic!("expected the ref line, got {other:?}"),
};
assert_eq!(
String::from_utf8(line).unwrap().trim(),
&format!("{} refs/heads/main", hex(ref_oid)),
"the double's ref list rides the V2 ls-refs machine"
);
match next_packet(&mut channel, &mut framer, "the ls-refs flush").await {
Packet::Flush => {}
other => panic!("expected the flush, got {other:?}"),
}
assert_eq!(
doubles.resolved_ids(),
vec!["owner/demo".to_owned()],
"alkssh handed the normalized id through; it never resolved anything itself"
);
channel
.data_bytes(FLUSH)
.await
.expect("the trailing flush rides");
expect_exit_status(&mut channel, &mut framer, 0, "the clean serve end").await;
assert!(
await_close(&mut channel).await,
"exit-status(0) lands before the close (the spawned task's report)"
);
session.unmount().await;
}
fn is_version_two(line: &[u8]) -> bool {
std::str::from_utf8(line)
.map(|s| s.trim().starts_with("version 2"))
.unwrap_or(false)
}
/// - *Contract stamp:* POC-3 §A.5 / alkgit ADR-008 N-2 (the
/// collapsed generic denial before any protocol byte — unknown ≡
/// unauthorized, no existence oracle): an unknown repo is *not*
/// an alkssh decline — the seam's denial band is the first byte
/// on the channel, then exit-status(1) per the POC's wire
/// evidence, then close (status before close, spawned task).
#[tokio::test]
async fn unknown_repo_denial_band_precedes_any_protocol_byte_then_exit_status_one() {
let doubles = Arc::new(gd::MemGit::new());
let session = git_session(&doubles).await;
let mut channel = route_git(&session, "git-upload-pack '/owner/absent'").await;
let mut framer = PktFramer::new();
let band = match next_packet(&mut channel, &mut framer, "the denial band").await {
Packet::Payload(payload) => payload,
other => panic!(
"the collapsed denial is the first byte — no pack/ref byte precedes it, got {other:?}"
),
};
assert_eq!(
std::str::from_utf8(&band).unwrap().trim(),
"ERR access denied: owner/absent",
"the generic denial names only the client-sent id"
);
expect_exit_status(&mut channel, &mut framer, 1, "the denied serve").await;
assert!(await_close(&mut channel).await, "status before close");
assert!(
doubles.applied().is_empty() && doubles.generated().is_empty(),
"a denied repo never reached the backends (no existence oracle)"
);
session.unmount().await;
}
/// - *Contract stamp:* the visible-surface invariant (AGENTS.md
/// convention 7) + the identity-ride decision (`w2-auth-bridge`'s
/// parked item, discharged `w3-git-mapping`): the resolved
/// identity is the *live* difference between two
/// otherwise-identical rows — a private repo granted to the
/// riding identity serves, the same repo without the grant
/// denies (alkgit's `authorize` evaluation witnesses the ride
/// through the seam — the authorize-path witness row).
#[tokio::test]
async fn identity_ride_witnessed_by_the_authorize_gate() {
let granted = Arc::new(gd::MemGit::new());
granted.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![],
));
let ok_session = git_session(&granted).await;
let mut channel = route_git(&ok_session, "git-upload-pack '/owner/demo'").await;
let mut framer = PktFramer::new();
loop {
match next_packet(&mut channel, &mut framer, "granted advertisement").await {
Packet::Flush => break,
Packet::Payload(_) => {}
}
}
ok_session.unmount().await;
let ungranted = Arc::new(gd::MemGit::new());
ungranted.install(gd::RepoSetup::private_ungranted("owner/demo", vec![]));
let denied_session = git_session(&ungranted).await;
let mut channel = route_git(&denied_session, "git-upload-pack '/owner/demo'").await;
let mut framer = PktFramer::new();
let band = match next_packet(&mut channel, &mut framer, "the ungranted denial").await {
Packet::Payload(payload) => payload,
other => panic!("the ungranted id denies through the seam, got {other:?}"),
};
assert!(band.starts_with(b"ERR access denied"));
expect_exit_status(&mut channel, &mut framer, 1, "the ungranted denial").await;
assert!(await_close(&mut channel).await);
denied_session.unmount().await;
}
/// - *Contract stamp:* mapping.md §the seams (the git row:
/// `serve_duplex_resolved(repo_id, identity, service = push, …)`
/// — the receive-pack direction) + alkgit ADR-020 §1 (the
/// packless CAS path: commands + flush, no pack) + ADR-013 §6–7
/// (`GitRefs::apply_updates` is the single CAS home — the wire
/// layer applies, the double records).
#[tokio::test]
async fn packless_receive_pack_applies_the_cas_and_reports() {
let doubles = Arc::new(gd::MemGit::new());
let ref_oid = gd::oid(0x5a);
doubles.install(gd::RepoSetup::granted(
"owner/demo",
"git-rider",
vec![alkgit::AccessAction::Write],
vec![gd::RefLine {
name: "refs/heads/main".into(),
oid: Some(ref_oid),
peeled: None,
symref_target: None,
}],
));
let session = git_session(&doubles).await;
let mut channel = route_git(&session, "git-receive-pack '/owner/demo'").await;
let mut framer = PktFramer::new();
let advertisement =
match next_packet(&mut channel, &mut framer, "the V0 advertisement").await {
Packet::Payload(line) => line,
other => panic!("push speaks first (V0 advertisement), got {other:?}"),
};
let text = String::from_utf8(advertisement).unwrap();
assert!(
text.contains("refs/heads/main") && text.contains("report-status"),
"the V0 ref lines ride, caps on the first line: {text:?}"
);
match next_packet(&mut channel, &mut framer, "advertisement flush").await {
Packet::Flush => {}
other => panic!("expected the advertisement flush, got {other:?}"),
}
let zero = "0".repeat(40);
let mut command =
pkt(format!("{} {} refs/heads/main\0report-status\n", hex(ref_oid), zero).as_bytes());
command.extend_from_slice(FLUSH);
channel
.data_bytes(command)
.await
.expect("the delete command rides");
let mut report_rows: Vec<String> = Vec::new();
loop {
match next_packet(&mut channel, &mut framer, "the report").await {
Packet::Flush => break,
Packet::Payload(line) => {
report_rows.push(String::from_utf8(line).unwrap().trim().to_owned())
}
}
}
assert_eq!(report_rows, vec!["unpack ok", "ok refs/heads/main"]);
channel
.eof()
.await
.expect("the client signals the packless-push end-of-stream");
expect_exit_status(&mut channel, &mut framer, 0, "the packless push").await;
assert!(await_close(&mut channel).await);
let applied = doubles.applied();
assert_eq!(applied.len(), 1, "the CAS application is the wire layer's");
let applied = &applied[0];
assert_eq!(applied.repo_id, "owner/demo");
assert_eq!(applied.updates.len(), 1);
assert_eq!(applied.updates[0].name, "refs/heads/main");
assert_eq!(applied.updates[0].expected, Some(ref_oid));
assert_eq!(applied.updates[0].new, None, "the zero-id delete shape");
assert!(
doubles.prepared().is_empty(),
"a packless push never reaches pack ingestion"
);
session.unmount().await;
}
/// - *Contract stamp:* POC-3 §2 (unknown services declined
/// honestly — the decided decline-with-reason surface; the
/// record): request-level failure, alive channel, nothing
/// reaches the seam.
#[tokio::test]
async fn unknown_services_decline_request_level_with_alive_channel() {
let doubles = Arc::new(gd::MemGit::new());
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![],
));
let session = git_session(&doubles).await;
for command in [
"git-send-pack '/owner/demo'",
"git-upload-archive '/owner/demo'",
] {
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, command)
.await
.expect("the exec rides the wire");
let reply = next_message(&mut channel)
.await
.expect("the decline answers");
assert!(
matches!(reply, ChannelMsg::Failure),
"{command}: the grammar declinable answers failure, got {reply:?}"
);
assert!(
doubles.resolved_ids().is_empty() && doubles.applied().is_empty(),
"{command}: a refused service never reaches the seam"
);
still_alive(&mut channel, command).await;
}
session.unmount().await;
}
/// - *Contract stamp:* POC-3 §A (SSH exec must carry the repo) —
/// service-only declines request-level, alive channel.
#[tokio::test]
async fn service_only_declines_request_level_with_alive_channel() {
let doubles = Arc::new(gd::MemGit::new());
let session = git_session(&doubles).await;
for command in ["git-upload-pack", "git-receive-pack "] {
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, command)
.await
.expect("the exec rides the wire");
let reply = next_message(&mut channel)
.await
.expect("the decline answers");
assert!(
matches!(reply, ChannelMsg::Failure),
"{command}: failure reply"
);
still_alive(&mut channel, command).await;
}
session.unmount().await;
}
/// - *Contract stamp:* `alkgit::validate_repo_id`'s grammar as
/// the shape gate (the parse hands the id through; invalid ids
/// decline at the git row, never resolve): charset, segments,
/// and the empty id all decline request-level with an alive
/// channel — the `invalid-repo-id` class.
#[tokio::test]
async fn invalid_repo_ids_decline_before_the_seam() {
let doubles = Arc::new(gd::MemGit::new());
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![],
));
let session = git_session(&doubles).await;
for command in [
"git-upload-pack ''",
"git-upload-pack '/Owner/Demo'",
"git-upload-pack '/a/b/c'",
"git-upload-pack '/.git'",
] {
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
request_exec(&channel, command)
.await
.expect("the exec rides the wire");
let reply = next_message(&mut channel)
.await
.expect("the decline answers");
assert!(
matches!(reply, ChannelMsg::Failure),
"{command}: the shape gate answers failure, got {reply:?}"
);
assert!(
doubles.resolved_ids().is_empty(),
"{command}: a shape-invalid id never reaches the registry"
);
still_alive(&mut channel, command).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 rides the wire");
assert!(matches!(
next_message(&mut channel).await,
Some(ChannelMsg::Success)
));
session.unmount().await;
}
/// - *Contract stamp:* mapping.md §Data-plane/limits +
/// `Limits::max_exec_command_len` (the per-length cap enforced
/// **before** any parse or allocation; decline-first,
/// alloc-never — the wave-2 carried-not-enforced reservation
/// consumed `w3-git-mapping`): an over-length command answers
/// failure, alive channel, nothing parsed.
#[tokio::test]
async fn over_length_commands_decline_before_the_parse() {
let doubles = Arc::new(gd::MemGit::new());
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![],
));
let provider = Arc::new(MemIdentityProvider::new());
provider.register(
fixture_key("alpha").fingerprint,
ident("git-rider-cap", &["git"]),
);
let mut serve = harness_serve_config(
provider,
&[fixture_key("bravo")],
Limits {
max_exec_command_len: 32,
..Limits::default()
},
);
serve.git_seam = Some(gd::git_seam(&doubles));
let adapter = SshAdapter::new(serve)
.await
.expect("the cap build's adapter constructs");
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, "git-rider-cap", fixture_key("alpha"))
.await;
assert!(
result.success(),
"the cap build authenticates before its row"
);
let mut channel = open_session_channel(&session.client)
.await
.expect("the session channel opens");
let long_command = format!("git-upload-pack '/owner/{}'", "x".repeat(48));
request_exec(&channel, &long_command)
.await
.expect("the exec rides the wire");
let reply = next_message(&mut channel).await.expect("the cap answers");
assert!(
matches!(reply, ChannelMsg::Failure),
"the cap answers failure"
);
assert!(
doubles.resolved_ids().is_empty(),
"an over-length command is never parsed nor routed"
);
still_alive(&mut channel, "the over-length command").await;
session.unmount().await;
}
/// - *Contract stamp:* the status-before-close hardening
/// (`w4-git-exit-close` — the wave-3 gate's booked finding 1,
/// the saturated-`Sender<Msg>` tail made real on the wire): the
/// serve's *answer volume* exceeds the whole outbound pipeline
/// (the harness client's packet loop is frozen on the armed
/// data gate → the transport duplex `DUPLEX_CAPACITY` fills →
/// the server's flush pends → russh's bounded message queue —
/// `event_buffer_size`, default 10, pinned below — fills → the
/// serve's remaining answer writes pend). The gate's release
/// then drives the serve tail through the just-unwinding
/// contention: the backlogged answer data drains **through the
/// same queue the report sends ride**, and the exit-status
/// still lands **before** the close — the split-halves
/// construction's property (the report is the only
/// close-contracting sender; sequential awaited sends on the
/// one handle), pinning every wire row of this class against
/// scheduler luck.
#[tokio::test]
async fn backlogged_seam_tail_still_reports_exit_status_before_close() {
assert_eq!(
russh::server::Config::default().event_buffer_size,
10,
"the row's construction pins russh's default message-queue capacity \
(the saturated-tail scenario's depth) — a russh default change here \
re-reads this row's freeze-settle yields, not its assertions"
);
let doubles = Arc::new(gd::MemGit::new());
let ref_oid = gd::oid(0xbb);
let name_padding = "x".repeat(1200);
let flooded_refs: Vec<gd::RefLine> = (0..300)
.map(|n| gd::RefLine {
name: format!("refs/heads/backlog-{n:03}-{name_padding}"),
oid: Some(ref_oid),
peeled: None,
symref_target: None,
})
.collect();
doubles.install(gd::RepoSetup::private_granted(
"owner/backlog",
"git-rider",
flooded_refs.clone(),
));
let serve_gate = doubles.install_gate("owner/backlog");
let provider = Arc::new(MemIdentityProvider::new());
provider.register(
fixture_key("alpha").fingerprint,
ident("git-rider", &["git"]),
);
let mut serve = harness_serve_config(provider, &[fixture_key("bravo")], Limits::default());
serve.git_seam = Some(gd::git_seam(&doubles));
let adapter = SshAdapter::new(serve)
.await
.expect("the backlogged tail's adapter constructs");
let policy = Arc::new(MemHostKeyPolicy::with_default(true));
let gate = Arc::new(DataGate::new());
let mut session = drive_with_data_gate(
adapter,
Arc::clone(&policy),
Limits::default(),
Some(Arc::clone(&gate)),
)
.await
.expect("the gated loopback mounts");
let result =
authenticate_publickey(&mut session.client, "git-rider", fixture_key("alpha")).await;
assert!(result.success(), "the backlogged tail's row authenticates");
let mut channel = route_git(&session, "git-upload-pack '/owner/backlog'").await;
let mut framer = PktFramer::new();
loop {
match next_packet(&mut channel, &mut framer, "backlog advertisement").await {
Packet::Flush => break,
Packet::Payload(_) => {}
}
}
let mut request = pkt(b"command=ls-refs\n");
request.extend_from_slice(DELIM);
request.extend_from_slice(pkt(b"ref-prefix refs/heads/\n").as_slice());
request.extend_from_slice(FLUSH);
channel
.data_bytes(request)
.await
.expect("the ls-refs request rides");
channel
.data_bytes(FLUSH)
.await
.expect("the trailing flush rides");
gate.arm(channel.id());
serve_gate.release();
while gate.held_packets() < 1 {
tokio::task::yield_now().await;
}
for _ in 0..256 {
tokio::task::yield_now().await;
}
gate.release();
let mut seen = 0;
loop {
match next_packet(&mut channel, &mut framer, "the backlogged answer").await {
Packet::Flush => break,
Packet::Payload(line) => {
let expected = format!(
"{} refs/heads/backlog-{seen:03}-{name_padding}",
hex(ref_oid)
);
assert_eq!(
String::from_utf8(line).unwrap().trim(),
expected,
"the backlogged answer's line {seen} rides in order"
);
seen += 1;
}
}
}
assert_eq!(
seen,
flooded_refs.len(),
"the whole 300-line answer drained through the contended queue"
);
expect_exit_status(&mut channel, &mut framer, 0, "the backlogged tail").await;
assert!(
await_close(&mut channel).await,
"exit-status(0) lands before the close through the contended tail"
);
session.unmount().await;
}
// ------------------------------------------------------------------
// The store-and-take handoff, observed: two sequential services on
// two channels of one session — the store is the session task's,
// never poisoned by the taken one.
// ------------------------------------------------------------------
/// - *Contract stamp:* POC-3 §A.4 (the accepted-but-held Channel →
/// `channels.remove(&id)` → `into_stream()` in the spawned task;
/// the session task reuses the store) — the second serve on a
/// fresh channel of the same session rides the same handoff.
#[tokio::test]
async fn store_is_reused_across_sequential_serves() {
let doubles = Arc::new(gd::MemGit::new());
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![],
));
let session = git_session(&doubles).await;
for round in 1..=2 {
let mut channel = route_git(&session, "git-upload-pack '/owner/demo'").await;
let mut framer = PktFramer::new();
loop {
match next_packet(&mut channel, &mut framer, "round advertisement").await {
Packet::Flush => break,
Packet::Payload(_) => {}
}
}
channel
.data_bytes(FLUSH)
.await
.expect("the trailing flush rides");
expect_exit_status(
&mut channel,
&mut framer,
0,
&format!("serve round {round}"),
)
.await;
assert!(await_close(&mut channel).await);
}
session.unmount().await;
}
// ------------------------------------------------------------------
// The channel-not-held guard (`w4-channel-not-held-rows`): a second
// exec on an already-served channel declines request-level — the
// store-and-take handoff holds a channel exactly once, the wave-3
// gate's booked finding 2.
// ------------------------------------------------------------------
/// - *Contract stamp:* mapping.md §Request-level rows (the git
/// row's store-and-take handoff: the `channel-not-held` class —
/// a second exec on an already-served channel answers
/// `channel_failure`, inline, the first serve rides on — no
/// second serving path ever spawns) + the event vocabulary
/// (`src/observe.rs`: class `request`, reason
/// `channel-not-held`, route `git`; the advertisement drains
/// first and the serve parks at the gated `list_refs` behind it,
/// so the wire is quiescent for the decline — race-free by
/// construction).
#[tokio::test]
async fn second_exec_on_a_served_git_channel_declines_channel_not_held() {
ec::install();
let doubles = Arc::new(gd::MemGit::new());
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![],
));
let serve_gate = doubles.install_gate("owner/demo");
let session = git_session(&doubles).await;
let mut channel = route_git(&session, "git-upload-pack '/owner/demo'").await;
let mut framer = PktFramer::new();
loop {
match next_packet(&mut channel, &mut framer, "the advertisement").await {
Packet::Flush => break,
Packet::Payload(_) => {}
}
}
for _ in 0..200 {
if doubles.resolved_ids().len() == 1 {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
assert_eq!(
doubles.resolved_ids(),
vec!["owner/demo".to_owned()],
"the first serve resolved and parks at the gated list_refs behind \
its advertisement — the stored channel is taken, the wire quiescent"
);
request_exec(&channel, "git-upload-pack '/owner/demo'")
.await
.expect("the second exec rides the wire");
match next_message(&mut channel).await {
Some(ChannelMsg::Failure) => {}
other => {
panic!("a second exec on a served channel answers channel_failure, got {other:?}")
}
}
let event = ec::await_event(&[
("row", "exec_request"),
("decline_class", "request"),
("reason", "channel-not-held"),
("exec_route", "git"),
])
.await;
ec::assert_request_decline(&event, true);
assert_eq!(
doubles.resolved_ids().len(),
1,
"the declined re-exec never spawned a second serving path"
);
request_subsystem(&channel, "sftp")
.await
.expect("the alive channel carries another request");
match next_message(&mut channel).await {
Some(ChannelMsg::Failure) => {}
other => panic!("the inline decline leaves the channel usable, got {other:?}"),
}
serve_gate.release();
session.unmount().await;
}
}
#[cfg(unix)]
#[cfg(feature = "git")]
mod real_tool {
//! The real-tool smoke (`w3-git-mapping`): one `git clone` (2.43+)
//! through a TCP-mounted adapter against the hand-rolled doubles —
//! the POC-3 stage-A gate re-landed cheaply. Tooling posture
//! (recorded): the smoke requires `git` ≥ 2.43 and `ssh` in the
//! environment; when absent it is **skipped with a printed reason**
//! (skipped-with-reason at most, never silently green).
use std::process::Command;
use std::sync::Arc;
use alkssh::Limits;
use alkssh::SshAdapter;
use super::common::git_doubles as gd;
use super::common::harness::serve_config as harness_serve_config;
use super::common::{MemIdentityProvider, fixture, identity};
#[tokio::test]
async fn real_git_clone_smoke() {
let (git_ok, git_reason) = git_available();
if !git_ok {
eprintln!("SKIPPED (with reason): real-tool clone smoke — {git_reason}");
return;
}
if Command::new("ssh").arg("-V").output().is_err() {
eprintln!("SKIPPED (with reason): real-tool clone smoke — ssh is unavailable");
return;
}
let work = std::env::temp_dir().join(format!("alkssh-git-smoke-{}", std::process::id()));
std::fs::create_dir_all(&work).expect("the smoke workdir creates");
let key_path = work.join("id_ed25519");
std::fs::write(&key_path, fixture("alpha").pem).expect("the fixture key writes");
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))
.expect("the key file perms set");
let doubles = Arc::new(gd::MemGit::new());
doubles.install(gd::RepoSetup::private_granted(
"owner/demo",
"git-rider",
vec![gd::RefLine {
name: "HEAD".into(),
oid: None,
peeled: None,
symref_target: Some("refs/heads/main".into()),
}],
));
let provider = Arc::new(MemIdentityProvider::new());
provider.register(
fixture("alpha").fingerprint,
identity("git-rider", &["git"]),
);
let mut serve = harness_serve_config(provider, &[fixture("bravo")], Limits::default());
serve.git_seam = Some(gd::git_seam(&doubles));
let adapter = Arc::new(
SshAdapter::new(serve)
.await
.expect("the smoke adapter constructs"),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("the smoke listener binds");
let port = listener.local_addr().expect("local addr").port();
let accept_task = tokio::spawn(async move {
loop {
let Ok((stream, _)) = listener.accept().await else {
return;
};
let adapter = Arc::clone(&adapter);
tokio::spawn(async move {
let _ = adapter.handle(stream).await;
});
}
});
let ssh = format!(
"ssh -i {} -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=10",
key_path.display()
);
let target = work.join("clone");
let outcome = tokio::process::Command::new("git")
.arg("clone")
.arg(format!("ssh://git-rider@127.0.0.1:{port}/owner/demo.git"))
.arg(&target)
.env("GIT_SSH_COMMAND", &ssh)
.output()
.await
.expect("git clone spawns");
accept_task.abort();
let stderr = String::from_utf8_lossy(&outcome.stderr).to_string();
assert!(
outcome.status.success(),
"vanilla git clone (2.43) through the door succeeds — stderr: {stderr}"
);
assert!(
target.join(".git").is_dir(),
"the clone materializes a repository (the empty-repo clone: unborn HEAD)"
);
assert!(
stderr.contains("empty repository"),
"the unborn-HEAD advertisement is what the clone consumed — stderr: {stderr}"
);
assert!(
doubles.resolved_ids().iter().any(|id| id == "owner/demo"),
"the resolved id is the normalized one (the .git suffix rode off)"
);
let _ = std::fs::remove_dir_all(&work);
}
fn git_available() -> (bool, String) {
let output = Command::new("git").arg("--version").output();
let Ok(output) = output else {
return (false, "git is unavailable".to_owned());
};
let text = String::from_utf8_lossy(&output.stdout).to_string();
let version = text
.split_whitespace()
.last()
.unwrap_or_default()
.split('.')
.filter_map(|c| c.parse::<u32>().ok())
.collect::<Vec<u32>>();
if version.first() == Some(&2) && version.get(1).is_some_and(|minor| *minor >= 43) {
(true, text.trim().to_owned())
} else {
(false, format!("git {text:?} is older than 2.43.0"))
}
}
}