gunnar-ssh 3.0.0

gunnar's SSH server: a russh listener with publickey identity, a strict git exec dispatch, and the repository path sandbox.
Documentation
//! **The disk-headroom gate at the front door, red first (measured, not asserted).**
//!
//! `Dispatch::authorise_service` is the fan-in SSH and smart-HTTP share, and it
//! runs before a single pack byte is read — there is no transport in these
//! tests at all, so a refusal here IS "zero pack bytes read". Each refusal has
//! its allow twin, and the chain-order tests are built so that a gate that
//! always answers `Allow` — the mutation to watch for — turns the refuse
//! half red: the refusal line is asserted to be the DISK's, not merely "some
//! refusal".

mod common;

use std::sync::Arc;

use common::{mint_key, plane_with_key, principal, the_repo};
use gunnar_core::PrincipalKind;
use gunnar_ssh::command::{GitService, Operation};
use gunnar_ssh::server::{Dispatch, DispatchError};
use gunnar_ssh::{Access, DiskFloor, DiskHeadroomGate, PlanAction, PlanGate, PlanVerdict};

/// A floor no filesystem can satisfy.
fn full_disk(root: &std::path::Path) -> Arc<DiskHeadroomGate> {
    Arc::new(DiskHeadroomGate::new(
        root,
        DiskFloor {
            percent: 0,
            bytes: u64::MAX,
        },
    ))
}

/// A floor every filesystem satisfies.
fn roomy_disk(root: &std::path::Path) -> Arc<DiskHeadroomGate> {
    Arc::new(DiskHeadroomGate::new(
        root,
        DiskFloor {
            percent: 0,
            bytes: 1,
        },
    ))
}

/// The metered gate's stand-in: a plan that refuses every write with its own
/// line, so the test can see WHICH link answered.
struct LapsedPlan;

impl PlanGate for LapsedPlan {
    fn verdict(&self, _: &str, action: PlanAction) -> PlanVerdict {
        match action {
            PlanAction::Write => PlanVerdict::Refuse("plan 10gb expired: writes closed".into()),
            PlanAction::AnonymousRead => PlanVerdict::Allow,
        }
    }
    fn pack_quota(&self, _: &str) -> PlanVerdict {
        PlanVerdict::Allow
    }
    fn name(&self) -> &'static str {
        "lapsed-plan"
    }
}

/// The always-allow gate: the invisible mutation, wired on purpose so the
/// tests prove it cannot mask a refusal beside it.
struct OpenGate;

impl PlanGate for OpenGate {
    fn verdict(&self, _: &str, _: PlanAction) -> PlanVerdict {
        PlanVerdict::Allow
    }
    fn pack_quota(&self, _: &str) -> PlanVerdict {
        PlanVerdict::Allow
    }
    fn name(&self) -> &'static str {
        "open"
    }
}

fn push(dispatch: &Dispatch) -> Result<(), DispatchError> {
    dispatch
        .authorise_service(
            &principal("principal-1"),
            None,
            GitService::ReceivePack,
            the_repo(),
        )
        .map(|_| ())
}

#[test]
fn a_floor_above_the_free_space_refuses_the_push_before_any_pack_byte_and_names_the_numbers() {
    let dir = tempfile::tempdir().expect("tempdir");
    let key = mint_key();
    let plane = plane_with_key(&key, Access::Write);
    let gate = full_disk(dir.path());
    let room = gate.headroom().expect("statvfs");
    let dispatch = Dispatch::new(plane.control.with_plan_gate(gate));

    let err = push(&dispatch).expect_err("a full disk must refuse the push");
    let DispatchError::Plan { namespace, line } = &err else {
        panic!("the refusal must be the gate's own arm, not a denial: {err:?}");
    };
    assert_eq!(namespace, "nordisk");
    // The customer's line is plain (wiring tracer B 2026-10-08): the free bytes and the
    // floor are the journal's, not the pusher's.
    assert!(
        line.starts_with(gunnar_ssh::AT_FLOOR)
            && !line.contains(&gunnar_ssh::headroom::human(room.floor)),
        "the line says the storage is full, without numbers: {line}"
    );
    assert_eq!(
        err.client_message().as_deref(),
        Some(line.as_str()),
        "the client is told the whole line — the numbers are what the operator needs"
    );

    // The LFS upload is the other data-growing verb and is refused the same way.
    let lfs = dispatch
        .authorise_service(
            &principal("principal-1"),
            None,
            GitService::LfsTransfer(Operation::Upload),
            the_repo(),
        )
        .expect_err("an LFS upload grows the disk too");
    assert!(matches!(lfs, DispatchError::Plan { .. }), "{lfs:?}");

    // Reads are never held hostage by a full disk.
    dispatch
        .authorise_service(
            &principal("principal-1"),
            None,
            GitService::UploadPack,
            the_repo(),
        )
        .expect("a clone from a full disk still works");
}

