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.
This commit is contained in:
volt committed 2026-10-26 00:32:58 +00:00
1 parent 12f956a515
commit dd106f1ee2
6 files changed
+476 -78

No files matched your search

@@ -1,20 +1,24 @@
# archive-lcmm-token-ledger-2026-10-26 # archive-lcmm-token-ledger-2026-10-26
A machine-verified ledger of long-context multimodal papers: for each of 14 papers, the context A machine-verified ledger of long-context multimodal papers: for each of 15 papers, the context
length the paper itself states in its own abstract, copied verbatim, with two pinned and length the paper itself states in its own abstract, copied verbatim, with two pinned and
sha256-hashed arXiv sources per entry. sha256-hashed arXiv sources per entry.
- `ledger.json` -- the ledger (14 entries, each with verbatim claim sentence, figure, token count, - `ledger.json` -- the ledger (15 entries, each with verbatim claim sentence, figure, token count,
role, metadata, source hashes) role, metadata, source hashes)
- `roles-expected.json` -- the expected `claim_role` per entry, with the verbatim phrase from that
entry's own sentence that justifies it; `verify_ledger.py` grades the ledger against it
- `REPORT.md` -- the digest; `scan-candidates.json` -- the full >=32K scan with exclusions - `REPORT.md` -- the digest; `scan-candidates.json` -- the full >=32K scan with exclusions
- `source/atom-lcmm.xml`, `source/abs/*.html`, `source/scan-probe-2026.json` -- pinned bytes - `source/atom-lcmm.xml`, `source/abs/*.html`, `source/scan-probe-2026.json` -- pinned bytes
- `verify_ledger.py` (offline), `mutation_battery.py` (13 mutations), `run_verdict.py` (live) - `verify_ledger.py` (offline, 183 checks), `mutation_battery.py` (mutation battery),
`run_verdict.py` (live)
- `manifest.json` -- sha256 of every file - `manifest.json` -- sha256 of every file
Reproduce: Reproduce:
``` ```
python3 verify_ledger.py # exit 0 = ledger holds python3 verify_ledger.py # exit 0 = ledger holds, 1 = a check failed
python3 mutation_battery.py # exit 0 = 13/13 mutations caught python3 mutation_battery.py # exit 0 = every mutation caught (and the control held)
python3 run_verdict.py # exit 0 = live arXiv matches pinned bytes (needs network) python3 run_verdict.py # exit 0 = live arXiv matches pinned bytes (needs network),
# exit 1 = bytes differ, exit 2 = could not grade (endpoint down)
``` ```
@@ -1,13 +1,14 @@
{ {
"EVIDENCE-runs.txt": "0174c4f5f3e718a371ddf33388eb579c0c1321e9f953cc86941fa5ac20ebfd15", "EVIDENCE-runs.txt": "0174c4f5f3e718a371ddf33388eb579c0c1321e9f953cc86941fa5ac20ebfd15",
"README.md": "ff76c1a71ab1363c4868aacea1e75a4a9bbd96db7f8ab2df0a9e93ce9f057ab4", "README.md": "6b85987907facf24dfdfdb61d474fa39ef30251b43fae59acc82fae370ad340c",
"REPORT.md": "643e5b8b3b59ed06d8bb4a63be25f057ebcb2452984a4af852de09fbb1cc6bf3", "REPORT.md": "643e5b8b3b59ed06d8bb4a63be25f057ebcb2452984a4af852de09fbb1cc6bf3",
"build_ledger.py": "3dc27281779f69e13813087f3996861f2bb7e41a75decce62038e1a720c9e4f5", "build_ledger.py": "3dc27281779f69e13813087f3996861f2bb7e41a75decce62038e1a720c9e4f5",
"build_report.py": "5617f4e5fb0691452016e8e6d9b083546caa871043148556bd446055f4ab12a7", "build_report.py": "5617f4e5fb0691452016e8e6d9b083546caa871043148556bd446055f4ab12a7",
"finalize_manifest.py": "393262c20d639ed921953bc3ee30c809df881b3ef275626085130394970139dc", "finalize_manifest.py": "393262c20d639ed921953bc3ee30c809df881b3ef275626085130394970139dc",
"ledger.json": "8fdac60921a221ccc07441150f372fa614bbed3bbd547f43bd4d71328fd54634", "ledger.json": "8fdac60921a221ccc07441150f372fa614bbed3bbd547f43bd4d71328fd54634",
"mutation_battery.py": "e653666ecfc54ea5bdc0d6a6431709ba16cebaa00700c684c1649824af0a225a", "mutation_battery.py": "aadd5f49a8f5ff96756c5aa8336b21b937f8b45c7b1c31482ec9aea5653ce2ca",
"run_verdict.py": "a425b3b5b532e82d78847126b401edd2ef3cbc61edfc43ea260f749ac9bf61e3", "roles-expected.json": "892231f358cf67757e87cad484bbbbd15182a756c8ac9d6cd21d9cada829e49c",
"run_verdict.py": "d667fccbc1ddc01789e00901aae3f6f46873a59c55023c7997b2ebaa9c0b8283",
"scan-candidates.json": "93b6d61bf2880cb8ffa13a278df24e1ca53ee717b3b74ba59cacb28e8cf4ca2b", "scan-candidates.json": "93b6d61bf2880cb8ffa13a278df24e1ca53ee717b3b74ba59cacb28e8cf4ca2b",
"source/abs/2410.07073v2.html": "2320392f1558da6947c2c614e96373eb00e8adefff9a70386f6d989e2d43c290", "source/abs/2410.07073v2.html": "2320392f1558da6947c2c614e96373eb00e8adefff9a70386f6d989e2d43c290",
"source/abs/2412.09616v2.html": "e37052780bba835e97c451bddf1a6979724bcd211ce30e69e43e4cb762ca14d2", "source/abs/2412.09616v2.html": "e37052780bba835e97c451bddf1a6979724bcd211ce30e69e43e4cb762ca14d2",
@@ -26,5 +27,5 @@
"source/abs/2610.15542v1.html": "ac722cac8c75f77867b01f3f2f4ffe0f4e8d85401f0111f1db01377a79817c57", "source/abs/2610.15542v1.html": "ac722cac8c75f77867b01f3f2f4ffe0f4e8d85401f0111f1db01377a79817c57",
"source/atom-lcmm.xml": "7e2add7e0d3eb44c19b17cc4ff36225c0e26643cf9185ba2a421e08f1a6b9942", "source/atom-lcmm.xml": "7e2add7e0d3eb44c19b17cc4ff36225c0e26643cf9185ba2a421e08f1a6b9942",
"source/scan-probe-2026.json": "de026197689120e8d68fb7203ba78df3e5c59b314bbc5d5af14f08e75f7c0fd2", "source/scan-probe-2026.json": "de026197689120e8d68fb7203ba78df3e5c59b314bbc5d5af14f08e75f7c0fd2",
"verify_ledger.py": "3a24e3f171989d6dc3e747b05c3600e5d3bf99573e066e82620a190122a5387e" "verify_ledger.py": "ba0e05efb8483e53f32162e432a2e5551cd252b787ffba81742c5f1a1c71ee5f"
} }
@@ -4,8 +4,20 @@
Copies the ledger to a scratch dir, applies each mutation, runs verify_ledger.py Copies the ledger to a scratch dir, applies each mutation, runs verify_ledger.py
as a subprocess and asserts a non-zero exit. Also asserts the pristine copy as a subprocess and asserts a non-zero exit. Also asserts the pristine copy
passes. Exit 0 = every mutation was caught (and the control held). passes. Exit 0 = every mutation was caught (and the control held).
Two things changed after queue item Q72:
* the scratch tree is taken from --scratch, else $TMPDIR, else the system
temp dir, and is removed on the way out (--keep leaves it). The first
version hard-coded an absolute path under the author's own home, so on a
host without that directory mkdtemp raised FileNotFoundError before grading
anything, and it left its trees behind where it did run;
* a mutation can name the check that must catch it. Without that, a
manifest-hash failure (any edit to ledger.json invalidates manifest.json)
masks whether the leg under test is awake at all -- every mutation would
look caught for the wrong reason.
""" """
import json import json
import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
@@ -28,20 +40,25 @@ def run_verify(root):
p = subprocess.run([sys.executable, str(ROOT / "verify_ledger.py"), str(root)], p = subprocess.run([sys.executable, str(ROOT / "verify_ledger.py"), str(root)],
capture_output=True, text=True) capture_output=True, text=True)
verdict = [l for l in p.stdout.splitlines() if l.startswith("VERDICT:")] verdict = [l for l in p.stdout.splitlines() if l.startswith("VERDICT:")]
return p.returncode, (verdict[-1] if verdict else "?") failed = [l.strip()[7:].split(" -- ")[0] for l in p.stdout.splitlines() if l.strip().startswith("[FAIL]")]
return p.returncode, (verdict[-1] if verdict else "?"), failed
def save_roles(root, R):
(root / "roles-expected.json").write_text(json.dumps(R, indent=1, ensure_ascii=False) + "\n")
MUTATIONS = [] MUTATIONS = []
def mutation(name): def mutation(name, expect=None):
def deco(fn): def deco(fn):
MUTATIONS.append((name, fn)) MUTATIONS.append((name, fn, expect))
return fn return fn
return deco return deco
@mutation("M1 context_tokens inflated by 1") @mutation("M1 context_tokens inflated by 1", "E:")
def m1(root): def m1(root):
L = load(root) L = load(root)
e = next(x for x in L["entries"] if x["claim_role"] == "model_context_window") e = next(x for x in L["entries"] if x["claim_role"] == "model_context_window")
@@ -49,14 +66,15 @@ def m1(root):
save(root, L) save(root, L)
@mutation("M2 title word altered") @mutation("M2 title word altered", "F:")
def m2(root): def m2(root):
L = load(root) L = load(root)
L["entries"][0]["title"] = L["entries"][0]["title"].replace(" ", " ", 1) t = L["entries"][0]["title"]
L["entries"][0]["title"] = t.replace(t.split(" ")[0], t.split(" ")[0] + "x", 1)
save(root, L) save(root, L)
@mutation("M3 claim sentence edited (token -> token!)") @mutation("M3 claim sentence edited (token -> token!)", "D:")
def m3(root): def m3(root):
L = load(root) L = load(root)
e = L["entries"][0] e = L["entries"][0]
@@ -64,7 +82,7 @@ def m3(root):
save(root, L) save(root, L)
@mutation("M4 pinned source sha256 flipped") @mutation("M4 pinned source sha256 flipped", "C:")
def m4(root): def m4(root):
L = load(root) L = load(root)
h = L["entries"][0]["sources"][0]["sha256"] h = L["entries"][0]["sources"][0]["sha256"]
@@ -72,21 +90,21 @@ def m4(root):
save(root, L) save(root, L)
@mutation("M5 published date shifted") @mutation("M5 published date shifted", "F:")
def m5(root): def m5(root):
L = load(root) L = load(root)
L["entries"][0]["published"] = "1999-01-01T00:00:00Z" L["entries"][0]["published"] = "1999-01-01T00:00:00Z"
save(root, L) save(root, L)
@mutation("M6 primary_category swapped") @mutation("M6 primary_category swapped", "F:")
def m6(root): def m6(root):
L = load(root) L = load(root)
L["entries"][0]["primary_category"] = "cs.XX" L["entries"][0]["primary_category"] = "cs.XX"
save(root, L) save(root, L)
@mutation("M7 entry dropped (n_entries kept in sync)") @mutation("M7 entry dropped (n_entries kept in sync)", "P6")
def m7(root): def m7(root):
L = load(root) L = load(root)
L["entries"] = L["entries"][:-1] L["entries"] = L["entries"][:-1]
@@ -94,14 +112,14 @@ def m7(root):
save(root, L) save(root, L)
@mutation("M8 bogus claim_role") @mutation("M8 bogus claim_role", "B3")
def m8(root): def m8(root):
L = load(root) L = load(root)
L["entries"][0]["claim_role"] = "magic_number" L["entries"][0]["claim_role"] = "magic_number"
save(root, L) save(root, L)
@mutation("M9 REPORT.md citation removed") @mutation("M9 REPORT.md citation removed", "H1")
def m9(root): def m9(root):
rep = root / "REPORT.md" rep = root / "REPORT.md"
L = load(root) L = load(root)
@@ -109,7 +127,7 @@ def m9(root):
rep.write_text(rep.read_text().replace(aid, "0000.00000")) rep.write_text(rep.read_text().replace(aid, "0000.00000"))
@mutation("M10 pinned abs page bytes tampered") @mutation("M10 pinned abs page bytes tampered", "C:")
def m10(root): def m10(root):
L = load(root) L = load(root)
p = root / L["entries"][0]["sources"][1]["path"] p = root / L["entries"][0]["sources"][1]["path"]
@@ -118,7 +136,7 @@ def m10(root):
p.write_bytes(bytes(b)) p.write_bytes(bytes(b))
@mutation("M11 manifest entry deleted") @mutation("M11 manifest entry deleted", "I1")
def m11(root): def m11(root):
m = json.loads((root / "manifest.json").read_text()) m = json.loads((root / "manifest.json").read_text())
k = next(iter(m)) k = next(iter(m))
@@ -126,7 +144,7 @@ def m11(root):
(root / "manifest.json").write_text(json.dumps(m, indent=1) + "\n") (root / "manifest.json").write_text(json.dumps(m, indent=1) + "\n")
@mutation("M12 manifest hash stale") @mutation("M12 manifest hash stale", "I2")
def m12(root): def m12(root):
m = json.loads((root / "manifest.json").read_text()) m = json.loads((root / "manifest.json").read_text())
k = next(iter(m)) k = next(iter(m))
@@ -134,13 +152,77 @@ def m12(root):
(root / "manifest.json").write_text(json.dumps(m, indent=1) + "\n") (root / "manifest.json").write_text(json.dumps(m, indent=1) + "\n")
@mutation("M13 claim_note emptied (honesty field)") @mutation("M13 claim_note emptied (honesty field)", "B6")
def m13(root): def m13(root):
L = load(root) L = load(root)
L["entries"][0]["claim_note"] = "" L["entries"][0]["claim_note"] = ""
save(root, L) save(root, L)
@mutation("M14 three entries dropped, n_entries + manifest recomputed (the Q72 mutation)", "P6")
def m14(root):
drop = {"2608.08630v1", "2610.15542v1", "2607.14548v1"}
L = load(root)
L["entries"] = [e for e in L["entries"] if e["arxiv_id"] not in drop]
L["n_entries"] = len(L["entries"])
L["api_query"] = "https://export.arxiv.org/api/query?id_list=" + ",".join(e["arxiv_id"] for e in L["entries"])
save(root, L)
rep = root / "REPORT.md"
rep.write_text("\n".join(l for l in rep.read_text().splitlines() if not any(d in l for d in drop)) + "\n")
m = json.loads((root / "manifest.json").read_text())
m["ledger.json"] = __import__("hashlib").sha256((root / "ledger.json").read_bytes()).hexdigest()
m["REPORT.md"] = __import__("hashlib").sha256((root / "REPORT.md").read_bytes()).hexdigest()
(root / "manifest.json").write_text(json.dumps(m, indent=1) + "\n")
@mutation("M15 claim_role relabelled to model_context_window on 2507.02001v1 (the Q72 mutation)", "B8")
def m15(root):
L = load(root)
e = next(x for x in L["entries"] if x["arxiv_id"] == "2507.02001v1")
e["claim_role"] = "model_context_window"
e["claim_note"] = "Model context window is 32K tokens."
save(root, L)
@mutation("M16 excluded candidate's reason blanked", "P7")
def m16(root):
s = json.loads((root / "scan-candidates.json").read_text())
ex = next(c for c in s["candidates"] if c["decision"] == "excluded")
ex["reason"] = " "
(root / "scan-candidates.json").write_text(json.dumps(s, indent=1, ensure_ascii=False) + "\n")
@mutation("M17 README paper count one lower", "J1")
def m17(root):
p = root / "README.md"
p.write_text(p.read_text().replace("for each of 15 papers", "for each of 14 papers"))
@mutation("M18 ledger entry invented outside the scan", "P5")
def m18(root):
L = load(root)
e = json.loads(json.dumps(L["entries"][0]))
e["arxiv_id"] = "2699.99999v1"
L["entries"].append(e)
L["n_entries"] = len(L["entries"])
save(root, L)
@mutation("M19 expected-role table disagrees with the ledger", "B8")
def m19(root):
R = json.loads((root / "roles-expected.json").read_text())
R["roles"]["2410.07073v2"]["claim_role"] = "benchmark_input_lengths"
save_roles(root, R)
@mutation("M20 candidate dropped from the scan (population shrink)", "P4")
def m20(root):
s = json.loads((root / "scan-candidates.json").read_text())
s["candidates"] = s["candidates"][:-1]
s["n_candidates"] = len(s["candidates"])
(root / "scan-candidates.json").write_text(json.dumps(s, indent=1, ensure_ascii=False) + "\n")
def fresh_copy(dest): def fresh_copy(dest):
if dest.exists(): if dest.exists():
shutil.rmtree(dest) shutil.rmtree(dest)
@@ -149,28 +231,40 @@ def fresh_copy(dest):
def main(): def main():
tmp = Path(tempfile.mkdtemp(prefix="lcmm-mut-", dir="/root/.hermes/profiles/sphinx/cache/scratch")) keep = "--keep" in sys.argv
print(f"scratch: {tmp}") scratch = os.environ.get("LCMM_SCRATCH") or tempfile.gettempdir()
control = fresh_copy(tmp / "control") for a in sys.argv[1:]:
rc, verdict = run_verify(control) if a.startswith("--scratch="):
print(f" [{'PASS' if rc == 0 else 'FAIL'}] control (pristine copy must verify) -> rc={rc} {verdict}") scratch = a.split("=", 1)[1]
if rc != 0: Path(scratch).mkdir(parents=True, exist_ok=True)
print("control FAILED; aborting battery") tmp = Path(tempfile.mkdtemp(prefix="lcmm-mut-", dir=scratch))
return 1 print(f"scratch: {tmp} (removed on exit unless --keep)")
try:
control = fresh_copy(tmp / "control")
rc, verdict, failed = run_verify(control)
print(f" [{'PASS' if rc == 0 else 'FAIL'}] control (pristine copy must verify) -> rc={rc} {verdict}")
if rc != 0:
print("control FAILED; aborting battery")
return 1
caught = 0 caught = 0
for name, fn in MUTATIONS: for name, fn, expect in MUTATIONS:
d = fresh_copy(tmp / name.replace(" ", "_")) d = fresh_copy(tmp / name.split(" ")[0])
fn(d) fn(d)
rc, verdict = run_verify(d) rc, verdict, failed = run_verify(d)
ok = rc != 0 hit = [f for f in failed if expect and f.startswith(expect)]
caught += ok ok = rc != 0 and (not expect or bool(hit))
print(f" [{'PASS' if ok else 'FAIL'}] {name} -> rc={rc} {verdict}") caught += ok
why = "" if ok else f" <- wanted a {expect} failure, saw {failed[:4]}"
print(f" [{'PASS' if ok else 'FAIL'}] {name} -> rc={rc} {verdict}{why}")
total = len(MUTATIONS) total = len(MUTATIONS)
print(f"\n{caught}/{total} mutations caught") print(f"\n{caught}/{total} mutations caught")
print("VERDICT:", "FALSIFIABLE" if caught == total else "NOT FALSIFIABLE") print("VERDICT:", "FALSIFIABLE" if caught == total else "NOT FALSIFIABLE")
return 0 if caught == total else 1 return 0 if caught == total else 1
finally:
if not keep:
shutil.rmtree(tmp, ignore_errors=True)
if __name__ == "__main__": if __name__ == "__main__":
@@ -0,0 +1,81 @@
{
"schema": "sphinx.lcmm-roles/1",
"note": "Expected claim_role per ledger entry, with the verbatim phrase from that entry's own claim sentence that justifies it. verify_ledger.py grades ledger.json against this table AND grades this table's evidence phrase against the sentence, so a relabel has to edit two files and cannot drop the quotation. The order of fields is fixed so the diff is readable.",
"roles": {
"2410.07073v2": {
"claim_role": "model_context_window",
"evidence_phrase": "long context window of 128K tokens",
"why": "the introduced model (Pixtral-12B) processes images in its own window"
},
"2412.09616v2": {
"claim_role": "scaling_claim",
"evidence_phrase": "increased to 256K tokens, the model is capable of processing multimodal sequences up to 1M tokens",
"why": "a train-length -> capability claim: 256K trained, 1M reported as reach"
},
"2502.05177v3": {
"claim_role": "model_context_window",
"evidence_phrase": "over 4K frames or 1M tokens",
"why": "the introduced model's own processing span (Long-VITA)"
},
"2504.16083v2": {
"claim_role": "evaluated_input_length",
"evidence_phrase": "at 1M tokens while maintaining accuracy",
"why": "the largest input the authors measure acceleration at, not a model window"
},
"2505.10610v3": {
"claim_role": "benchmark_input_lengths",
"evidence_phrase": "five standardized input lengths (8K-128K tokens)",
"why": "top of a benchmark's standardized input ladder (MMLongBench)"
},
"2507.01006v6": {
"claim_role": "model_context_window",
"evidence_phrase": "a 128K context window",
"why": "the introduced GLM-4.6V series' own window"
},
"2507.02001v1": {
"claim_role": "method_context_window",
"evidence_phrase": "our approach using a context window of 32K",
"why": "the authors' method uses 32K; the 700K in the same sentence is the baseline VLM's window"
},
"2510.13276v2": {
"claim_role": "benchmark_input_lengths",
"evidence_phrase": "context lengths scaled from 16K to 128K tokens",
"why": "the benchmark's context-length span (MMLongCite)"
},
"2511.21631v2": {
"claim_role": "model_context_window",
"evidence_phrase": "contexts of up to 256K tokens",
"why": "the introduced model family's native window (Qwen3-VL)"
},
"2602.00746v1": {
"claim_role": "evaluated_input_length",
"evidence_phrase": "at 1M tokens",
"why": "the input length at which compression latency is measured"
},
"2604.08644v1": {
"claim_role": "model_context_window",
"evidence_phrase": "extends context length up to 256K tokens",
"why": "the introduced model's (EXAONE 4.5) own context length"
},
"2605.14906v1": {
"claim_role": "benchmark_input_lengths",
"evidence_phrase": "four standard context lengths (32K-256K tokens)",
"why": "top of the benchmark's context-length ladder (MemLens)"
},
"2608.08630v1": {
"claim_role": "training_length",
"evidence_phrase": "training up to 120K tokens",
"why": "largest training length; the same sentence reports inference beyond 280K"
},
"2610.15542v1": {
"claim_role": "model_context_window",
"evidence_phrase": "extends the context window fourfold to 262,144 tokens",
"why": "the introduced models' (Apertus 1.5) own window"
},
"2607.14548v1": {
"claim_role": "model_context_window",
"evidence_phrase": "a 32K context window",
"why": "the base model's (Hy3.0-VL-A3B) own window"
}
}
}
@@ -3,13 +3,23 @@
Re-fetches the Atom feed for the ledger's ids and every abstract page, compares 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 sha256 with the pinned bytes, and re-checks every claim sentence against the
live abstract. Needs network. Exit 0 = live arXiv == pinned ledger. 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 hashlib
import json import json
import re import re
import sys import sys
import time import time
import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -20,15 +30,62 @@ API = "https://export.arxiv.org/api/query"
ABS = "https://export.arxiv.org/abs/" ABS = "https://export.arxiv.org/abs/"
UA = "sphinx-archive-lane/1.0 (live verdict)" UA = "sphinx-archive-lane/1.0 (live verdict)"
ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else Path(__file__).resolve().parent).resolve() _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()) L = json.loads((ROOT / "ledger.json").read_text())
entries = L["entries"] 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): def fetch(url):
req = urllib.request.Request(url, headers={"User-Agent": UA}) req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=90) as r: try:
return r.read() 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): def norm(s):
@@ -36,20 +93,42 @@ def norm(s):
def main(): def main():
fails = [] 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) ids = ",".join(e["arxiv_id"] for e in entries)
live_atom = fetch(API + "?" + urllib.parse.urlencode({"id_list": ids, "start": 0, "max_results": len(entries) + 5})) try:
live_sha = hashlib.sha256(live_atom).hexdigest() live_atom = fetch(API + "?" + urllib.parse.urlencode({"id_list": ids, "start": 0, "max_results": len(entries) + 5}))
pinned_sha = next(s["sha256"] for s in entries[0]["sources"] if s["kind"] == "arxiv_api_atom") root = atom_root(live_atom, API)
ok = live_sha == pinned_sha except Ungradable as ex:
print(f" [{'PASS' if ok else 'FAIL'}] live Atom bytes identical to pinned ({live_sha[:12]} vs {pinned_sha[:12]})") 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: if not ok:
fails.append("atom bytes differ") 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}")
root = ET.fromstring(live_atom)
live = {} live = {}
for el in root.findall("a:entry", NS): for el in root.findall("a:entry", NS):
live[el.find("a:id", NS).text.strip().rsplit("/", 1)[-1]] = el 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 n_claim = n_abs = n_ver = 0
for e in entries: for e in entries:
@@ -64,7 +143,12 @@ def main():
else: else:
print(f" [FAIL] {aid} claim sentence not in live abstract") print(f" [FAIL] {aid} claim sentence not in live abstract")
fails.append(f"{aid} claim") fails.append(f"{aid} claim")
b = fetch(ABS + aid) 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"): if hashlib.sha256(b).hexdigest() == next(s["sha256"] for s in e["sources"] if s["kind"] == "arxiv_abs_page"):
n_abs += 1 n_abs += 1
else: else:
@@ -73,23 +157,35 @@ def main():
# newest version of the unversioned id # newest version of the unversioned id
base = aid.split("v")[0] base = aid.split("v")[0]
try: try:
u = ET.fromstring(fetch(API + "?" + urllib.parse.urlencode({"id_list": base, "start": 0, "max_results": 1}))) u = atom_root(fetch(API + "?" + urllib.parse.urlencode({"id_list": base, "start": 0, "max_results": 1})), API)
newest = u.find("a:entry", NS).find("a:id", NS).text.strip().rsplit("/", 1)[-1] got = u.findall("a:entry", NS)
if newest != aid: if len(got) != 1:
print(f" [note] {aid} is not the newest version; arXiv now has {newest}") print(f" [note] version probe for {base} returned {len(got)} entries; not graded")
except Exception as ex: 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}") print(f" [note] version probe failed for {base}: {ex}")
time.sleep(1) time.sleep(1)
n = len(entries) n = len(entries)
print(f" claims present live: {n_claim}/{n}") print(f" claims present live: {n_claim}/{n}")
print(f" abs pages identical live: {n_abs}/{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_claim == n else 'FAIL'}] all claims present in live abstracts")
print(f" [{'PASS' if n_abs == n else 'FAIL'}] all abs pages byte-identical live") print(f" [{'PASS' if n_abs == n and not ungraded else 'FAIL'}] all abs pages byte-identical live")
if n_claim != n or n_abs != n: if n_claim != n:
fails.append("live content mismatch") fails.append("claims missing live")
print("VERDICT:", "LIVE ARXIV MATCHES LEDGER" if not fails else f"LIVE DRIFT: {fails}") if fails:
return 0 if not fails else 1 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__": if __name__ == "__main__":
@@ -4,6 +4,24 @@
Re-derives every claim from the bytes pinned next to this script. No network. Re-derives every claim from the bytes pinned next to this script. No network.
Usage: python3 verify_ledger.py [ledger_dir] (default: this file's directory) Usage: python3 verify_ledger.py [ledger_dir] (default: this file's directory)
Exit 0 = every check holds; exit 1 = at least one check failed. Exit 0 = every check holds; exit 1 = at least one check failed.
Three legs were added after the first release (queue item Q72), because the
verifier as shipped graded each entry but not the ledger's population or the
reading of its role field:
P* the chain 327 scanned -> 18 candidates -> 15 kept is re-derived from the
pinned probe; an entry dropped from ledger.json, invented outside the
candidate set, or an excluded candidate stripped of its reason is a fault.
B7-B10 claim_role is graded against a committed per-id table
(roles-expected.json) whose evidence phrase must appear verbatim in the
entry's own claim sentence, plus a role->marker rule that reads the
sentence; a relabel has to edit two files and still keep the quotation.
J1 the README's own "N papers" / "N entries" claims are graded against the
ledger, so the document cannot drift from the artifact it describes.
Every check prints PASS/FAIL; the verdict is FAIL if any check fails. A leg
that cannot read its input fails rather than being skipped, so a deleted file
is never silent.
""" """
import hashlib import hashlib
import html as htmlmod import html as htmlmod
@@ -22,6 +40,23 @@ ROLES = {
"method_context_window", "method_context_window",
"scaling_claim", "scaling_claim",
} }
# What each role's own claim sentence must say, and what a model-window entry
# must not say. Marker rules are graded against the quoted sentence, not
# against the role name, so a relabel has to survive contact with the text.
ROLE_MARKERS = {
"model_context_window": r"(context window|context length|contexts? of up to|process\w+[^.]{0,160}tokens)",
"method_context_window": r"(our (method|approach|framework|system)|we propose)",
"benchmark_input_lengths": r"(benchmark|standard(?:ized)?\s+(?:input|context)\s+lengths?|context lengths?\s+(?:scaled|of)|examples across|questions)",
"evaluated_input_length": r"(experiment|evaluat|measur|latency|compar|accelerat)",
"training_length": r"\btrain(?:ing|ed)?\b",
"scaling_claim": r"(increas|scal|generaliz|capable of|potential)",
}
# A sentence that says how a *method* or a *benchmark* was run must not be
# filed as the model's own window: this is the distinction REPORT.md section 1
# promises ("a benchmark's longest input is never reported as a model's
# context window"), and it is the one a relabel of 2507.02001v1 breaks.
MODEL_WINDOW_MUST_NOT = r"(benchmark|standard(?:ized)?\s+(?:input|context)\s+lengths?|context lengths?\s+scaled|our (method|approach|framework)|we propose|dataset is increased)"
SCAN_THRESHOLD = 32000
ID_RX = re.compile(r"^\d{4}\.\d{4,5}v\d+$") ID_RX = re.compile(r"^\d{4}\.\d{4,5}v\d+$")
ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else Path(__file__).resolve().parent).resolve() ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else Path(__file__).resolve().parent).resolve()
@@ -49,6 +84,24 @@ def text_of_html(b):
return norm(htmlmod.unescape(s)) return norm(htmlmod.unescape(s))
def loads(path):
"""Parse JSON, or return None. A missing/broken file is a graded fault."""
try:
return json.loads(Path(path).read_text())
except Exception:
return None
def tokens_in(fig):
"""The last length figure in a string, in tokens; -1 if there is none."""
ms = list(re.finditer(r"(\d[\d,]*(?:\.\d+)?)\s*([kKmM])\b", fig))
if ms:
m = ms[-1]
return int(float(m.group(1).replace(",", "")) * (1e3 if m.group(2).lower() == "k" else 1e6))
mb = list(re.finditer(r"(\d[\d,]*(?:\.\d+)?)\s*tokens?\b", fig, re.I))
return int(float(mb[-1].group(1).replace(",", ""))) if mb else -1
def main(): def main():
print(f"verifying ledger at {ROOT}") print(f"verifying ledger at {ROOT}")
ledger_p = ROOT / "ledger.json" ledger_p = ROOT / "ledger.json"
@@ -65,6 +118,47 @@ def main():
check("A6 REPORT.md present", (ROOT / "REPORT.md").exists()) check("A6 REPORT.md present", (ROOT / "REPORT.md").exists())
ids = [e["arxiv_id"] for e in entries] ids = [e["arxiv_id"] for e in entries]
id_set = set(ids)
# P: the population, re-derived. 327 scanned -> 18 candidates -> 15 kept.
scan = loads(ROOT / "scan-candidates.json")
check("P1 scan-candidates.json present and parses", scan is not None)
cands = (scan or {}).get("candidates", [])
check("P2 n_candidates matches candidate list",
(scan or {}).get("n_candidates") == len(cands) and bool(str((scan or {}).get("rule", "")).strip()),
f"{len(cands)} rows, rule={str((scan or {}).get('rule', ''))[:40]!r}")
probe = loads(ROOT / "source" / "scan-probe-2026.json")
check("P3 the pinned scan probe parses", isinstance(probe, dict) and bool(probe),
f"{len(probe) if isinstance(probe, dict) else 0} records")
derived = {}
for k, v in (probe or {}).items():
if isinstance(v, dict) and v.get("ctx"):
n = tokens_in(v["ctx"])
if n >= SCAN_THRESHOLD:
derived[k] = n
cand_ids = {c.get("arxiv_id") for c in cands}
check("P4 candidates re-derive from the probe at >= 32K tokens",
bool(probe) and set(derived) == cand_ids,
f"{len(probe or {})} scanned, {len(derived)} >= {SCAN_THRESHOLD}, {len(cand_ids)} candidates; "
f"derived-only={sorted(set(derived) - cand_ids)[:4]} candidates-only={sorted(cand_ids - set(derived))[:4]}")
check("P5 every ledger id is an included candidate (nothing invented)",
id_set <= cand_ids, f"outside the scan: {sorted(id_set - cand_ids)}")
included = {c.get("arxiv_id") for c in cands if c.get("decision") == "included"}
check("P6 every included candidate is in the ledger (nothing dropped)",
included <= id_set, f"missing from ledger: {sorted(included - id_set)}")
excluded = [c for c in cands if c.get("decision") == "excluded"]
check("P7 every excluded candidate carries a non-empty reason",
all(str(c.get("reason", "")).strip() for c in excluded),
f"{sum(1 for c in excluded if not str(c.get('reason', '')).strip())} of {len(excluded)} blank")
check("P8 excluded candidates are absent from the ledger",
not (id_set & {c.get("arxiv_id") for c in excluded}),
f"in both: {sorted(id_set & {c.get('arxiv_id') for c in excluded})}")
api_ids = set()
if "id_list=" in str(L.get("api_query", "")):
api_ids = {i.strip() for i in L["api_query"].split("id_list=", 1)[1].split("&")[0].split(",") if i.strip()}
check("P9 api_query id_list matches the ledger ids", api_ids == id_set,
f"{len(api_ids)} queried vs {len(id_set)} kept; only-query={sorted(api_ids - id_set)} only-ledger={sorted(id_set - api_ids)}")
check("B1 ids unique", len(ids) == len(set(ids))) check("B1 ids unique", len(ids) == len(set(ids)))
check("B2 ids canonical (NNNN.NNNNNvN)", all(ID_RX.match(i) for i in ids)) check("B2 ids canonical (NNNN.NNNNNvN)", all(ID_RX.match(i) for i in ids))
check("B3 roles from allowed set", all(e["claim_role"] in ROLES for e in entries)) check("B3 roles from allowed set", all(e["claim_role"] in ROLES for e in entries))
@@ -73,6 +167,34 @@ def main():
check("B5 >=5 model_context_window entries", sum(1 for e in entries if e["claim_role"] == "model_context_window") >= 5) check("B5 >=5 model_context_window entries", sum(1 for e in entries if e["claim_role"] == "model_context_window") >= 5)
check("B6 claim_note non-empty everywhere", all(str(e.get("claim_note", "")).strip() for e in entries)) check("B6 claim_note non-empty everywhere", all(str(e.get("claim_note", "")).strip() for e in entries))
# B7-B10: claim_role graded against a second, committed file and the sentence.
roles = loads(ROOT / "roles-expected.json")
table = (roles or {}).get("roles", {})
check("B7 roles-expected.json present and covers exactly the ledger ids",
roles is not None and set(table) == id_set,
f"unexpected={sorted(set(table) - id_set)} missing={sorted(id_set - set(table))}")
bad_role = [i for i in ids if (table.get(i) or {}).get("claim_role") != next(e["claim_role"] for e in entries if e["arxiv_id"] == i)]
check("B8 claim_role matches the expected-role table", not bad_role,
"; ".join(f"{i}: ledger={next(e['claim_role'] for e in entries if e['arxiv_id'] == i)} table={(table.get(i) or {}).get('claim_role')}" for i in bad_role))
bad_ev = [i for i in ids
if norm((table.get(i) or {}).get("evidence_phrase", "")) and
norm((table.get(i) or {}).get("evidence_phrase", "")) not in
norm(next(e["claim_sentence"] for e in entries if e["arxiv_id"] == i))]
check("B9 table evidence phrase is verbatim in the claim sentence", not bad_ev, f"not quoted: {bad_ev}")
marker_bad = []
for e in entries:
role, sent = e["claim_role"], norm(e["claim_sentence"])
why = []
if role not in ROLE_MARKERS:
why.append("no marker rule for role")
elif not re.search(ROLE_MARKERS[role], sent, re.I):
why.append(f"sentence has no {role} marker")
if role == "model_context_window" and re.search(MODEL_WINDOW_MUST_NOT, sent, re.I):
why.append("sentence is about a method/benchmark, not the model's own window")
if why:
marker_bad.append(f"{e['arxiv_id']}[{'/'.join(why)}]")
check("B10 role agrees with the sentence-level marker rule", not marker_bad, "; ".join(marker_bad))
atom_p = ROOT / "source" / "atom-lcmm.xml" atom_p = ROOT / "source" / "atom-lcmm.xml"
check("C1 pinned Atom XML present", atom_p.exists()) check("C1 pinned Atom XML present", atom_p.exists())
if not atom_p.exists(): if not atom_p.exists():
@@ -108,14 +230,8 @@ def main():
fig = e["context_figure"] fig = e["context_figure"]
ok_fig = fig in e["claim_sentence"] ok_fig = fig in e["claim_sentence"]
check(f"E:{aid} figure inside claim sentence", ok_fig) check(f"E:{aid} figure inside claim sentence", ok_fig)
ms = list(re.finditer(r"(\d[\d,]*(?:\.\d+)?)\s*([kKmM])\b", fig)) v = tokens_in(fig)
if ms: check(f"E:{aid} parsed figure == context_tokens", v == e["context_tokens"], f"{v} vs {e['context_tokens']}")
m = ms[-1]
v = float(m.group(1).replace(",", "")) * (1e3 if m.group(2).lower() == "k" else 1e6)
else:
mb = list(re.finditer(r"(\d[\d,]*(?:\.\d+)?)\s*tokens?\b", fig, re.I))
v = float(mb[-1].group(1).replace(",", "")) if mb else -1
check(f"E:{aid} parsed figure == context_tokens", int(v) == e["context_tokens"], f"{int(v)} vs {e['context_tokens']}")
check(f"E:{aid} context >= 32000 tokens (32K)", e["context_tokens"] >= 32000, str(e["context_tokens"])) check(f"E:{aid} context >= 32000 tokens (32K)", e["context_tokens"] >= 32000, str(e["context_tokens"]))
# F: metadata re-derived from the same pinned bytes # F: metadata re-derived from the same pinned bytes
check(f"F:{aid} title matches Atom", norm(el.find("a:title", NS).text) == norm(e["title"])) check(f"F:{aid} title matches Atom", norm(el.find("a:title", NS).text) == norm(e["title"]))
@@ -158,6 +274,12 @@ def main():
bad = [f for f, h in manifest.items() if sha(ROOT / f) != h] bad = [f for f, h in manifest.items() if sha(ROOT / f) != h]
check("I2 manifest hashes match bytes", not bad, f"mismatch {bad}") check("I2 manifest hashes match bytes", not bad, f"mismatch {bad}")
# J: the README describes this ledger, not an earlier one
readme_p = ROOT / "README.md"
counts = sorted({int(m.group(1)) for m in re.finditer(r"(\d+)\s+(?:papers|entries)\b", readme_p.read_text())}) if readme_p.exists() else []
check("J1 README paper/entry counts match the ledger", counts == [len(entries)],
f"README says {counts or 'nothing'}, ledger has {len(entries)}")
return finish() return finish()