Tests / Test (push) Skipped
Tests / OCR (tesseract fixture) (push) Skipped
Tests / Release (semver) (push) Skipped
temporal_freshness then authority_pairing; unresolved keeps (not confirmed). bin/facts/audit contradict. Gitea #29.
108 lines
3.7 KiB
Python
Executable File
108 lines
3.7 KiB
Python
Executable File
#!/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:]))
|