mod oraculo;
use clap::{Parser, Subcommand};
use joeira_core::{AmbienteMock, Ponto, Predicado, Regra, Severidade, Veredito, avalia, prova};
#[derive(Parser)]
#[command(name = "joeira", about = "The typed git-hook control plane")]
struct Cli {
#[command(subcommand)]
comando: Comando,
}
#[derive(Subcommand)]
enum Comando {
Eval,
Prova {
#[arg(long)]
all: bool,
},
Pontos,
Oraculo {
#[arg(long, default_value = ".")]
repo: std::path::PathBuf,
#[arg(long, default_value_t = 500)]
n: usize,
},
}
fn main() -> anyhow::Result<()> {
match Cli::parse().comando {
Comando::Eval => cmd_eval(),
Comando::Prova { all } => cmd_prova(all),
Comando::Oraculo { repo, n } => {
let regras = corpus()?;
let linhas = oraculo::corre(&repo, n, ®ras)?;
oraculo::relata(&linhas)
}
Comando::Pontos => {
for p in Ponto::todos() {
println!(" {:<12} reads {:?}", p.arquivo(), p.leitura());
}
Ok(())
}
}
}
fn cmd_eval() -> anyhow::Result<()> {
let regras = corpus()?;
let amb = AmbienteMock::com_mensagem("init")
.com_stage("flake.lock", "a\n<<<<<<< HEAD\nb\n>>>>>>> other\n")
.com_head("app.conf", "listen = 0\n")
.com_stage("app.conf", "listen = 0\npassword: correcthorsebattery\n");
println!("corpus: {} rules", regras.len());
for r in ®ras {
let v = avalia(r, &amb);
let marca = match &v {
Veredito::Achado { .. } => "FOUND ",
Veredito::Limpo => "clean ",
Veredito::NaoSeAplica { .. } => "n/a ",
Veredito::Cego { .. } => "BLIND ",
};
println!(
" {marca} {:<28} floor={:?} ceiling={:?}{}",
r.nome(),
r.severidade(),
r.tecto(),
if v.gateia() { " [would gate]" } else { "" }
);
}
Ok(())
}
fn cmd_prova(all: bool) -> anyhow::Result<()> {
let regras = corpus()?;
if all {
let no_catalogo = joeira_lisp::ler_catalogo(joeira_lisp::CATALOGO)
.map_err(|e| anyhow::anyhow!("catalog: {e}"))?
.len();
println!(
"catalog: {no_catalogo} forms, corpus: {} rules",
regras.len()
);
anyhow::ensure!(
regras.len() == no_catalogo,
"corpus has {} rules but the catalog declares {no_catalogo} — refusing to report green over a subset",
regras.len()
);
}
let (n, rows) = prova(®ras);
anyhow::ensure!(n > 0, "corpus is EMPTY — refusing to report green");
let vermelhas = rows.iter().filter(|r| !r.verde()).count();
for r in &rows {
println!(
" {} {:<28} refuses-its-block-witness={} passes-its-pass-witness={}",
if r.verde() { "ok " } else { "FAIL" },
r.regra,
r.bloqueia_ok,
r.passa_ok
);
}
println!(
"prova: {}/{n} rules green in both directions",
n - vermelhas
);
anyhow::ensure!(vermelhas == 0, "{vermelhas} of {n} rules are red");
Ok(())
}
fn corpus() -> anyhow::Result<Vec<Regra>> {
let formas = joeira_lisp::ler_catalogo(joeira_lisp::CATALOGO)
.map_err(|e| anyhow::anyhow!("the compiled-in catalog does not parse: {e}"))?;
anyhow::ensure!(
!formas.is_empty(),
"the catalog is EMPTY — refusing to build a corpus that proves nothing"
);
let mut regras = Vec::with_capacity(formas.len());
for f in &formas {
let t = testemunhas(f).ok_or_else(|| {
anyhow::anyhow!(
"catalog form {:?} has no witnesses in this binary — a rule that \
cannot be proven in both directions must not ship silently",
f.nome
)
})?;
let rev = f
.reversibilidade
.ok_or_else(|| anyhow::anyhow!("catalog form {:?} omits :reversibilidade", f.nome))?;
let fp =
f.fp.ok_or_else(|| anyhow::anyhow!("catalog form {:?} omits :fp", f.nome))?;
let mut r = Regra::nova(
&f.nome,
f.ponto,
t.predicado,
rev,
fp,
&f.mensagem,
t.bloqueia,
t.passa,
);
if let Some(piso) = t.piso {
r = r.com_piso(piso);
}
regras.push(r);
}
Ok(regras)
}
struct Testemunhas {
predicado: Predicado,
bloqueia: AmbienteMock,
passa: AmbienteMock,
piso: Option<Severidade>,
}
fn testemunhas(f: &joeira_lisp::FormaLida) -> Option<Testemunhas> {
Some(match f.nome.as_str() {
"msg-placeholder-subject" => Testemunhas {
predicado: Predicado::Algum(vec![
Predicado::AssuntoNaLista {
lista: f.lista.clone(),
},
Predicado::AssuntoCaudaNaLista {
lista: f.lista.clone(),
},
]),
bloqueia: AmbienteMock::com_mensagem("init"),
passa: AmbienteMock::com_mensagem("joeira: port the incumbent concerns"),
piso: None,
},
"msg-ai-attribution-trailer" => Testemunhas {
predicado: Predicado::MensagemTemTrailer {
marcadores: vec!["Co-Authored-By:".into(), "Claude-Session:".into()],
},
bloqueia: AmbienteMock::com_mensagem("feat: x\n\nClaude-Session: abc"),
passa: AmbienteMock::com_mensagem("feat: x\n\nplain body"),
piso: None,
},
"vcs-conflict-markers" => Testemunhas {
predicado: Predicado::BlobTemParDeLinhas {
abre: "<<<<<<< ".into(),
fecha: ">>>>>>> ".into(),
},
bloqueia: AmbienteMock::default()
.com_stage("flake.lock", "<<<<<<< HEAD\nx\n>>>>>>> them\n"),
passa: AmbienteMock::default().com_stage("README.md", "# T\n\n=======\n"),
piso: Some(Severidade::Bloqueia),
},
"gen-lock-tie" => Testemunhas {
predicado: Predicado::AmarraDeHash {
arquivo: "Cargo.lock".into(),
sidecar: "Cargo.gen.lock".into(),
campo: "cargo_lock_sha256".into(),
},
bloqueia: AmbienteMock::default()
.com_stage("Cargo.lock", "l")
.com_stage("Cargo.gen.lock", r#"{"cargo_lock_sha256": "deadbeef"}"#)
.com_sha("Cargo.lock", "cafebabe"),
passa: AmbienteMock::default()
.com_stage("Cargo.lock", "l")
.com_stage("Cargo.gen.lock", r#"{"cargo_lock_sha256": "cafebabe"}"#)
.com_sha("Cargo.lock", "cafebabe"),
piso: Some(Severidade::Bloqueia),
},
"sec-plaintext-credential" => Testemunhas {
predicado: Predicado::LinhaAdicionadaCasa {
padroes: vec![
joeira_core::Padrao::novo(
"plaintext-credential",
r"(?i)(^|[^A-Za-z0-9])(password|passwd|secret|client_secret)\s*[:=]\s*[^:\s]\S{7,}",
)
.ok()?,
],
},
bloqueia: AmbienteMock::default()
.com_stage("notes.txt", "password: correcthorsebattery\n"),
passa: AmbienteMock::default().com_stage(
"lib.rs",
"use cofre_secret::Secret::new;\nlet x = std::secret::y;\n",
),
piso: None,
},
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_catalog_and_the_witnesses_are_in_bijection() {
let formas = joeira_lisp::ler_catalogo(joeira_lisp::CATALOGO).expect("catalog parses");
let regras = corpus().expect("corpus builds");
assert_eq!(
regras.len(),
formas.len(),
"every catalog form must reach the corpus"
);
let arms = [
"msg-placeholder-subject",
"msg-ai-attribution-trailer",
"vcs-conflict-markers",
"gen-lock-tie",
"sec-plaintext-credential",
];
for a in arms {
assert!(
formas.iter().any(|f| f.nome == a),
"witness arm {a:?} has no catalog form — it proves nothing"
);
}
assert_eq!(arms.len(), formas.len(), "no arm is unreachable");
}
#[test]
fn the_placeholder_rule_carries_all_77_words() {
let formas = joeira_lisp::ler_catalogo(joeira_lisp::CATALOGO).expect("parses");
let msg = formas
.iter()
.find(|f| f.nome == "msg-placeholder-subject")
.expect("present");
assert_eq!(msg.lista.len(), 77);
let t = testemunhas(msg).expect("witnessed");
match t.predicado {
Predicado::Algum(ref arms) => {
assert_eq!(arms.len(), 2, "whole-subject AND tail-after-colon");
for a in arms {
match a {
Predicado::AssuntoNaLista { lista }
| Predicado::AssuntoCaudaNaLista { lista } => {
assert_eq!(lista.len(), 77, "both arms carry the full list");
}
outro => panic!("unexpected arm: {outro:?}"),
}
}
}
ref outro => panic!("expected Algum, got {outro:?}"),
}
}
}