Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/storage-git-gix/parked-gix-2/gunnar-wire-gix/tests/serve_wiring.rs

#![cfg(feature = "server")]

//! The two refusals and the one default that `gunnar serve` is responsible for,
//! plus proof that the gRPC control plane is wired to `gunnar-core` and not to
//! `gunnar-grpc`'s in-memory test double.
//!
//! Each is asserted in **both** directions. "The gRPC bind is loopback" passes
//! against a predicate that returns true for everything, so the SSH bind (which
//! is deliberately public) is asserted not to be. "An empty key table refuses"
//! passes against a server that refuses everything, so the same server with an
//! admin key is asserted to start.

use std::sync::Arc;

use gunnar_cli::server::{ServeConfig, Server, ServerStores};
use gunnar_core::{NewRepo, RepoRef};

fn runtime() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .expect("a tokio runtime")
}

// ── D9: the gRPC listener defaults to loopback ──────────────────────────────

/// Goes red if the default moves off loopback.
///
/// The control plane authenticates (`AuthMode::Required` is the default), so
/// this is defence in depth rather than the only lock — but D9's escape hatch
/// `insecure_allow_all_for_local_development` still exists, and a loopback
/// default is what keeps *that* hole local rather than remote. It costs
/// nothing.
///
/// The sentence here used to read "gRPC is unauthenticated in v1 (D9): anyone
/// who can reach the port is an admin of the control plane". That stopped
/// being true and nothing caught it; see
/// `the_started_server_reports_the_auth_mode_it_actually_serves` below, which
/// is the guard for the same staleness on the startup banner.
#[test]
fn the_grpc_default_bind_is_loopback() {
    let config = ServeConfig::default();
    assert_eq!(
        config.grpc_bind,
        gunnar_grpc::DEFAULT_BIND,
        "gunnar-cli must take gunnar-grpc's default rather than inventing one, or the two \
         defaults can drift apart"
    );
    assert_eq!(config.grpc_bind.to_string(), "127.0.0.1:50051");
    assert!(
        config.grpc_bind.ip().is_loopback(),
        "the unauthenticated control plane must not default to a routable address"
    );

    // The identity-value guard. `is_loopback()` must be capable of answering
    // false on a bind gunnar actually uses, or the assertion above proves
    // nothing about the predicate.
    assert!(
        !config.ssh_bind.ip().is_loopback(),
        "SSH authenticates every connection by public key and is meant to bind publicly; if \
         this is loopback too then the loopback assertion above is vacuous"
    );
}

// ── §5.1, as it now stands: an empty table STARTS, with the door open ───────

