#!/usr/bin/env python3 """facts/audit - evidence & lexicon checks for the 2dph brain. bin/facts/audit self # lexicon: every fact in db has >=2 sources bin/facts/audit db # evidence gate: run against var/kb.lbug `self` mode checks the repo itself (no network, no runtime deps). It greps for known-good two-source pairings and confirms the docs are consistent. `db` mode loads every Leaf with root=facts and asserts each has source_rev and a non-empty `loc` (the "where did you see it" evidence pointer) and that 'confirmed' facts carry a two-source `source` field. 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 / "tools")) def audit_db() -> list[str]: from kblib import connect, init_schema 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) init_schema(conn) 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(): if conf != "confirmed": problems.append(f"{lid}: facts require confidence='confirmed', got '{conf}'") if not source or " x " not in source: problems.append(f"{lid}: needs 2-source evidence in source, got '{source}'") if not loc: problems.append(f"{lid}: missing loc (evidence pointer)") if not how: problems.append(f"{lid}: missing how") 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 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 main(argv: list[str]) -> int: import argparse p = argparse.ArgumentParser(description="evidence & lexicon audit") p.add_argument("mode", choices=("self", "db")) p.add_argument("--json", action="store_true") a = p.parse_args(argv) problems = audit_self() if a.mode == "self" else audit_db() out = {"mode": a.mode, "ok": not problems, "problems": problems} 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:]))