Spaces:
Running
Running
File size: 7,019 Bytes
e2d54c9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | """Recover raw per-round tables from orx run logs.
This project runs in orx LOCAL mode, where `orx artifacts` does not exist and the run
log is the only evidence channel. The expensive stages therefore print every per-round
metric to stdout, and this script parses those lines back into CSV so the numbers can
be published as downloadable raw data next to the claims that rest on them.
Theory-stage artifacts are NOT recovered this way: those stages are deterministic and
run in seconds, so they are regenerated by rerunning them, which is a stronger
guarantee than parsing text.
Usage:
python tools/extract_evidence.py <out_dir> <label>=<run_id> [...]
"""
from __future__ import annotations
import csv
import hashlib
import json
import re
import subprocess
import sys
from pathlib import Path
# Numbers may be negative ("H=-0.0000" for a fully collapsed round), so every
# numeric group allows a leading minus -- without it those rows are silently dropped,
# which loses exactly the collapsed rounds the comparison depends on.
# " round 7 H(L)=1.9312 mean_len= 18.4 distinct= 31 nearA=0.14 nearB=0.09 loss=3.201 74s"
TEXT_ROUND = re.compile(
r"round\s+(\d+)\s+H\(L\)=(-?[\d.]+)\s+mean_len=\s*(-?[\d.]+)\s+distinct=\s*(\d+)\s+"
r"nearA=(-?[\d.nan]+)\s+nearB=(-?[\d.nan]+)\s+loss=(-?[\d.]+)\s+(\d+)s"
)
# " round 3 H=1.9312 KL=0.0821 featVar=0.412 intraVar=0.377 classes=10 91s"
FLOW_ROUND = re.compile(
r"round\s+(\d+)\s+H=(-?[\d.]+)\s+KL=(-?[\d.]+)\s+featVar=(-?[\d.]+)\s+"
r"intraVar=(-?[\d.]+)\s+classes=(\d+)\s+(\d+)s"
)
KV = re.compile(r"^\s{4}(\S.*?)\s{2,}(.+?)\s*$")
EMIT_BEGIN = re.compile(
r"^<<<ORX-ARTIFACT path=(\S+) bytes=(\d+) sha256=([0-9a-f]{64}) "
r"encoding=(\S+) status=(\S+)"
)
def recover_artifacts(log: str, dest: Path) -> list[dict]:
"""Rebuild the artifact files a job wrote, from the framed blocks in its log.
Each block declares its own length and SHA-256, so a truncated or interleaved
log is rejected rather than silently yielding a corrupt file.
"""
import base64
recovered, lines, i = [], log.splitlines(), 0
while i < len(lines):
m = EMIT_BEGIN.match(lines[i])
if not m:
i += 1
continue
rel, size, digest, encoding, status = m.groups()
i += 1
payload = []
while i < len(lines) and not lines[i].startswith(">>>ORX-ARTIFACT-END"):
payload.append(lines[i].strip())
i += 1
i += 1
rec = {"path": rel, "declared_bytes": int(size), "declared_sha256": digest,
"status": status, "ok": False}
if encoding == "base64" and status == "OK":
try:
raw = base64.b64decode("".join(payload), validate=True)
except Exception as exc: # noqa: BLE001 - report, never guess
rec["error"] = f"base64 decode failed: {exc}"
else:
actual = hashlib.sha256(raw).hexdigest()
rec["actual_bytes"], rec["actual_sha256"] = len(raw), actual
if actual == digest and len(raw) == int(size):
out = dest / rel
out.parent.mkdir(parents=True, exist_ok=True)
out.write_bytes(raw)
rec["ok"] = True
else:
rec["error"] = "hash/length mismatch — log is truncated or interleaved"
else:
rec["error"] = f"not recoverable (encoding={encoding} status={status})"
recovered.append(rec)
return recovered
def fetch(run_id: str) -> str:
"""Fetch the WHOLE log.
`orx logs` tails by default, so a byte budget smaller than the log silently
drops the beginning — which, with artifact frames spread through the run,
means silently losing evidence. Ask for far more than any run produces.
"""
return subprocess.run(
["orx", "logs", run_id, "--bytes", "100000000"],
capture_output=True, text=True, check=True,
).stdout
def parse(log: str) -> dict:
text_rows, flow_rows = [], []
for m in TEXT_ROUND.finditer(log):
text_rows.append({
"round": int(m.group(1)), "H_L": float(m.group(2)),
"mean_len": float(m.group(3)), "n_distinct_lengths": int(m.group(4)),
"frac_near_T_A": float(m.group(5)), "frac_near_T_B": float(m.group(6)),
"train_loss": float(m.group(7)), "seconds": int(m.group(8)),
})
for m in FLOW_ROUND.finditer(log):
flow_rows.append({
"round": int(m.group(1)), "class_entropy": float(m.group(2)),
"kl_to_uniform": float(m.group(3)), "feature_variance": float(m.group(4)),
"intra_class_variance": float(m.group(5)),
"n_classes_present": int(m.group(6)), "seconds": int(m.group(7)),
})
meta = {}
for line in log.splitlines():
m = KV.match(line)
if m and len(m.group(1)) < 60:
meta.setdefault(m.group(1).strip(), m.group(2).strip())
verdicts = re.findall(r"VERDICT (\S+): (VERIFIED|FALSIFIED|BLOCKED)", log)
gates = re.findall(r"GATE: (PASS|FAIL)", log)
return {
"text_rounds": text_rows, "flow_rounds": flow_rows, "meta": meta,
"verdicts": [{"claim": c, "status": s} for c, s in verdicts],
"gates": gates,
"all_gates_pass": bool(gates) and all(g == "PASS" for g in gates),
"exit_ok": "OK: every recorded verdict is supported by its own evidence." in log,
}
def main() -> int:
if len(sys.argv) < 3:
print(__doc__)
return 2
out = Path(sys.argv[1])
out.mkdir(parents=True, exist_ok=True)
index = {}
for spec in sys.argv[2:]:
label, run_id = spec.split("=", 1)
log = fetch(run_id)
(out / f"log_{label}.txt").write_text(log)
d = parse(log)
d["run_id"] = run_id
d["artifacts"] = recover_artifacts(log, out / "artifacts" / label)
bad = [a for a in d["artifacts"] if not a["ok"]]
print(f"{label}: recovered {len(d['artifacts']) - len(bad)}/{len(d['artifacts'])} artifacts"
+ (f" ({len(bad)} FAILED)" if bad else ""))
for a in bad:
print(f" !! {a['path']}: {a.get('error')}")
for key, rows in (("text_rounds", d["text_rounds"]), ("flow_rounds", d["flow_rounds"])):
if rows:
p = out / f"rounds_{label}.csv"
with p.open("w", newline="") as fh:
w = csv.DictWriter(fh, fieldnames=list(rows[0]))
w.writeheader()
w.writerows(rows)
print(f"{label}: {len(rows)} rounds -> {p}")
index[label] = d
(out / f"meta_{label}.json").write_text(json.dumps(d, indent=2))
(out / "index.json").write_text(json.dumps(index, indent=2))
print(f"wrote {out/'index.json'}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|