Files
troupe-standup/wizard-work/sphinx/archive-lcmm-token-ledger-2026-10-26/run_verdict.py
T
volt dd106f1ee2 volt: Q72 -- grade the lcmm ledger's population and its role field (169 -> 183 checks, 20/20 mutants, 3 legs measured RED on the shipped tree)
- P1-P9: 327 scanned -> 18 candidates -> 15 kept re-derived from source/scan-probe-2026.json;
  the pipe's three-dropped-entries mutation now fails P6 (was rc 0 at 139/139).
- B7-B10: claim_role graded against roles-expected.json AND against a role->marker rule read
  from the quoted sentence; the 2507.02001v1 relabel now fails B8 and B10 (was rc 0 at 169/169).
- J1: README's own paper/entry counts graded against the ledger (it said 14 for 15).
- run_verdict.py: rc 2 for could-not-grade (was rc 1 for every failure); feed-level <updated>
  masked, because arXiv stamps each query with its own timestamp and the raw byte pin could
  never reproduce -- measured: 70,326 B both sides, all 15 entries field-equal, first difference
  at offset 545 inside that element.
- mutation_battery.py: scratch from --scratch/TMPDIR and removed on exit (was a hard-coded
  /root/.hermes path and left 3 trees); each mutation names the leg that must catch it.

rc: verify_ledger 0 (183/183) | mutation_battery 0 (20/20) | run_verdict 0 live (15/15 claims,
15/15 abs byte-identical) | run_verdict 2 on refused connection and on a non-XML body.
2026-10-26 00:32:58 +00:00

