use assert_cmd::Command;
use std::fs;
use std::path::Path;
type TestResult = Result<(), Box<dyn std::error::Error>>;
const MANIFEST: &str = r#"
version = 1
canonical = "json-schema-2020-12"
authoring = "zod"
[[contracts]]
name = "user"
source = "contracts/user.zod.ts"
emit = ["zod"]
[[mappings]]
glob = "app/api/**/*.ts"
contracts = ["user"]
require = "boundary-validation"
[gates]
protected_paths = ["pushkin.toml"]
retrieval_paths = ["crates/**/*.rs"]
retrieval_tool = "mcp__codebase-retrieval__codebase-retrieval"
"#;
const GATED: &str = "crates/pushkin-core/src/pipeline.rs";
const GATED_DIR: &str = "crates/";
const RULE: &str = "pushkin.retrieval.raw_read";
fn seed(dir: &Path, manifest: &str) -> TestResult {
fs::write(dir.join("pushkin.toml"), manifest)?;
fs::create_dir_all(dir.join("crates/pushkin-core/src"))?;
fs::write(dir.join(GATED), "// gated\n")?;
Ok(())
}
fn repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
seed(dir.path(), MANIFEST)?;
Ok(dir)
}
fn unopted_repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let manifest = MANIFEST
.replace(r#"retrieval_paths = ["crates/**/*.rs"]"#, "")
.replace(
r#"retrieval_tool = "mcp__codebase-retrieval__codebase-retrieval""#,
"",
);
seed(dir.path(), &manifest)?;
Ok(dir)
}
fn bash(dir: &Path, command: &str) -> Result<String, Box<dyn std::error::Error>> {
let payload = serde_json::json!({
"session_id": "bash-gate-verbs",
"tool_name": "Bash",
"tool_input": { "command": command },
})
.to_string();
let output = Command::cargo_bin("pushkin")?
.current_dir(dir)
.write_stdin(payload)
.args(["hook", "claude"])
.output()?;
Ok(format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
))
}
fn assert_denied(output: &str, why: &str) {
assert!(output.contains(RULE), "{why}: {output}");
}
fn assert_allowed(output: &str, why: &str) {
assert!(!output.contains(RULE), "{why}: {output}");
}
#[test]
fn git_add_stages_a_path_and_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git add {GATED}"))?;
assert_allowed(&output, "staging reveals no content");
Ok(())
}
#[test]
fn git_checkout_restores_a_path_and_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git checkout -- {GATED}"))?;
assert_allowed(&output, "restore reveals no content");
Ok(())
}
#[test]
fn rm_deletes_without_revealing_and_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("rm {GATED}"))?;
assert_allowed(&output, "deletion reveals no content");
Ok(())
}
#[test]
fn git_log_oneline_shows_subjects_not_contents() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git log --oneline -- {GATED}"))?;
assert_allowed(&output, "log metadata reveals no content");
Ok(())
}
#[test]
fn wc_counts_lines_without_revealing_them() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("wc -l {GATED}"))?;
assert_allowed(&output, "a line count reveals no content");
Ok(())
}
#[test]
fn cargo_never_enters_the_scan() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("cargo build --manifest-path {GATED}"))?;
assert_allowed(&output, "cargo is not a reader");
Ok(())
}
#[test]
fn git_blame_prints_the_file_and_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git blame {GATED}"))?;
assert_denied(&output, "blame prints the whole file");
Ok(())
}
#[test]
fn git_log_with_patch_prints_the_file_and_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git log -p -- {GATED}"))?;
assert_denied(&output, "log -p prints the file");
Ok(())
}
#[test]
fn git_grep_searches_gated_content_and_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git grep 'fn parse' -- {GATED_DIR}"))?;
assert_denied(&output, "git grep reads every file under the pathspec");
Ok(())
}
#[test]
fn git_show_of_a_rev_scoped_path_prints_the_file_and_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git show HEAD:{GATED}"))?;
assert_denied(&output, "rev:path is a whole-file read");
Ok(())
}
#[test]
fn git_diff_of_a_gated_path_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git diff {GATED}"))?;
assert_denied(&output, "a diff carries file content");
Ok(())
}
#[test]
fn git_diff_name_only_is_metadata_and_is_allowed() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git diff --name-only {GATED}"))?;
assert_allowed(&output, "--name-only reveals no content");
Ok(())
}
#[test]
fn git_show_with_stat_is_metadata_and_is_allowed() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("git show --stat HEAD -- {GATED}"))?;
assert_allowed(&output, "--stat reveals no content");
Ok(())
}
#[test]
fn a_recursive_grep_of_a_gated_directory_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("grep -rn 'fn parse' {GATED_DIR}"))?;
assert_denied(&output, "-r reaches every gated file under the directory");
Ok(())
}
#[test]
fn ripgrep_is_recursive_by_default_and_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("rg 'fn parse' {GATED_DIR}"))?;
assert_denied(&output, "rg recurses without being asked");
Ok(())
}
#[test]
fn listing_the_same_directory_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("ls -l {GATED_DIR}"))?;
assert_allowed(&output, "a listing is not a read");
Ok(())
}
#[test]
fn an_apparent_range_does_not_unlock_a_shell_read() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("sed -n 1,50p {GATED}"))?;
assert_denied(&output, "a bound the gate cannot verify is not a bound");
Ok(())
}
#[test]
fn head_with_a_line_count_is_still_a_shell_read() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("head -50 {GATED}"))?;
assert_denied(&output, "`head -999999` reads as bounded as `head -50`");
Ok(())
}
#[test]
fn an_unbounded_sed_script_is_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("sed 's/a/b/' {GATED}"))?;
assert_denied(&output, "sed streams the whole file");
Ok(())
}
#[test]
fn the_read_surface_still_honors_an_explicit_range() -> TestResult {
let dir = repo()?;
let payload = serde_json::json!({
"session_id": "bash-gate-verbs",
"tool_name": "Read",
"tool_input": { "file_path": GATED, "offset": 1, "limit": 50 },
})
.to_string();
let output = Command::cargo_bin("pushkin")?
.current_dir(dir.path())
.write_stdin(payload)
.args(["hook", "claude"])
.output()?;
let output = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_allowed(&output, "a verifiable range is still the deliberate shape");
Ok(())
}
#[test]
fn cat_of_a_gated_path_is_still_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("cat {GATED}"))?;
assert_denied(&output, "the unbounded single-file read is C's whole point");
Ok(())
}
#[test]
fn an_unopted_repo_gates_no_verb() -> TestResult {
let dir = unopted_repo()?;
for command in [
format!("cat {GATED}"),
format!("grep -rn 'x' {GATED_DIR}"),
format!("git show HEAD:{GATED}"),
] {
let output = bash(dir.path(), &command)?;
assert_allowed(&output, "an unopted repo gates nothing");
}
Ok(())
}