Sixteen mutating routes now refuse a caller who cannot present the token the server started with; queries stay open, because a question is not a write. The environment variable is ELY_EYE_AUTH_TOKEN, following the repo's env_prefix convention — the shorter name I reached for first returned 403 where the smoke expected 401, and the server printed the reason. Words from a stranger are quarantined on the way in: an HTTP upload is third_party by definition, and a sentence carrying an instruction-shaped phrase is quarantined whoever wrote it. A quarantined atom stays findable and stays out of the compiled context until a second, live, unquarantined source agrees with its rarest words. Corroboration is computed at read time because it is a property of the corpus now, not of the moment a memory was written; it is term-level, and the docstring says so. forget now erases the source row and the compiled contexts beside the atoms, so a forgotten memory leaves nothing that can still speak. The semantic cache key carries a store revision, which fixes a real staleness: a context compiled before new evidence arrived kept its old quarantine decision forever. bench.audit --product still pointed at evals.py, which the p1-6 split moved into backend/src/ely_eye/proofs/. It failed fast, but the anti-self-justification rule had quietly stopped auditing the product; it now walks the package, and a test pins that the package exists and holds no re-roll. Gates: pytest 306 passed; ruff clean; mypy cold cache 24 errors in 9 files, the recorded baseline, net zero; pyright 0 errors on the 23 backend files and the 2 bench files; go build/vet/test and gofmt clean; npm run build 221.39 kB with eslint exit 0; bench/filesize_check clean; bench.audit --product clean over 31 harness files; scripts/mcp_write_smoke.sh PASS with the gate live across two servers and the MCP child.
287 lines
13 KiB
Python
287 lines
13 KiB
Python
"""b-5: the audit that trusts nothing we wrote, including this file.
|
|
|
|
Four rules, each machine-checked:
|
|
|
|
1. single_attempt - no loop may re-roll a case until a score target clears (best-of-N).
|
|
2. full_n - a frozen score must cover the frozen n, or say which --limit it used.
|
|
3. chance_row - every scored arm or store sits next to the score random guessing gets.
|
|
4. producer_verifier - gold labels name third-party datasets, and the scorer imports no model.
|
|
|
|
Run: python -m bench.audit # audit the repo and the reports
|
|
python -m bench.audit --self-test # prove the auditor catches injected cheating
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import ast
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from bench.sets_common import MODALITIES, SETS
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
REPORTS = ROOT / "reports"
|
|
CORPUS_MANIFEST = ROOT / "corpus" / "manifest.jsonl"
|
|
|
|
# Words that only appear where a score is compared to a target; a loop that re-rolls until one clears is best-of-N whatever it is called.
|
|
SCORE_WORDS = ("pass_rate", "pass_target", "accuracy", "recall", "hit_rate", "score", "target")
|
|
RETRY_MARKERS = ("best_of", "bestof", "reroll", "re_roll", "until_pass", "retry_until")
|
|
# The scorer must stay a pure function of (query, ranked ids, gold ids), or the thing being graded starts grading itself.
|
|
SCORER_FORBIDDEN = ("ely_eye", "transformers", "sentence_transformers", "torch", "bench.systems", "bench.counterpart")
|
|
FROZEN_REPORTS = ("baseline.json", "baseline_external.json")
|
|
|
|
|
|
def _words_in(node: ast.AST) -> set[str]:
|
|
"""Score vocabulary reachable from one expression, by name or attribute."""
|
|
found: set[str] = set()
|
|
for child in ast.walk(node):
|
|
if isinstance(child, ast.Name | ast.Attribute | ast.Constant):
|
|
text = str(getattr(child, "attr", None) or getattr(child, "id", None) or getattr(child, "value", ""))
|
|
text = text.lower()
|
|
found.update(word for word in SCORE_WORDS if word in text)
|
|
return found
|
|
|
|
|
|
def best_of_violations(source: str, name: str) -> list[str]:
|
|
"""Flag re-rolling: a while-loop that breaks on a score comparison, or an explicit best-of marker."""
|
|
try:
|
|
tree = ast.parse(source, filename=str(name))
|
|
except SyntaxError as error:
|
|
raise SystemExit(f"{name}: cannot parse ({error})") from error
|
|
violations: list[str] = []
|
|
for node in ast.walk(tree):
|
|
if not isinstance(node, ast.While):
|
|
continue
|
|
loop_words = _words_in(node.test)
|
|
break_words: set[str] = set()
|
|
for inner in ast.walk(node):
|
|
if isinstance(inner, ast.Break):
|
|
for parent in _enclosing_tests(inner, node):
|
|
break_words |= _words_in(parent)
|
|
if loop_words or break_words:
|
|
words = ", ".join(sorted(loop_words | break_words))
|
|
violations.append(f"{name}:{node.lineno}: retry-until-score loop re-rolls on {words}")
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Name | ast.Attribute | ast.keyword):
|
|
text = str(getattr(node, "attr", None) or getattr(node, "id", None) or getattr(node, "arg", "")).lower()
|
|
elif isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
|
|
text = node.name.lower() # a function called collect_until_pass is the same trick
|
|
else:
|
|
continue
|
|
if any(marker in text for marker in RETRY_MARKERS):
|
|
violations.append(f"{name}:{node.lineno}: {text!r} re-rolls until a target")
|
|
return violations
|
|
|
|
|
|
def _enclosing_tests(break_node: ast.Break, loop: ast.While) -> list[ast.expr]:
|
|
"""Tests guarding this break between it and the loop head; a bare break counts as the loop test."""
|
|
tests: list[ast.expr] = []
|
|
for node in ast.walk(loop):
|
|
if isinstance(node, ast.If) and any(child is break_node for child in node.body):
|
|
tests.append(node.test)
|
|
return tests or [loop.test]
|
|
|
|
|
|
def audit_sources(paths: list[Path]) -> list[str]:
|
|
"""Rule 1 over our own harness: the benchmark code may never re-roll a query."""
|
|
violations: list[str] = []
|
|
for path in paths:
|
|
violations.extend(best_of_violations(path.read_text(encoding="utf-8"), str(path.relative_to(ROOT.parent))))
|
|
return violations
|
|
|
|
|
|
def frozen_counts() -> dict[str, Any]:
|
|
"""The n every frozen report is required to cover."""
|
|
path = SETS / "manifest.json"
|
|
try:
|
|
manifest = json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError) as error:
|
|
raise SystemExit(f"{path}: cannot read the frozen set manifest ({error})") from error
|
|
missing = [key for key in ("queries", "queries_answerable", "modalities") if key not in manifest]
|
|
if missing:
|
|
raise SystemExit(f"{path}: manifest is missing {missing}")
|
|
return manifest
|
|
|
|
|
|
def report_violations(path: Path, manifest: dict[str, Any]) -> list[str]:
|
|
"""Rules 2 and 3 over one report: full n, and a chance row beside every score."""
|
|
try:
|
|
report = json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError) as error:
|
|
return [f"{path.name}: unreadable report ({error})"]
|
|
violations: list[str] = []
|
|
meta = report.get("meta", {})
|
|
if (
|
|
(meta.get("queries") != manifest["queries"] or meta.get("answerable") != manifest["queries_answerable"])
|
|
and not meta.get("limit_per_modality")
|
|
):
|
|
violations.append(
|
|
f"{path.name}: scored {meta.get('queries')}/{meta.get('answerable')} queries but the frozen set "
|
|
f"is {manifest['queries']}/{manifest['queries_answerable']} and no --limit is recorded"
|
|
)
|
|
chance = report.get("chance")
|
|
if chance is None:
|
|
violations.append(f"{path.name}: no chance row, so no score in it can be read as signal")
|
|
scored = report.get("arms") or report.get("stores") or []
|
|
if not scored:
|
|
violations.append(f"{path.name}: no arms or stores scored")
|
|
for entry in scored:
|
|
name = entry.get("arm") or entry.get("store") or "?"
|
|
overall = entry.get("overall") or {}
|
|
for key in ("recall_at_10", "hit_at_10", "hit_at_10_wilson_lower"):
|
|
if key not in overall:
|
|
violations.append(f"{path.name}: {name} is missing {key}")
|
|
if chance is not None and overall.get("recall_at_10") is not None and chance.get("recall_at_10") is None:
|
|
violations.append(f"{path.name}: chance row has no recall_at_10 to compare {name} against")
|
|
return violations
|
|
|
|
|
|
def scorer_is_independent() -> list[str]:
|
|
"""Rule 4a: the scorer is a pure function of ids, so it cannot share the question's generation."""
|
|
path = ROOT / "metrics.py"
|
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
violations: list[str] = []
|
|
for node in ast.walk(tree):
|
|
modules: list[str] = []
|
|
if isinstance(node, ast.Import):
|
|
modules = [alias.name for alias in node.names]
|
|
elif isinstance(node, ast.ImportFrom) and node.module:
|
|
modules = [node.module]
|
|
line = getattr(node, "lineno", 0)
|
|
for module in modules:
|
|
if any(module == banned or module.startswith(f"{banned}.") for banned in SCORER_FORBIDDEN):
|
|
violations.append(f"bench/metrics.py:{line}: imports {module}, which the scorer must not need")
|
|
return violations
|
|
|
|
|
|
def third_party_origins() -> list[str]:
|
|
"""Rule 4b: every gold label must name a dataset we downloaded, at the revision we froze."""
|
|
datasets: dict[str, str] = {}
|
|
try:
|
|
lines = CORPUS_MANIFEST.read_text(encoding="utf-8").splitlines()
|
|
except OSError as error:
|
|
raise SystemExit(f"{CORPUS_MANIFEST}: cannot read the corpus manifest ({error})") from error
|
|
for number, line in enumerate(lines, start=1):
|
|
if not line.strip():
|
|
continue
|
|
try:
|
|
record = json.loads(line)
|
|
datasets[str(record["dataset"])] = str(record["revision"])
|
|
except (json.JSONDecodeError, KeyError) as error:
|
|
raise SystemExit(f"{CORPUS_MANIFEST}:{number}: unreadable record ({error})") from error
|
|
if not datasets:
|
|
raise SystemExit(f"{CORPUS_MANIFEST}: names no dataset")
|
|
violations: list[str] = []
|
|
for modality in MODALITIES:
|
|
path = SETS / f"{modality}.jsonl"
|
|
if not path.is_file():
|
|
violations.append(f"{path}: missing set file")
|
|
continue
|
|
for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
|
|
if not line.strip():
|
|
continue
|
|
try:
|
|
record = json.loads(line)
|
|
except json.JSONDecodeError as error:
|
|
raise SystemExit(f"{path}:{number}: unreadable record ({error})") from error
|
|
origin = record.get("origin") or {}
|
|
dataset = str(origin.get("dataset", ""))
|
|
if dataset not in datasets:
|
|
violations.append(f"bench/sets/{modality}.jsonl:{number}: origin {dataset!r} is not a frozen dataset")
|
|
elif origin.get("revision") != datasets[dataset]:
|
|
violations.append(
|
|
f"bench/sets/{modality}.jsonl:{number}: {dataset} is cited at revision "
|
|
f"{origin.get('revision')}, frozen at {datasets[dataset]}"
|
|
)
|
|
return violations
|
|
|
|
|
|
def injected_cases() -> list[tuple[str, list[str]]]:
|
|
"""Cheating we plant on purpose; the auditor must refuse each one."""
|
|
best_of_n = (
|
|
"def collect(proofs):\n"
|
|
" while len(proofs) < 3:\n"
|
|
" proofs.append(run_once())\n"
|
|
" if pass_rate >= target:\n"
|
|
" break\n"
|
|
)
|
|
no_chance = {"meta": {"queries": 5308, "answerable": 2928}, "arms": [{"arm": "x", "overall": {}}]}
|
|
return [(best_of_n, ["retry-until-score"]), (json.dumps(no_chance), ["no chance row"])]
|
|
|
|
|
|
def self_test(manifest: dict[str, Any]) -> int:
|
|
"""Prove the auditor catches the injected cases; a rule that catches nothing is not a rule."""
|
|
failures = 0
|
|
for source, expected in injected_cases():
|
|
if expected[0] == "retry-until-score":
|
|
found = best_of_violations(source, "injected.py")
|
|
else:
|
|
path = Path("injected_report.json")
|
|
path.write_text(source, encoding="utf-8")
|
|
found = report_violations(path, manifest)
|
|
path.unlink()
|
|
if not found:
|
|
print(f"SELF-TEST FAIL: the auditor accepted an injected case ({expected[0]})")
|
|
failures += 1
|
|
else:
|
|
print(f"self-test ok: caught {expected[0]} -> {found[0]}")
|
|
return failures
|
|
|
|
|
|
def product_sources(root: Path) -> list[Path]:
|
|
"""The product's proof suite, wherever it lives now: it moved out of one evals.py into a package."""
|
|
package = root / "backend" / "src" / "ely_eye" / "proofs"
|
|
if not package.is_dir():
|
|
raise SystemExit(f"{package}: --product asked for the proof suite but it is not there")
|
|
sources = sorted(package.glob("*.py"))
|
|
if not sources:
|
|
raise SystemExit(f"{package}: the proof suite holds no python file")
|
|
return sources
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("--self-test", action="store_true", help="check the auditor against injected cheating")
|
|
parser.add_argument("--product", action="store_true", help="also audit the product's proof suite for re-rolling")
|
|
args = parser.parse_args()
|
|
|
|
manifest = frozen_counts()
|
|
if args.self_test:
|
|
return 1 if self_test(manifest) else 0
|
|
|
|
sources = [
|
|
path
|
|
for path in sorted(ROOT.rglob("*.py"))
|
|
if "tests" not in path.parts and "corpus" not in path.parts and path.name != "audit.py"
|
|
]
|
|
violations = audit_sources(sources)
|
|
if args.product:
|
|
for path in product_sources(ROOT.parent):
|
|
name = str(path.relative_to(ROOT.parent))
|
|
violations.extend(best_of_violations(path.read_text(encoding="utf-8"), name))
|
|
|
|
for name in FROZEN_REPORTS:
|
|
path = REPORTS / name
|
|
if path.is_file():
|
|
violations.extend(report_violations(path, manifest))
|
|
else:
|
|
violations.append(f"{path}: frozen report missing")
|
|
violations.extend(scorer_is_independent())
|
|
violations.extend(third_party_origins())
|
|
|
|
print(f"audited {len(sources)} harness files, {len(FROZEN_REPORTS)} reports, {len(MODALITIES)} set files")
|
|
if violations:
|
|
for violation in violations:
|
|
print(f"VIOLATION: {violation}", file=sys.stderr)
|
|
print(f"{len(violations)} violation(s)", file=sys.stderr)
|
|
return 1
|
|
print("clean: single attempt, full n, chance rows present, producer is not verifier")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|