193 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""Live re-fetch verdict: does arXiv still serve exactly what the ledger pinned?
Re-fetches the Atom feed for the ledger's ids and every abstract page, compares
sha256 with the pinned bytes, and re-checks every claim sentence against the
live abstract. Needs network.
Usage: python3 run_verdict.py [ledger_dir] [--api URL] [--abs URL] [--timeout SECS]
Exit 0 = live arXiv == pinned ledger.
Exit 1 = live bytes differ from the ledger (a real drift, with the entries named).
Exit 2 = could not grade: the endpoint refused, timed out, or served a body that
is not an Atom feed. An outage must not read as a byte mismatch, which
is what this script did before queue item Q72 -- every failure, network
or content, came back as rc 1.
"""
import hashlib
import json
import re
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from pathlib import Path
NS = {"a": "http://www.w3.org/2005/Atom"}
API = "https://export.arxiv.org/api/query"
ABS = "https://export.arxiv.org/abs/"
UA = "sphinx-archive-lane/1.0 (live verdict)"
_args = sys.argv[1:]
_timeout = 90
for _a in _args:
if _a.startswith("--api="):
API = _a.split("=", 1)[1]
elif _a.startswith("--abs="):
ABS = _a.split("=", 1)[1]
elif _a.startswith("--timeout="):
_timeout = float(_a.split("=", 1)[1])
_pos = [a for a in _args if not a.startswith("--")]
ROOT = Path(_pos[0] if _pos else Path(__file__).resolve().parent).resolve()
L = json.loads((ROOT / "ledger.json").read_text())
entries = L["entries"]
class Ungradable(Exception):
"""The endpoint could not be read; nothing about the ledger was learned."""
# arXiv stamps every Atom response with the time of the request:
# <updated>2026-10-26T00:27:06Z</updated> (feed level, first <updated>)
# Two consecutive identical requests an hour apart differed in exactly that
# element and in nothing else (same 70,326 B, all 15 entries field-equal,
# first byte difference at offset 545 inside the element). A byte pin on the
# raw feed therefore fails on the second request by construction, so the live
# leg masks that one element in BOTH bodies and then demands byte equality of
# everything else -- entry order, ids, titles, summaries, dates, categories.
FEED_UPDATED = re.compile(rb"(<updated>)[^<]*(</updated>)")
def mask_feed_updated(b):
return FEED_UPDATED.sub(rb"\1<request-time>\2", b, count=1)
def fetch(url):
req = urllib.request.Request(url, headers={"User-Agent": UA})
try:
with urllib.request.urlopen(req, timeout=_timeout) as r:
body = r.read()
except urllib.error.HTTPError as ex:
raise Ungradable(f"HTTP {ex.code} {ex.reason} from {url}")
except urllib.error.URLError as ex:
raise Ungradable(f"unreachable {url}: {ex.reason}")
except Exception as ex: # timeout, reset, TLS
raise Ungradable(f"{type(ex).__name__} reading {url}: {ex}")
if not body:
raise Ungradable(f"empty body from {url}")
return body
def atom_root(body, url):
try:
root = ET.fromstring(body)
except ET.ParseError as ex:
raise Ungradable(f"body from {url} is not XML ({ex}); {len(body)} B, starts {body[:40]!r}")
return root
def norm(s):
return re.sub(r"\s+", " ", s).strip()
def main():
fails = [] # rc 1: the live bytes disagree with the ledger
ungraded = [] # rc 2: we could not see the live bytes at all
ids = ",".join(e["arxiv_id"] for e in entries)
try:
live_atom = fetch(API + "?" + urllib.parse.urlencode({"id_list": ids, "start": 0, "max_results": len(entries) + 5}))
root = atom_root(live_atom, API)
except Ungradable as ex:
print(f" [UNGRADED] {ex}")
print("VERDICT: UNGRADED -- live arXiv could not be read; nothing claimed about the ledger")
return 2
pinned_src = next(s for s in entries[0]["sources"] if s["kind"] == "arxiv_api_atom")
try:
pinned_atom = (ROOT / pinned_src["path"]).read_bytes()
except OSError as ex:
print(f" [UNGRADED] pinned Atom unreadable: {ex}")
print("VERDICT: UNGRADED -- pinned bytes missing; nothing to compare against")
return 2
raw_live, raw_pin = hashlib.sha256(live_atom).hexdigest(), hashlib.sha256(pinned_atom).hexdigest()
ok = hashlib.sha256(mask_feed_updated(live_atom)).hexdigest() == hashlib.sha256(mask_feed_updated(pinned_atom)).hexdigest()
print(f" [{'PASS' if ok else 'FAIL'}] live Atom content identical to pinned, feed <updated> masked "
f"({raw_live[:12]} vs {raw_pin[:12]})")
if raw_live != raw_pin and ok:
print(" [note] raw bytes differ only in the feed-level <updated> stamp arXiv sets per request")
if not ok:
fails.append("atom content differs")
a, b = mask_feed_updated(live_atom), mask_feed_updated(pinned_atom)
i = next((k for k in range(min(len(a), len(b))) if a[k] != b[k]), min(len(a), len(b)))
print(f" [note] first masked difference at offset {i}: live={a[i:i + 60]!r} pinned={b[i:i + 60]!r}")
live = {}
for el in root.findall("a:entry", NS):
live[el.find("a:id", NS).text.strip().rsplit("/", 1)[-1]] = el
if not live:
print(f" [UNGRADED] live feed parsed but carries 0 entries for {len(entries)} ids")
print("VERDICT: UNGRADED -- empty live feed")
return 2
n_claim = n_abs = n_ver = 0
for e in entries:
aid = e["arxiv_id"]
el = live.get(aid)
if el is None:
print(f" [FAIL] {aid} missing from live feed")
fails.append(f"{aid} missing")
continue
if norm(e["claim_sentence"]) in norm(el.find("a:summary", NS).text):
n_claim += 1
else:
print(f" [FAIL] {aid} claim sentence not in live abstract")
fails.append(f"{aid} claim")
try:
b = fetch(ABS + aid)
except Ungradable as ex:
print(f" [UNGRADED] {aid} abs page: {ex}")
ungraded.append(f"{aid} abs unreadable")
continue
if hashlib.sha256(b).hexdigest() == next(s["sha256"] for s in e["sources"] if s["kind"] == "arxiv_abs_page"):
n_abs += 1
else:
print(f" [FAIL] {aid} live abs page differs from pinned")
fails.append(f"{aid} abs")
# newest version of the unversioned id
base = aid.split("v")[0]
try:
u = atom_root(fetch(API + "?" + urllib.parse.urlencode({"id_list": base, "start": 0, "max_results": 1})), API)
got = u.findall("a:entry", NS)
if len(got) != 1:
print(f" [note] version probe for {base} returned {len(got)} entries; not graded")
else:
gid = got[0].find("a:id", NS)
newest = gid.text.strip().rsplit("/", 1)[-1] if gid is not None and gid.text else ""
if newest and newest != aid:
print(f" [note] {aid} is not the newest version; arXiv now has {newest}")
except (Ungradable, AttributeError) as ex:
print(f" [note] version probe failed for {base}: {ex}")
time.sleep(1)
n = len(entries)
print(f" claims present live: {n_claim}/{n}")
print(f" abs pages identical live: {n_abs}/{n}")
print(f" abs pages unreadable: {len(ungraded)}")
print(f" [{'PASS' if n_claim == n else 'FAIL'}] all claims present in live abstracts")
print(f" [{'PASS' if n_abs == n and not ungraded else 'FAIL'}] all abs pages byte-identical live")
if n_claim != n:
fails.append("claims missing live")
if fails:
print("VERDICT:", f"LIVE DRIFT: {fails}")
return 1
if ungraded:
print("VERDICT:", f"UNGRADED: {ungraded} -- drift not observed, so not claimed")
return 2
print("VERDICT:", "LIVE ARXIV MATCHES LEDGER")
return 0
if __name__ == "__main__":
sys.exit(main())