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

nordisk/gunnar

crates/gunnar-cli/tests/ai_trailers_refused.rs

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

//! **The forge refuses a push that introduces an AI attribution trailer**
//! (owner ruling 2026-09-28), end to end: stock `git` over real ssh into a
//! repository with NO policy configured — the gate is on by default.
//!
//! One repository, in the order an estate meets it:
//!
//! 1. a USER pushes trailer-bearing history into the empty repository:
//!    refused, and the client's error names the commit and the fix;
//! 2. the installer's seed — the `installer-seed` MACHINE principal — pushes
//!    the same history into the still-empty repository: accepted;
//! 3. the user pushes a historical trailer commit (now on `main`) to a new
//!    branch: accepted, it is not new to the forge;
//! 4. the user adds a NEW trailer commit: refused with its sha; a commit that
//!    only mentions the tool in prose: accepted.

mod common;

use std::path::{Path, PathBuf};

use common::{tools_present, Harness};
use gunnar_core::{Grant, HashKind, Level, Principal, PrincipalKind};

const NS: &str = "team";
const REPO: &str = "project";
const TRAILER: &str = "Co-Authored-By: Claude <noreply@anthropic.com>";

fn head(harness: &Harness, work: &Path) -> String {
    harness
        .git(work, &["rev-parse", "HEAD"])
        .expect_ok("rev-parse")
        .stdout
        .trim()
        .to_owned()
}

fn commit(harness: &Harness, work: &Path, file: &str, message: &str) -> String {
    std::fs::write(work.join(file), message).expect("write");
    harness.git(work, &["add", "."]).expect_ok("add");
    harness
        .git(work, &["commit", "--quiet", "-m", message])
        .expect_ok("commit");
    head(harness, work)
}

/// The seed's key, enrolled as the `installer-seed` machine principal with
/// the namespace Admin a first push needs — exactly what the installer's
/// `principal.installer-seed = "machine admin <key>"` line would give it.
fn seed_ssh_command(harness: &Harness) -> String {
    let private = gunnar_core::mint_ed25519().expect("mint the seed key");
    let parsed = gunnar_core::ssh_key::PrivateKey::from_openssh(&private).expect("parses");
    let public = parsed.public_key().to_openssh().expect("render");
    let path: PathBuf = harness.dir.path().join("seed_key");
    std::fs::write(&path, &private).expect("write the seed key");
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt as _;
        std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).expect("chmod");
    }
    let auth = harness.stores.auth();
    auth.add_principal(
        Principal::new("installer-seed", "installer-seed", PrincipalKind::Machine).expect("valid"),
    )
    .expect("add the seed principal");
    auth.register_key("installer-seed", &public, None)
        .expect("register the seed key");
    auth.grant(Grant::namespace("installer-seed", NS, Level::Admin))
        .expect("namespace admin");
    harness.ssh_command().replace(
        &harness.key_path.display().to_string(),
        &path.display().to_string(),
    )
}

#[test]
fn a_new_trailer_commit_is_refused_and_history_the_forge_holds_is_not() {
    assert!(tools_present(), "this test requires git and ssh");
    let mut harness = Harness::start(NS, REPO, HashKind::Sha1);
    let url = harness.url(NS, REPO);

    // A mirror's history: a trailer commit deep in it.
    let work = harness.dir.path().join("work");
    harness
        .git(
            harness.dir.path(),
            &[
                "init",
                "--quiet",
                "-b",
                "main",
                work.to_str().expect("utf-8"),
            ],
        )
        .expect_ok("git init");
    commit(&harness, &work, "a.txt", "root");
    let historical = commit(
        &harness,
        &work,
        "b.txt",
        &format!("historical work\n\n{TRAILER}"),
    );
    commit(&harness, &work, "c.txt", "later, clean");

    // 1. A user pushing it into the EMPTY repository is refused.
    let refused = harness.git(&work, &["push", &url, "main"]);
    assert!(
        !refused.ok,
        "a user seeded trailer history into an empty repository:\n{}",
        refused.stderr
    );
    assert!(
        refused.stderr.contains(&historical),
        "the refusal must name the commit: {}",
        refused.stderr
    );
    assert!(
        refused.stderr.contains("git commit --amend"),
        "the refusal must say how to fix it: {}",
        refused.stderr
    );

    // 2. The seed, the same history, the same still-empty repository.
    let seed = seed_ssh_command(&harness);
    harness
        .git_with_ssh(&work, &seed, &["push", "--quiet", &url, "main"])
        .expect_ok("the installer seed into an empty repository");

    // 3. A historical trailer commit, already on `main`, to a new branch name.
    harness
        .git(
            &work,
            &[
                "push",
                "--quiet",
                &url,
                &format!("{historical}:refs/heads/old-work"),
            ],
        )
        .expect_ok("history the forge already holds, at a new name");

    // 4. New work: a trailer is refused and names its sha; prose passes.
    let bad = commit(&harness, &work, "d.txt", &format!("new work\n\n{TRAILER}"));
    let refused = harness.git(&work, &["push", &url, "main"]);
    assert!(!refused.ok, "a new trailer commit was accepted");
    assert!(refused.stderr.contains(&bad), "{}", refused.stderr);
    assert!(refused.stderr.contains(TRAILER), "{}", refused.stderr);

    harness
        .git(&work, &["reset", "--quiet", "--hard", "HEAD~1"])
        .expect_ok("drop the trailer commit");
    commit(
        &harness,
        &work,
        "d.txt",
        "Switch the review bot to Claude\n\nNo Co-Authored-By trailers, ever.",
    );
    harness
        .git(&work, &["push", "--quiet", &url, "main"])
        .expect_ok("a prose mention is not a trailer");

    harness.stop();
}