/// **What the §5.1 refusal became, and why it had to.**
///
/// Until 2026-08-05 this exact fixture asserted `Server::start` returned an
/// error containing *"refusing to serve"* and *"--admin-key"*. That refusal is
/// gone: it meant a freshly built gunnar could not be reached to be given its
/// first key, so there was no first boot at all, and the only way in was
/// `gunnar key add` with the server DOWN — which needs a shell the appliance
/// does not have.
///
/// The server now starts and announces an OPEN unauthenticated enrolment
/// window. `tests/bootstrap.rs` is where that window's own refusals are
/// asserted; this test's job is the one thing `serve_wiring` owns: that
/// `Server::start` **branches on the readiness value** and says which state it
/// is in, rather than starting silently.
#[test]
fn an_empty_key_table_starts_with_the_enrolment_window_open_and_says_so() {
    let dir = tempfile::tempdir().expect("temp dir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("open the stores"));
    assert!(
        stores.auth().is_empty().expect("read the table"),
        "the fixture must start with an empty identity table or it proves nothing"
    );
    assert_eq!(
        stores.auth().prepare_to_serve(None).expect("decide"),
        gunnar_core::Readiness::AwaitingBootstrap
    );

    let rt = runtime();
    let server = rt
        .block_on(Server::start(
            Arc::clone(&stores),
            ServeConfig {
                no_grpc: true,
                ..ServeConfig::ephemeral()
            },
        ))
        .expect("an empty identity table must START now; refusing to start was the defect");
    assert!(
        server.bootstrap_window_open(),
        "…and it must know, and be able to tell its own banner, that the door is open"
    );
    rt.block_on(server.shutdown()).expect("stop");

    // The sentence an operator reads, in every branch, driven directly so each
    // one can be seen on a box that is only ever in one of them.
    let bind = "127.0.0.1:50051".parse().expect("an address");
    let open = gunnar_cli::server::serve::announce_readiness(
        &gunnar_core::Readiness::AwaitingBootstrap,
        dir.path(),
        bind,
        false,
    );
    assert!(
        open.contains("NO IDENTITY HAS EVER BEEN ENROLLED"),
        "{open}"
    );
    assert!(
        open.contains(&dir.path().display().to_string()),
        "it must name the data path, or an operator cannot tell WHICH server: {open}"
    );

    // …and with no control plane there is no way to enrol at all, which is a
    // different and worse state that must not read the same.
    let stranded = gunnar_cli::server::serve::announce_readiness(
        &gunnar_core::Readiness::AwaitingBootstrap,
        dir.path(),
        bind,
        true,
    );
    assert!(stranded.contains("no way to enrol"), "{stranded}");

    let ready = gunnar_cli::server::serve::announce_readiness(
        &gunnar_core::Readiness::Ready,
        dir.path(),
        bind,
        false,
    );
    assert!(
        !ready.contains("NO IDENTITY"),
        "an enrolled server must not shout the open-door line: {ready}"
    );
}

/// The green half: the same empty table with an admin key starts, installs the
/// bootstrap admin, and gives it a **server-scoped** grant, without which it
/// could not create the first namespace and the server would be reachable and
/// useless (§5.1).
#[test]
fn an_admin_key_bootstraps_an_empty_table_and_serves() {
    let dir = tempfile::tempdir().expect("temp dir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("open the stores"));

    let private = gunnar_core::mint_ed25519().expect("mint an admin key");
    let public = gunnar_core::ssh_key::PrivateKey::from_openssh(&private)
        .expect("parse")
        .public_key()
        .to_openssh()
        .expect("render");

    let rt = runtime();
    let server = rt
        .block_on(Server::start(
            Arc::clone(&stores),
            ServeConfig {
                no_grpc: true,
                admin_key: Some(public.clone()),
                ..ServeConfig::ephemeral()
            },
        ))
        .expect("an admin key must let the same server start");

    assert!(!stores.auth().is_empty().expect("read the table"));
    let grants = stores.auth().grants(None).expect("read the grants");
    assert!(
        grants
            .iter()
            .any(|g| g.scope == gunnar_core::Scope::Server && g.level == gunnar_core::Level::Admin),
        "the bootstrap admin needs a Scope::Server grant to create the first namespace; got \
         {grants:?}"
    );
    rt.block_on(server.shutdown()).expect("shut down");
}

/// The SSH host key is minted once and reused, so clients do not see a
/// host-key-changed warning after a restart (§5.1).
#[test]
fn the_host_key_survives_a_restart() {
    let dir = tempfile::tempdir().expect("temp dir");
    let private = gunnar_core::mint_ed25519().expect("mint");
    let admin = gunnar_core::ssh_key::PrivateKey::from_openssh(&private)
        .expect("parse")
        .public_key()
        .to_openssh()
        .expect("render");
    let rt = runtime();

    let first = {
        let stores = Arc::new(ServerStores::open(dir.path()).expect("open"));
        let key = gunnar_core::ensure_host_key(&stores.secrets()).expect("host key");
        let server = rt
            .block_on(Server::start(
                Arc::clone(&stores),
                ServeConfig {
                    no_grpc: true,
                    admin_key: Some(admin.clone()),
                    ..ServeConfig::ephemeral()
                },
            ))
            .expect("start");
        rt.block_on(server.shutdown()).expect("shut down");
        key.fingerprint().to_string()
    };

    // A second ServerStores on the same directory: only possible because the
    // first was dropped and released its store lock, which is itself worth
    // knowing works.
    let stores = Arc::new(ServerStores::open(dir.path()).expect("reopen"));
    let second = gunnar_core::ensure_host_key(&stores.secrets())
        .expect("host key")
        .fingerprint()
        .to_string();
    assert_eq!(
        first, second,
        "the host key was regenerated across a restart"
    );

    // The red half: a *fresh* data root must produce a different key, or the
    // assertion above would pass on a constant.
    let other = tempfile::tempdir().expect("temp dir");
    let elsewhere = ServerStores::open(other.path()).expect("open");
    let third = gunnar_core::ensure_host_key(&elsewhere.secrets())
        .expect("host key")
        .fingerprint()
        .to_string();
    assert_ne!(
        first, third,
        "two independent servers minted the same host key; the stability assertion above is \
         meaningless"
    );
}

// ── the gRPC control plane is the real one ──────────────────────────────────

/// `Admin.Health` and `Repos.List` answered from `gunnar-core`, over a real
/// tonic connection.
///
/// The repository is created through the catalog *before* the server starts and
/// is then asked for over gRPC, so a `MemoryControlPlane` (which starts empty)
/// could not produce this answer.
#[test]
fn the_grpc_control_plane_answers_from_gunnar_core() {
    let dir = tempfile::tempdir().expect("temp dir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("open"));
    stores.catalog().create_namespace("team").expect("ns");
    stores
        .catalog()
        .create(NewRepo::new(RepoRef::new("team", "project").unwrap()))
        .expect("repo");

    let private = gunnar_core::mint_ed25519().expect("mint");
    let admin_key = gunnar_core::ssh_key::PrivateKey::from_openssh(&private).expect("parse");
    let admin = admin_key.public_key().to_openssh().expect("render");

    // gRPC authenticates against the SAME key table as SSH (D9 closed): the
    // bootstrap admin key registered above is the credential below, and there
    // is no second secret anywhere in this test.

    let rt = runtime();
    let server = rt
        .block_on(Server::start(
            Arc::clone(&stores),
            ServeConfig {
                admin_key: Some(admin),
                ..ServeConfig::ephemeral()
            },
        ))
        .expect("start");
    let addr = server.grpc_addr().expect("the gRPC listener is bound");
    assert!(
        addr.ip().is_loopback(),
        "an ephemeral test bind must still be loopback"
    );

    let endpoint = format!("http://{addr}");
    rt.block_on(async {
        // `connect` is not generated on these clients (gunnar-proto builds
        // tonic without the channel feature), so the channel is built here.
        let channel = tonic::transport::Channel::from_shared(endpoint)
            .expect("a valid endpoint")
            .connect()
            .await
            .expect("connect to the gRPC control plane");
        let mut admin_client = gunnar_proto::admin_client::AdminClient::new(channel.clone());

        // ── D9 is closed: the same call with no credential must be refused,
        //    over the real socket, through the real router. This is the proof
        //    that `gunnar_grpc::router` actually MOUNTS the gate: a gate that
        //    exists and is not wired in is the exact shape of a silent green.
        let refused = admin_client
            .health(gunnar_proto::HealthRequest {})
            .await
            .expect_err("an unauthenticated Admin.Health must be refused");
        assert_eq!(
            refused.code(),
            tonic::Code::Unauthenticated,
            "the gRPC control plane served an unauthenticated caller: {refused:?}"
        );

        let health = admin_client
            .health(authed_with(&admin_key, gunnar_proto::HealthRequest {}))
            .await
            .expect("Admin.Health")
            .into_inner();
        assert!(
            health.serving,
            "the control plane reports itself not serving"
        );
        assert!(
            health.grpc_authentication_enabled,
            "D9 is closed and this listener requires a credential — the refusal above proves \
             it — so Admin.Health must SAY SO on the wire. A `false` here would be a lie an \
             operator would act on."
        );

        let stats = admin_client
            .stats(authed_with(&admin_key, gunnar_proto::StatsRequest {}))
            .await
            .expect("Admin.Stats")
            .into_inner();
        assert_eq!(
            stats.repos, 1,
            "Admin.Stats is not counting the real catalog"
        );
        assert_eq!(stats.namespaces, 1);

        let mut repos = gunnar_proto::repos_client::ReposClient::new(channel);
        let listed = repos
            .list(authed_with(
                &admin_key,
                gunnar_proto::ListReposRequest::default(),
            ))
            .await
            .expect("Repos.List")
            .into_inner();
        let names: Vec<String> = listed
            .repos
            .iter()
            .filter_map(|r| r.path.as_ref())
            .map(|p| p.to_string())
            .collect();
        assert_eq!(
            names,
            vec!["team/project".to_string()],
            "Repos.List did not return the repository the catalog holds — the seam is not \
             wired to gunnar-core"
        );
    });

    rt.block_on(server.shutdown()).expect("shut down");
}

/// `gunnar gc` drives `gunnar-store`'s `repack()` (S-006).
///
/// This test used to assert the opposite: that the verb refused, because
/// `repack()` did not exist. It is kept, inverted, rather than deleted: the
/// thing worth guarding is that `gc` reaches the store at all, and the way to
/// prove that is to hand it a repository with **several packs** and assert they
/// became one. `repack()`'s own behaviour (the atomic swap, the crash, the
/// byte-identical objects) is proven in `gunnar-store`'s `tests/repack.rs`
/// against the store directly; what can only be proven here is the wiring.
#[test]
fn gc_repacks_every_repository_it_lists() {
    let dir = tempfile::tempdir().expect("temp dir");
    let stores = ServerStores::open(dir.path()).expect("open");
    stores.catalog().create_namespace("team").expect("ns");
    let repo = stores
        .catalog()
        .create(NewRepo::new(RepoRef::new("team", "project").unwrap()))
        .expect("repo");

    // Two packs and a loose object, put there the way receive-pack does.
    let store = stores.repo_store(&repo).expect("the repository on disk");
    let hash = store.hash_kind();
    let pack_dir = store.git_dir().join("objects").join("pack");
    let mut written = Vec::new();
    for round in 0..2 {
        let id = gunnar_store::ObjectStore::write(
            &store.objects(),
            gunnar_store::gix_object::Kind::Blob,
            format!("gc round {round}\n").as_bytes(),
        )
        .expect("write a blob");
        let (bytes, _) = gunnar_pack::write_pack_to_vec(
            &[id],
            &store.objects(),
            &gunnar_pack::WriteOptions::new(hash),
        )
        .expect("write a pack");
        gunnar_pack::index_pack(
            &mut std::io::Cursor::new(&bytes),
            Some(&pack_dir),
            &gunnar_pack::IndexOptions::new(hash),
            None,
        )
        .expect("index the pack");
        written.push(id);
    }
    let count_packs = || {
        std::fs::read_dir(&pack_dir)
            .expect("the pack directory")
            .flatten()
            .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("pack"))
            .count()
    };
    assert_eq!(
        count_packs(),
        2,
        "the fixture did not produce two packs, so folding them proves nothing"
    );

    gunnar_cli::server::admin::gc(
        &stores,
        gunnar_cli::cli::GcArgs {
            path: None,
            ..Default::default()
        },
    )
    .expect("gc repacks rather than refusing");

    assert_eq!(
        count_packs(),
        1,
        "gc left the packs alone — the verb is not reaching repack()"
    );
    for id in &written {
        assert!(
            gunnar_store::ObjectStore::contains(&store.objects(), id),
            "gc lost object {id}"
        );
    }
}

/// **`gunnar gc --prune` reclaims; `gunnar gc` never has.**
///
/// `RepackOptions::prune_unreachable` was implemented, tested against the store
/// in `gunnar-store/tests/repack.rs`, and reachable from nothing: no flag on
/// `gunnar gc`, no caller anywhere in the tree. So nothing gunnar could do ever
/// reclaimed the objects of a rejected push, a deleted branch or a
/// force-pushed-away history. They stayed on disk forever, and an operator who
/// ran `gc` and watched the repository not shrink had no way to find out why.
///
/// **Three** arms on the same fixture, because the claim is a DIFFERENCE and
/// there are two of them. The blobs nothing references survive an ordinary `gc`
/// (which `gc_repacks_every_repository_it_lists` asserts directly, from the
/// other side); they survive `gc --prune` too, because they were written a
/// moment ago and the default grace is two weeks; and they go under
/// `--prune-grace now`.
///
/// Seen red, twice: with `.prune_unreachable(args.prune)` removed from
/// `admin::gc` this fails with *"gc --prune kept <oid>"*, the state the verb
/// shipped in; with `.prune_grace(args.prune_grace)` removed it fails on the
/// middle arm instead, which is the state `gunnar gc --prune` shipped in on
/// 2026-08-05 and which destroys a concurrent push.
#[test]
fn gc_prune_drops_what_no_reference_can_reach_and_gc_alone_keeps_it() {
    let dir = tempfile::tempdir().expect("temp dir");
    let stores = ServerStores::open(dir.path()).expect("open");
    stores.catalog().create_namespace("team").expect("ns");
    let repo = stores
        .catalog()
        .create(NewRepo::new(RepoRef::new("team", "project").unwrap()))
        .expect("repo");
    let store = stores.repo_store(&repo).expect("the repository on disk");
    let hash = store.hash_kind();
    let pack_dir = store.git_dir().join("objects").join("pack");

    // ONE reachable blob behind a reference, so the store has something to
    // keep. Without it every object in the repository is unreachable, and
    // `repack()` correctly REFUSES to empty a store — which is a guard, not a
    // prune, and a test that hit it would be measuring the wrong thing. This
    // test hit it on the first run.
    let kept = gunnar_store::ObjectStore::write(
        &store.objects(),
        gunnar_store::gix_object::Kind::Blob,
        b"referenced\n",
    )
    .expect("write the reachable blob");
    gunnar_store::RefStore::transaction(
        &store.refs(),
        vec![gunnar_store::RefEdit::force(
            gunnar_store::FullName::new("refs/heads/kept").expect("a legal ref name"),
            gunnar_store::Target::Object(kept),
        )],
    )
    .expect("point a ref at it");

    // Two packs of blobs no reference names: what a rejected push leaves
    // behind, and what a force-push orphans.
    let mut orphans = Vec::new();
    for round in 0..2 {
        let id = gunnar_store::ObjectStore::write(
            &store.objects(),
            gunnar_store::gix_object::Kind::Blob,
            format!("orphan {round}\n").as_bytes(),
        )
        .expect("write a blob");
        let (bytes, _) = gunnar_pack::write_pack_to_vec(
            &[id],
            &store.objects(),
            &gunnar_pack::WriteOptions::new(hash),
        )
        .expect("write a pack");
        gunnar_pack::index_pack(
            &mut std::io::Cursor::new(&bytes),
            Some(&pack_dir),
            &gunnar_pack::IndexOptions::new(hash),
            None,
        )
        .expect("index the pack");
        orphans.push(id);
    }
    for id in &orphans {
        assert!(
            gunnar_store::ObjectStore::contains(&store.objects(), id),
            "the fixture never wrote {id}, so both arms below would be vacuous"
        );
    }

    // ── an ordinary gc keeps them ──────────────────────────────────────────
    gunnar_cli::server::admin::gc(
        &stores,
        gunnar_cli::cli::GcArgs {
            path: None,
            ..Default::default()
        },
    )
    .expect("gc");
    for id in &orphans {
        assert!(
            gunnar_store::ObjectStore::contains(&store.objects(), id),
            "a `gc` with no --prune DROPPED {id}. Reclaiming is a destructive decision and must \
             never be the default"
        );
    }

    // ── and so does `--prune` under the DEFAULT grace ──────────────────────
    //
    // These blobs were written a millisecond ago, and the two-week grace is
    // what stops a scheduled `gc --prune` from destroying a push that is still
    // in flight (`RepackOptions::prune_grace`). Asserting it here and not only
    // in `gunnar-store` is the point: the flag has to reach `admin::gc` with the
    // store's default in it, and a `GcArgs` that defaulted the grace to zero
    // would leave every other assertion in this test green.
    gunnar_cli::server::admin::gc(
        &stores,
        gunnar_cli::cli::GcArgs {
            path: None,
            prune: true,
            ..Default::default()
        },
    )
    .expect("gc --prune under the default grace");
    for id in &orphans {
        assert!(
            gunnar_store::ObjectStore::contains(
                &stores.repo_store(&repo).expect("reopen").objects(),
                id
            ),
            "`gc --prune` dropped {id}, written a moment ago, under the default two-week \
             grace. A live push in its gate window would have lost its payload the same way"
        );
    }

    // ── --prune --prune-grace now reclaims them ────────────────────────────
    gunnar_cli::server::admin::gc(
        &stores,
        gunnar_cli::cli::GcArgs {
            path: None,
            prune: true,
            prune_grace: std::time::Duration::ZERO,
            ..Default::default()
        },
    )
    .expect("gc --prune --prune-grace now");
    // Re-opened, because `contains` may be answered off an index snapshot the
    // first store took before the fold replaced it — an assertion about disk
    // read through a stale handle is exactly the blind green this suite is for.
    let store = stores.repo_store(&repo).expect("reopen the repository");
    for id in &orphans {
        assert!(
            !gunnar_store::ObjectStore::contains(&store.objects(), id),
            "gc --prune kept {id}, which no reference can reach. The flag reaches nothing"
        );
    }
    // …and it pruned what was unreachable, not everything. A `--prune` that
    // emptied the repository would satisfy every assertion above.
    assert!(
        gunnar_store::ObjectStore::contains(&store.objects(), &kept),
        "gc --prune dropped {kept}, which refs/heads/kept points straight at"
    );
}

/// A gRPC request carrying a fresh, single-use sshsig credential signed with
/// `key`, the same key the §5 table holds. Each call mints a new nonce
/// because the server refuses a credential twice.
fn authed_with<T>(key: &gunnar_core::ssh_key::PrivateKey, message: T) -> tonic::Request<T> {
    use std::sync::atomic::{AtomicU64, Ordering};
    static SEQ: AtomicU64 = AtomicU64::new(0);
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("after 1970")
        .as_millis() as i64;
    let nonce = format!("serve-wiring-{:016}", SEQ.fetch_add(1, Ordering::Relaxed));
    let credential = gunnar_grpc::sign_credential(key, now, &nonce)
        .expect("sign a gRPC credential")
        .to_header_value();
    let mut request = tonic::Request::new(message);
    request.metadata_mut().insert(
        gunnar_grpc::CREDENTIAL_METADATA_KEY,
        credential.parse().expect("an ascii credential"),
    );
    request
}

/// **The server itself puts no private key on disk in the clear** (`D26`).
///
/// `gunnar-core` proves `SealedSecretStore` seals. That proves nothing about
/// whether the *server* uses it — an unwired sealing layer is exactly the
/// hollow guard shape, and until this test existed the boot path called
/// `control().secrets()` and wrote the host key as plaintext.
///
/// Asserted over the bytes of `secrets.arrow` after a real `Server::start`.
#[test]
fn a_started_server_leaves_no_plaintext_host_key_on_disk() {
    let dir = tempfile::tempdir().expect("temp dir");
    let private = gunnar_core::mint_ed25519().expect("mint");
    let admin = gunnar_core::ssh_key::PrivateKey::from_openssh(&private)
        .expect("parse")
        .public_key()
        .to_openssh()
        .expect("render");
    let rt = runtime();

    // The SERVER is the only writer here. Minting the key from the test first
    // would make this pass on the test's own sealing rather than on the boot
    // path's, which is the entire question.
    let stores = Arc::new(ServerStores::open(dir.path()).expect("open"));
    let server = rt
        .block_on(Server::start(
            Arc::clone(&stores),
            ServeConfig {
                no_grpc: true,
                admin_key: Some(admin),
                ..ServeConfig::ephemeral()
            },
        ))
        .expect("start");
    rt.block_on(server.shutdown()).expect("shut down");

    // SNAPSHOT THE FILE FIRST, before anything in this test reads a secret.
    // This ordering is load-bearing and was got wrong once: reading through
    // `stores.secrets()` first MIGRATES a legacy plaintext and compacts it out,
    // so the test would find a clean file and pass even with the server writing
    // plaintext. The guard was blind until the bytes were captured up front.
    //
    // `FsStore` keeps its Arrow logs in `<git dir>/gunnar-store/`; the KEK is
    // deliberately NOT in there, so a copy of the store directory carries no
    // key.
    let control = dir.path().join(gunnar_cli::server::stores::CONTROL_DIR);
    let log = std::fs::read(control.join("gunnar-store").join("secrets.arrow"))
        .expect("the server must have written a secret log");

    let key = gunnar_core::ensure_host_key(&stores.secrets()).expect("read back what it minted");
    drop(stores);
    assert!(
        !log.is_empty(),
        "the secret log is empty, so this test detects nothing"
    );

    let contains = |needle: &[u8]| log.windows(needle.len()).any(|w| w == needle);

    // The detector proves itself on something that IS there: the sealed-value
    // magic. If this fails, the searches below are not searching.
    assert!(
        contains(gunnar_core::SEAL_MAGIC),
        "the server wrote no sealed value at all, so `is not present` below means nothing"
    );
    assert!(
        !contains(b"BEGIN OPENSSH PRIVATE KEY"),
        "the server left the OpenSSH private-key armour on disk in the clear"
    );
    assert!(
        !contains(key.private_openssh()),
        "the server left the host key's exact bytes on disk in the clear"
    );

    // And the KEK really is a separate file, outside the log the cold tier
    // would ever carry.
    let kek = control.join(gunnar_core::KEK_FILE);
    assert!(kek.is_file(), "no KEK was written beside the control store");
    assert_eq!(std::fs::read(&kek).expect("read").len(), 32);
    assert!(
        !control
            .join("gunnar-store")
            .join(gunnar_core::KEK_FILE)
            .exists(),
        "the KEK is inside the store directory, so anything that copies the logs copies \
         the key with them"
    );
}

// ── the startup banner may not misstate the control plane's auth ───────────

/// `gunnar serve` prints one line about the gRPC listener on stdout, and until
/// 2026-08-05 that line was the string literal `(UNAUTHENTICATED in v1, D9)`,
/// printed unconditionally.
///
/// It had gone stale. [`gunnar_grpc::AuthMode::Required`] is the default and
/// `warn_authentication_disabled` was already logging *"gRPC authentication is
/// ON"* at `info!` two lines earlier, so one start printed both claims. An
/// operator reading the banner was told the control plane was open to anyone
/// who could reach the port, which is the opposite of what the server does —
/// and that is the direction of error that gets a port opened up "since it is
/// unauthenticated anyway".
///
/// # Both directions
///
/// A banner test that only checks the served default would pass against an
/// accessor hardwired to `true`, so the escape hatch is asserted to report
/// `false` through the same predicate. If somebody makes
/// `authentication_enabled` a constant, one of the two halves goes red.
#[test]
fn the_started_server_reports_the_auth_mode_it_actually_serves() {
    let dir = tempfile::tempdir().expect("temp dir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("open the stores"));

    let private = gunnar_core::mint_ed25519().expect("mint an admin key");
    let public = gunnar_core::ssh_key::PrivateKey::from_openssh(&private)
        .expect("parse")
        .public_key()
        .to_openssh()
        .expect("render");

    let rt = runtime();
    let server = rt
        .block_on(Server::start(
            Arc::clone(&stores),
            ServeConfig {
                admin_key: Some(public),
                ..ServeConfig::ephemeral()
            },
        ))
        .expect("the server must start with an admin key");

    let (addr, authenticated) = server
        .grpc_endpoint()
        .expect("the gRPC listener is on in this config, so it must report an endpoint");
    assert_eq!(
        Some(addr),
        server.grpc_addr(),
        "the two accessors must name the same listener"
    );
    assert!(
        authenticated,
        "the served ServerConfig defaults to AuthMode::Required, so the banner must not say \
         UNAUTHENTICATED"
    );
    assert_eq!(
        authenticated,
        gunnar_grpc::ServerConfig::default().authentication_enabled(),
        "the endpoint's verdict must be the served config's verdict, not a second opinion"
    );

    // The other direction: the same predicate over the escape hatch. Without
    // this, an `authentication_enabled` hardwired to `true` passes above.
    assert!(
        !gunnar_grpc::ServerConfig::default()
            .insecure_allow_all_for_local_development()
            .authentication_enabled(),
        "the D9 escape hatch must report false through the very predicate the banner reads, or \
         the banner cannot distinguish the two modes at all"
    );

    rt.block_on(server.shutdown()).expect("shut down");
}