#!/usr/bin/env python3 """facts/audit - evidence & lexicon checks for the 2dph brain. bin/facts/audit self # lexicon: docs + two-source rule bin/facts/audit db # evidence gate against var/kb.lbug bin/facts/audit contradict # D16 adjudication (JSON claim(s) on stdin) `self` mode checks the repo itself (no network, no runtime deps). `db` mode loads every Leaf with root=facts. Confirmed facts need ` x `; hypothesis contradictions need `a x b vs c x d` (both sides ≥2). `contradict` applies temporal_freshness then authority_pairing; ≥2 vs ≥2 with no rule stays hypothesis / `(not confirmed)`. Exit 0 = all checks pass, 1 = audit failures, 2 = could not evaluate. """ from __future__ import annotations import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "bin" / "tools")) from contradict import adjudicate, check_fact_row # noqa: E402 def audit_db() -> list[str]: from kblib import connect from kblib import VAR dbpath = VAR / "kb.lbug" if not dbpath.exists(): return ["no database yet; run bin/kb/index first"] db, conn = connect(dbpath) r = conn.execute("MATCH (l:Leaf {root:'facts'}) RETURN l.id, l.source, l.loc, l.how, l.confidence") problems: list[str] = [] for lid, source, loc, how, conf in r.get_all(): problems.extend(check_fact_row(str(lid), str(source or ""), str(loc or ""), str(how or ""), str(conf or ""))) conn.close() db.close() return problems def audit_self() -> list[str]: problems: list[str] = [] plan = (ROOT / "PLAN.md").read_text() if "recall@5" not in plan: problems.append("PLAN.md missing recall@5 gate") if re.search(r"(?i)facts must have.*2 sources|2.source", plan) is None: problems.append("PLAN.md missing the two-source evidence rule for facts") if "temporal_freshness" not in plan or "authority_pairing" not in plan: problems.append("PLAN.md missing D16 adjudication rules") if re.search(r"(?i)HNSW|BM25|deduction", (ROOT / "README.md").read_text()) is None: problems.append("README.md missing search/retrieval description") return problems def audit_contradict(raw: str) -> tuple[list[str], list[dict]]: raw = raw.strip() if not raw: return ["contradict: empty stdin (JSON claim or {claims:[...]})"], [] try: payload = json.loads(raw) except json.JSONDecodeError as e: return [f"contradict: invalid JSON: {e}"], [] if isinstance(payload, dict) and "claims" in payload: claims = list(payload.get("claims") or []) elif isinstance(payload, dict): claims = [payload] elif isinstance(payload, list): claims = payload else: return ["contradict: expected object or list"], [] details = [adjudicate(c) for c in claims] return [], details def main(argv: list[str]) -> int: import argparse p = argparse.ArgumentParser(description="evidence & lexicon audit") p.add_argument("mode", choices=("self", "db", "contradict")) p.add_argument("--json", action="store_true") a = p.parse_args(argv) details: list[dict] = [] if a.mode == "self": problems = audit_self() elif a.mode == "db": problems = audit_db() else: problems, details = audit_contradict(sys.stdin.read()) out: dict = {"mode": a.mode, "ok": not problems, "problems": problems} if details: out["contradictions"] = details if a.json: print(json.dumps(out, indent=2)) else: from yamlout import to_yaml print(to_yaml(out)) return 0 if not problems else 1 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))