#[test]
fn a_floor_below_the_free_space_allows_the_push() {
    let dir = tempfile::tempdir().expect("tempdir");
    let key = mint_key();
    let plane = plane_with_key(&key, Access::Write);
    let dispatch = Dispatch::new(plane.control.with_plan_gate(roomy_disk(dir.path())));
    push(&dispatch).expect("room under the floor is an allowed push");
}

#[test]
fn the_chain_is_asked_in_order_and_the_first_refusal_wins() {
    let dir = tempfile::tempdir().expect("tempdir");
    let key = mint_key();

    // Disk first (the server's order): a full box is told it is full, not
    // that its plan lapsed — and an always-allow gate in front changes nothing.
    let plane = plane_with_key(&key, Access::Write);
    let dispatch = Dispatch::new(
        plane
            .control
            .with_plan_gate(Arc::new(OpenGate))
            .with_plan_gate(full_disk(dir.path()))
            .with_plan_gate(Arc::new(LapsedPlan)),
    );
    let err = push(&dispatch).expect_err("refused");
    let DispatchError::Plan { line, .. } = &err else {
        panic!("{err:?}");
    };
    assert!(
        line.starts_with(gunnar_ssh::AT_FLOOR) && !line.contains("plan 10gb"),
        "the DISK's line must win over the plan's: {line}"
    );
    assert_eq!(
        dispatch
            .control()
            .plan_gates()
            .iter()
            .map(|g| g.name())
            .collect::<Vec<_>>(),
        ["open", "disk-headroom", "lapsed-plan"],
        "the chain is exactly the order it was built in"
    );

    // Plan first: now the plan's line wins. Same gates, different order,
    // different answer — the order is load-bearing and this is its proof.
    let plane = plane_with_key(&key, Access::Write);
    let dispatch = Dispatch::new(
        plane
            .control
            .with_plan_gate(Arc::new(LapsedPlan))
            .with_plan_gate(full_disk(dir.path())),
    );
    let err = push(&dispatch).expect_err("refused");
    let DispatchError::Plan { line, .. } = &err else {
        panic!("{err:?}");
    };
    assert!(
        line.contains("plan 10gb"),
        "the PLAN's line must win here: {line}"
    );

    // A roomy disk in front of a lapsed plan: the plan still refuses. The
    // disk gate allowing is not the chain allowing.
    let plane = plane_with_key(&key, Access::Write);
    let dispatch = Dispatch::new(
        plane
            .control
            .with_plan_gate(roomy_disk(dir.path()))
            .with_plan_gate(Arc::new(LapsedPlan)),
    );
    let err = push(&dispatch).expect_err("the plan still refuses");
    assert!(matches!(&err, DispatchError::Plan { line, .. } if line.contains("plan 10gb")));
}

#[test]
fn the_disk_binds_the_replication_machine_and_a_plan_does_not() {
    let dir = tempfile::tempdir().expect("tempdir");
    let key = mint_key();
    let machine =
        gunnar_core::Principal::new("primary", "primary", PrincipalKind::Machine).expect("legal");
    let build = |gate: Arc<dyn PlanGate>| {
        let plane = plane_with_key(&key, Access::Write);
        plane
            .auth()
            .add_principal(machine.clone())
            .expect("machine");
        plane
            .auth()
            .grant(gunnar_core::Grant::repo(
                "primary",
                the_repo(),
                gunnar_core::Level::Write,
            ))
            .expect("grant");
        Dispatch::new(plane.control.with_plan_gate(gate))
    };
    let attempt = |dispatch: &Dispatch| {
        dispatch
            .authorise_service(&machine, None, GitService::ReceivePack, the_repo())
            .map(|_| ())
    };

    // A lapsed plan does not stop the primary's copy arriving.
    attempt(&build(Arc::new(LapsedPlan))).expect("replication is not a tenant growing data");
    // A full disk does: a standby that cannot hold the bytes must say so first.
    let err = attempt(&build(full_disk(dir.path()))).expect_err("a full standby refuses");
    assert!(
        matches!(&err, DispatchError::Plan { line, .. } if line.starts_with(gunnar_ssh::AT_FLOOR)),
        "{err:?}"
    );
    // And with room it sails through.
    attempt(&build(roomy_disk(dir.path()))).expect("room, so the copy lands");
}

#[test]
fn no_gate_wired_is_every_namespace_free_with_infinite_disk() {
    let key = mint_key();
    let plane = plane_with_key(&key, Access::Write);
    let dispatch = Dispatch::new(plane.control);
    assert!(dispatch.control().plan_gates().is_empty());
    push(&dispatch).expect("a bare Control gates nothing");
}