use crate::gates::{self, GateError};
use nibli_render::{Register, render_logic_buffer};
pub const VALIDATOR_SYSTEM_PROMPT: &str = "\
You are an independent semantic auditor for the formalization of English into a \
machine-checkable knowledge-base language. You will be given a SOURCE text and, for \
each line of a candidate formalization, the CLAIMS line — a mechanical English \
rendering of what that line actually asserts, produced by a deterministic compiler \
(not by the formalizer). Grammar has already been machine-checked; do NOT comment on \
grammar or style.
Judge ONE thing: does the set of claims match the meaning of the source text? Watch \
especially for: wrong or missing participants (misresolved pronouns/anaphora), arguments \
in the wrong predicate places, words whose claims say something unrelated to the source, \
missing assertions, and invented assertions.
Reply in EXACTLY this format: first line, the single word MATCH or MISMATCH. If \
MISMATCH, follow with a numbered list of concrete discrepancies, each naming the KB line \
number and stating what the line claims versus what the source says. No other text.";
pub fn back_translation(kb_text: &str) -> Result<Vec<(String, String)>, GateError> {
let mut out = Vec::new();
for raw in kb_text.lines() {
let line = raw.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let buf = gates::local_gates(line)?;
out.push((line.to_string(), render_logic_buffer(&buf, Register::Spec)));
}
Ok(out)
}
pub fn judge_prompt(source: &str, back: &[(String, String)]) -> String {
let mut p = format!("SOURCE TEXT:\n{source}\n\nCANDIDATE FORMALIZATION (per KB line):\n");
for (i, (kb_line, claims)) in back.iter().enumerate() {
p.push_str(&format!("{}. {kb_line}\n claims: {claims}\n", i + 1));
}
p.push_str("\nVerdict?");
p
}
pub fn parse_verdict(reply: &str) -> Option<String> {
let mut lines = reply.trim().lines();
let first = lines.next().unwrap_or("").trim().to_ascii_uppercase();
if first.starts_with("MISMATCH") {
let issues: String = lines.collect::<Vec<_>>().join("\n").trim().to_string();
let issues = if issues.is_empty() {
"the verifier judged the meaning does not match the source (no details given)"
.to_string()
} else {
issues
};
Some(issues)
} else {
None
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
#[test]
fn back_translation_renders_each_line() {
let back = back_translation("dog(Adam).\n# comment\n\ncat(Betis).").unwrap();
assert_eq!(back.len(), 2);
assert_eq!(back[0].0, "dog(Adam).");
assert!(
back[0].1.to_lowercase().contains("adam"),
"claims must mention the participant: {}",
back[0].1
);
}
#[test]
fn judge_prompt_contains_source_lines_and_claims() {
let back = vec![("dog(Adam).".to_string(), "adam is a dog".to_string())];
let p = judge_prompt("Adam is a dog.", &back);
assert!(p.contains("SOURCE TEXT:\nAdam is a dog."));
assert!(p.contains("1. dog(Adam)."));
assert!(p.contains("claims: adam is a dog"));
}
#[test]
fn verdict_parsing_match_mismatch_and_fail_open() {
assert_eq!(parse_verdict("MATCH"), None);
assert_eq!(parse_verdict(" match "), None);
let v = parse_verdict("MISMATCH\n1. line 1 claims X but source says Y").unwrap();
assert!(v.contains("line 1 claims X"));
assert!(
parse_verdict("MISMATCH")
.unwrap()
.contains("verifier judged")
);
assert_eq!(parse_verdict("I think it's mostly fine?"), None);
assert_eq!(parse_verdict(""), None);
}
}