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";
fn repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
fs::write(dir.path().join("pushkin.toml"), MANIFEST)?;
fs::create_dir_all(dir.path().join("crates/pushkin-core/src"))?;
fs::write(dir.path().join(GATED), "// gated\n")?;
Ok(dir)
}
fn bash(dir: &Path, command: &str) -> Result<String, Box<dyn std::error::Error>> {
let payload = serde_json::json!({
"session_id": "bash-gate",
"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)
))
}
#[test]
fn the_grep_that_bypassed_the_gate_is_now_denied() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("grep -oE 'fn ' {GATED}"))?;
assert!(
output.contains("pushkin.retrieval.raw_read"),
"the observed bypass must now be caught: {output}"
);
Ok(())
}
#[test]
fn shell_readers_of_a_gated_path_are_denied() -> TestResult {
let dir = repo()?;
for command in [
format!("cat {GATED}"),
format!("head -50 {GATED}"),
format!("sed -n '1,80p' {GATED}"),
format!("awk '/fn /' {GATED}"),
format!("less {GATED}"),
format!("rg 'fn ' {GATED}"),
] {
let output = bash(dir.path(), &command)?;
assert!(
output.contains("pushkin.retrieval.raw_read"),
"`{command}` reads a gated path and must deny: {output}"
);
}
Ok(())
}
#[test]
fn the_bash_deny_reuses_the_read_contract_rule_and_prose() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), &format!("cat {GATED}"))?;
assert!(
output.contains("mcp__codebase-retrieval__codebase-retrieval"),
"the fix hint names the manifest's tool, as the Read path does: {output}"
);
assert!(
output.contains("read blocked"),
"a shell read is narrated as a read, not a write: {output}"
);
Ok(())
}
#[test]
fn commands_touching_no_gated_path_are_untouched() -> TestResult {
let dir = repo()?;
for command in [
"ls -la",
"cargo test --workspace",
"git status --short",
"cat README.md",
"grep -rn TODO docs/",
] {
let output = bash(dir.path(), command)?;
assert!(
!output.contains("pushkin.retrieval.raw_read"),
"`{command}` names no gated path and must pass: {output}"
);
}
Ok(())
}
#[test]
fn an_unopted_repo_never_gates_bash() -> TestResult {
let dir = tempfile::tempdir()?;
let manifest = MANIFEST
.replace(r#"retrieval_paths = ["crates/**/*.rs"]"#, "")
.replace(
r#"retrieval_tool = "mcp__codebase-retrieval__codebase-retrieval""#,
"",
);
fs::write(dir.path().join("pushkin.toml"), manifest)?;
fs::create_dir_all(dir.path().join("crates/pushkin-core/src"))?;
fs::write(dir.path().join(GATED), "// gated\n")?;
let output = bash(dir.path(), &format!("cat {GATED}"))?;
assert!(
!output.contains("pushkin.retrieval.raw_read"),
"opting out is the default and must stay free: {output}"
);
Ok(())
}
#[test]
fn shell_writes_are_out_of_scope_for_this_gate() -> TestResult {
let dir = repo()?;
let output = bash(dir.path(), "echo x > pushkin.toml")?;
assert!(
!output.contains("pushkin.retrieval.raw_read"),
"a write must not be reported as a raw-read violation: {output}"
);
Ok(())
}