sphinx: provenance-anchored multimodal/long-context corpus (279 arXiv nodes, hash-chained edge journal, 3 verifiers)

This commit is contained in:
sphinx committed 2026-10-26 11:26:17 +00:00
1 parent 5a635b361a
commit f13d727578
21 files changed
+22244

No files matched your search

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# CHECK.sh -- the single-command verdict for this archive.
#
# bash CHECK.sh ; echo $? # 0 = the archive holds, nonzero = it does not
#
# It runs, in order:
# 1. run_verdict.py -- internal integrity (proofs, hash chain, DAG)
# 2. indep_check.py -- live-arXiv corroboration + a second, from-scratch parse
# 3. mutation_battery.py-- falsifiability: 11 corruptions must all be caught
# and exits nonzero if ANY of them fails.
set -u
cd "$(dirname "$0")"
fail=0
for t in run_verdict.py indep_check.py mutation_battery.py; do
echo "=== $t ==="
python3 "$t" || fail=1
done
if [ "$fail" -ne 0 ]; then
echo "CHECK: FAIL"
exit 1
fi
echo "CHECK: PASS"
exit 0
@@ -0,0 +1,58 @@
# archive-lcmm-citegraph-2026-10-26
sphinx — Archive lane (multimodal, long-context research).
A **provenance-anchored research corpus** built from a live arXiv API fetch,
with an append-only hash-chained edge journal. 279 distinct papers, 94
title-similarity edges, every record tied to a verbatim line in the frozen
raw XML.
## Verdict (the command)
```
bash CHECK.sh
```
Exit 0 = the archive holds. It runs three independent checks:
| checker | what it proves |
|---|---|
| `run_verdict.py` | 22 internal-integrity checks: proof line numbers, proof block hashes, titles re-derived from the frozen bytes, hash-chained journal, acyclicity, honest counts |
| `indep_check.py` | re-fetches **live arXiv** by `id_list` and re-parses the frozen sources with regex (not ElementTree); corroborates all 279 titles against what arXiv serves today |
| `mutation_battery.py` | 11 single-field corruptions of a copy — all 11 must be caught, or the verdict is not a real test |
## Measured at build time
- records fetched: 320 → distinct nodes: **279**
- similarity edges (title Jaccard ≥ 0.25): **94** (55 nodes with outgoing, 61 with incoming)
- frozen sources: 4 raw XML files, 844,912 bytes total, all sha256-pinned
- journal head: recorded in `corpus.json` → `journal.head`
## Files
build_corpus.py fetches arXiv, builds corpus + chained journal
corpus.json the manifest (nodes, proofs, edges, journal head)
edges.jsonl append-only hash-chained edge journal
source/arxiv-*.xml frozen raw API responses (sha256-pinned)
run_verdict.py internal integrity verifier (22 checks)
indep_check.py live-arXiv corroboration + second from-scratch parse
mutation_battery.py falsifiability harness (11 mutations)
CHECK.sh one-command verdict
REPORT.md rendered report
probes/ the measurement probes that justified the design
## Why this design (measured, not guessed)
The first attempt — a **citation graph** from arXiv IDs in abstracts — produced
**0 edges**: `probes/probe.py` measured `arXiv:` mentions = 0 in all 80 fetched
abstracts. An empty journal would have made the verdict a tautology, so the
design was changed. `probes/probe_titles.py` then measured title-overlap
density (33 pairs at J≥0.2, 8 at J≥0.25 over 80 papers), which showed a real
but sparse signal — so the slice was widened to 320 records and the threshold
set at 0.25, yielding 94 edges.
## Note on `probes/probe_refs.py`
Full-text reference lists (ar5iv HTML) do carry arXiv IDs — 5 per paper — but
none resolve inside the fetched slice, so an in-slice citation graph stays
empty. Recorded here so the negative result is not re-litigated.
@@ -0,0 +1,94 @@
# archive-lcmm-corpus-2026-10-26
Built 2026-10-26T11:17:13Z by `build_corpus.py`.
## What this is
A provenance-anchored research corpus over the **multimodal / long-context**
lane, built from a **live arXiv API fetch** on the build date. It is not a
scrape-and-forget dump: every derived record carries a verbatim proof that
re-derives from the frozen raw bytes, and the edge journal is hash-chained.
## Provenance model
* The raw arXiv API response for each slice is frozen under `source/` with
its sha256 and byte count recorded in the manifest.
* Each node records `proof.line` (1-based line of its `<entry>` in the raw
XML) and `proof.block_sha256` (sha256 of that entry's exact byte block).
* A verifier re-derives line, block hash, and title straight from the frozen
bytes — it does not trust `corpus.json`.
## Fetches (measured)
| slice | query | requested | returned | bytes | sha256 |
|---|---|---|---|---|---|
| mm | `cat:cs.CV AND abs:"multimodal"` | 100 | 100 | 261064 | `e7404fda36641d29…` |
| lc | `cat:cs.CL AND abs:"long context"` | 100 | 100 | 264236 | `1118fef8a03bf5dc…` |
| mm2 | `cat:cs.CV AND abs:"vision language"` | 60 | 60 | 155027 | `1d10f8ca56ad0c1c…` |
| lc2 | `abs:"long context" AND abs:"benchmark"` | 60 | 60 | 164585 | `e8ca5492ec9d091e…` |
## Counts
* records fetched: **320**
* distinct nodes: **279**
* similarity edges (Jaccard >= 0.25): **94**
* nodes with outgoing edges: 55
* nodes with incoming edges: 61
Per-slice node counts: `lc`=100, `lc2`=25, `mm`=100, `mm2`=54
## Edge journal
* entries: **94**
* genesis: `0000000000000000000000000000000000000000000000000000000000000000`
* head: `2c0bf612e7c9db3cd0db0ac91cee031e4c2ef20d2e0b72722e4775f058e4988c`
* construction: `hash_n = sha256(json({seq, from, to, jaccard, prev_hash}))`,
`prev_hash_0 = '0'*64` — an append-only chain.
## Top edges by title similarity
| jaccard | later (from) | earlier (to) | shared stems |
|---|---|---|---|
| 0.556 | `2610.32263` | `2610.27298` | cont, foun, lear, mode, mult |
| 0.417 | `2610.31205` | `2610.25005` | lang, larg, mode, mult, visu |
| 0.364 | `2610.11592` | `2609.38137` | lang, long, mode, reas |
| 0.364 | `2610.32263` | `2610.27904` | cont, foun, mode, mult |
| 0.333 | `2609.36529` | `2609.36314` | long, mode, sequ, stat |
| 0.333 | `2610.17966` | `2610.06479` | beha, cach, comp |
| 0.333 | `2610.19928` | `2609.36935` | long, memo, reas |
| 0.333 | `2610.26203` | `2609.25611` | agen, omni |
| 0.333 | `2610.31304` | `2610.29642` | data, remo, sens |
| 0.333 | `2610.31494` | `2610.26826` | agen, benc, visu |
| 0.333 | `2610.32440` | `2610.32220` | grou, mode, visi, visu |
| 0.308 | `2610.27904` | `2610.27298` | cont, foun, mode, mult |
| 0.308 | `2610.28626` | `2610.27782` | embo, lang, mode, robo |
| 0.308 | `2610.31158` | `2610.25926` | agen, deep, repo, rese |
| 0.308 | `2610.32486` | `2610.31478` | anom, dete, effi, vide |
## Verdicts
Three independent checks, all green at build time:
* `run_verdict.py` — 22 internal-integrity checks (proofs, chain, DAG, counts)
* `indep_check.py` — re-fetches live arXiv by `id_list` and re-parses the frozen
sources with regex (not ElementTree); corroborates all 279 titles
* `mutation_battery.py` — 11 single-field corruptions, all 11 caught
Run everything with one command:
```
bash CHECK.sh
```
## Files
| file | role |
|---|---|
| `build_corpus.py` | fetches arXiv, builds corpus + chained journal |
| `corpus.json` | the manifest (nodes, proofs, edges, journal head) |
| `edges.jsonl` | append-only hash-chained edge journal |
| `source/arxiv-*.xml` | frozen raw API responses (sha256-pinned) |
| `run_verdict.py` | internal integrity verifier (22 checks) |
| `indep_check.py` | live-arXiv corroboration + second from-scratch parse |
| `mutation_battery.py` | falsifiability harness (11 mutations) |
| `CHECK.sh` | one-command verdict |
@@ -0,0 +1,265 @@
#!/usr/bin/env python3
"""build_corpus.py -- sphinx Archive lane.
Build a PROVENANCE-ANCHORED research corpus over the multimodal /
long-context lane, from a LIVE arXiv API fetch.
What makes this an archive rather than a dump:
* The raw API response is frozen as bytes with its sha256 recorded.
* Every derived record carries `proof` = the 1-based line number in the raw
XML where that record's <entry> block begins, and `proof_sha256` =
sha256 of that block's bytes. A verifier re-derives both from the frozen
source independently.
* A title-similarity DAG is derived from the corpus (edges only point from
a paper published later to a paper published earlier), and the journal of
edges is hash-chained append-only.
Everything is MEASURED in this run; nothing is asserted from memory.
Usage: python3 build_corpus.py
"""
import hashlib
import json
import os
import re
import sys
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, "source")
os.makedirs(SRC, exist_ok=True)
API = "https://export.arxiv.org/api/query"
ATOM = "{http://www.w3.org/2005/Atom}"
ARXIV = "{http://arxiv.org/schemas/atom}"
QUERIES = [
("mm", 'cat:cs.CV AND abs:"multimodal"', 100),
("lc", 'cat:cs.CL AND abs:"long context"', 100),
("mm2", 'cat:cs.CV AND abs:"vision language"', 60),
("lc2", 'abs:"long context" AND abs:"benchmark"', 60),
]
# title-tokenisation: stopwords + crude stem, used only for the similarity DAG
STOP = {
"a", "an", "the", "of", "for", "and", "or", "to", "in", "on", "with", "via",
"by", "from", "as", "at", "is", "are", "we", "our", "their", "its", "this",
"that", "these", "those", "using", "based", "towards", "through", "into",
"how", "what", "why", "when", "can", "do", "does", "be", "been", "than",
}
SIM_THRESHOLD = 0.25
def canon(aid):
return re.sub(r"v\d+$", "", aid.strip())
def sha256_bytes(b):
return hashlib.sha256(b).hexdigest()
def fetch_slice(label, query, n):
"""Fetch one slice. Returns (raw_bytes, path)."""
q = urllib.parse.urlencode(
{
"search_query": query,
"start": 0,
"max_results": n,
"sortBy": "submittedDate",
"sortOrder": "descending",
}
)
url = f"{API}?{q}"
req = urllib.request.Request(url, headers={"User-Agent": "sphinx-archive/1.0"})
with urllib.request.urlopen(req, timeout=90) as r:
body = r.read()
path = os.path.join(SRC, f"arxiv-{label}.xml")
with open(path, "wb") as f:
f.write(body)
return body, path
def parse_with_proofs(raw):
"""Parse raw XML and attach a verbatim-line proof for each <entry>.
A record's proof is the 1-based line number of the '<entry>' line plus the
sha256 of the exact byte block of that entry, so a verifier can re-derive
the record from the frozen source without trusting this builder.
"""
lines = raw.split(b"\n")
starts = [i for i, ln in enumerate(lines) if ln.strip() == b"<entry>"]
root = ET.fromstring(raw)
entries = root.findall(f"{ATOM}entry")
if len(entries) != len(starts):
raise RuntimeError(
f"entry count mismatch: ET={len(entries)} lines={len(starts)}"
)
recs = []
for e, ln in zip(entries, starts):
# block = from the <entry> line through the next <entry> (or EOF)
nxt = starts[starts.index(ln) + 1] if starts.index(ln) + 1 < len(starts) else len(lines)
block = b"\n".join(lines[ln:nxt])
aid = re.search(r"abs/([^/]+)$", e.findtext(f"{ATOM}id", "").strip())
rec = {
"arxiv_id": aid.group(1) if aid else e.findtext(f"{ATOM}id", "").strip(),
"title": re.sub(r"\s+", " ", (e.findtext(f"{ATOM}title", "") or "").strip()),
"abstract": re.sub(r"\s+", " ", (e.findtext(f"{ATOM}summary", "") or "").strip()),
"published": (e.findtext(f"{ATOM}published", "") or "").strip(),
"updated": (e.findtext(f"{ATOM}updated", "") or "").strip(),
"categories": [c.get("term") for c in e.findall(f"{ATOM}category") if c.get("term")],
"primary_category": e.findtext(f"{ARXIV}primary_category", "") or "",
"doi": e.findtext(f"{ARXIV}doi", "") or "",
"authors": [
(a.findtext(f"{ATOM}name", "") or "").strip()
for a in e.findall(f"{ATOM}author")
],
"proof": {
"line": ln + 1,
"block_sha256": sha256_bytes(block),
"block_bytes": len(block),
},
}
recs.append(rec)
return recs, lines, starts
def title_tokens(title):
out = set()
for w in re.findall(r"[A-Za-z][A-Za-z\-]+", title.lower()):
w = w.strip("-")
if len(w) < 3 or w in STOP:
continue
if len(w) > 4:
w = w[:4]
out.add(w)
return out
def main():
stamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
fetch_meta, all_recs, raw_by_label = [], [], {}
for label, query, n in QUERIES:
raw, path = fetch_slice(label, query, n)
raw_by_label[label] = raw
recs, lines, starts = parse_with_proofs(raw)
for r in recs:
r["slice"] = label
all_recs.extend(recs)
fetch_meta.append(
{
"label": label,
"query": query,
"requested_max": n,
"returned": len(recs),
"raw_bytes": len(raw),
"raw_sha256": sha256_bytes(raw),
"raw_path": os.path.relpath(path, HERE),
"entry_line_starts": [s + 1 for s in starts],
}
)
# dedupe by version-stripped node, keep highest version
by_node = {}
for r in all_recs:
node = canon(r["arxiv_id"])
prev = by_node.get(node)
if prev is None:
by_node[node] = r
else:
v_new = int(re.search(r"v(\d+)$", r["arxiv_id"]).group(1))
v_old = int(re.search(r"v(\d+)$", prev["arxiv_id"]).group(1))
if v_new > v_old:
by_node[node] = r
nodes = sorted(by_node.values(), key=lambda r: (r["published"], r["arxiv_id"]))
order = {canon(r["arxiv_id"]): i for i, r in enumerate(nodes)}
toks = {canon(r["arxiv_id"]): title_tokens(r["title"]) for r in nodes}
# similarity edges: later-published -> earlier-published only
edges = []
ids = [canon(r["arxiv_id"]) for r in nodes]
for i in range(len(ids)):
for j in range(i + 1, len(ids)):
a, b = ids[i], ids[j] # a published at or before b (nodes sorted)
sa, sb = toks[a], toks[b]
if not sa or not sb:
continue
jac = len(sa & sb) / len(sa | sb)
if jac >= SIM_THRESHOLD:
edges.append(
{
"from": b, # later
"to": a, # earlier
"jaccard": round(jac, 4),
"shared": sorted(sa & sb),
}
)
edges.sort(key=lambda e: (-e["jaccard"], e["from"], e["to"]))
# hash-chained append-only edge journal
journal_path = os.path.join(HERE, "edges.jsonl")
prev_hash = "0" * 64
with open(journal_path, "w") as f:
for seq, e in enumerate(edges, start=1):
row = {"seq": seq, "from": e["from"], "to": e["to"], "jaccard": e["jaccard"], "prev_hash": prev_hash}
h = sha256_bytes(json.dumps(row, sort_keys=True, separators=(",", ":")).encode())
row["hash"] = h
f.write(json.dumps(row, sort_keys=True) + "\n")
prev_hash = h
head = prev_hash
manifest = {
"artifact": "archive-lcmm-corpus-2026-10-26",
"lane": "Archive -- multimodal, long-context research",
"built_utc": stamp,
"builder": "build_corpus.py",
"fetch": fetch_meta,
"counts": {
"records_fetched": len(all_recs),
"distinct_nodes": len(nodes),
"similarity_edges": len(edges),
"threshold": SIM_THRESHOLD,
"nodes_with_out_edges": len({e["from"] for e in edges}),
"nodes_with_in_edges": len({e["to"] for e in edges}),
},
"nodes": [
{
"node": canon(r["arxiv_id"]),
"arxiv_id": r["arxiv_id"],
"title": r["title"],
"published": r["published"],
"updated": r["updated"],
"primary_category": r["primary_category"],
"categories": r["categories"],
"doi": r["doi"],
"n_authors": len(r["authors"]),
"authors": r["authors"],
"abstract_chars": len(r["abstract"]),
"slice": r["slice"],
"proof": r["proof"],
}
for r in nodes
],
"edges": edges,
"journal": {
"path": "edges.jsonl",
"entries": len(edges),
"genesis": "0" * 64,
"head": head,
},
}
with open(os.path.join(HERE, "corpus.json"), "w") as f:
json.dump(manifest, f, indent=2, sort_keys=True)
print(f"[build] fetched {len(all_recs)} records -> {len(nodes)} nodes")
print(f"[build] similarity edges (>= {SIM_THRESHOLD}): {len(edges)}")
print(f"[build] edge journal head: {head[:16]}...")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""build_report.py -- render REPORT.md from corpus.json (measured, not hand-written)."""
import json
import os
HERE = os.path.dirname(os.path.abspath(__file__))
m = json.load(open(os.path.join(HERE, "corpus.json")))
nodes = m["nodes"]
edges = m["edges"]
by_slice = {}
for n in nodes:
by_slice[n["slice"]] = by_slice.get(n["slice"], 0) + 1
lines = []
A = lines.append
A(f"# {m['artifact']}")
A("")
A(f"Built {m['built_utc']} by `build_corpus.py`.")
A("")
A("## What this is")
A("")
A("A provenance-anchored research corpus over the **multimodal / long-context**")
A("lane, built from a **live arXiv API fetch** on the build date. It is not a")
A("scrape-and-forget dump: every derived record carries a verbatim proof that")
A("re-derives from the frozen raw bytes, and the edge journal is hash-chained.")
A("")
A("## Provenance model")
A("")
A("* The raw arXiv API response for each slice is frozen under `source/` with")
A(" its sha256 and byte count recorded in the manifest.")
A("* Each node records `proof.line` (1-based line of its `<entry>` in the raw")
A(" XML) and `proof.block_sha256` (sha256 of that entry's exact byte block).")
A("* A verifier re-derives line, block hash, and title straight from the frozen")
A(" bytes — it does not trust `corpus.json`.")
A("")
A("## Fetches (measured)")
A("")
A("| slice | query | requested | returned | bytes | sha256 |")
A("|---|---|---|---|---|---|")
for fm in m["fetch"]:
A(f"| {fm['label']} | `{fm['query']}` | {fm['requested_max']} | {fm['returned']} "
f"| {fm['raw_bytes']} | `{fm['raw_sha256'][:16]}…` |")
A("")
A("## Counts")
A("")
A(f"* records fetched: **{m['counts']['records_fetched']}**")
A(f"* distinct nodes: **{m['counts']['distinct_nodes']}**")
A(f"* similarity edges (Jaccard >= {m['counts']['threshold']}): **{m['counts']['similarity_edges']}**")
A(f"* nodes with outgoing edges: {m['counts']['nodes_with_out_edges']}")
A(f"* nodes with incoming edges: {m['counts']['nodes_with_in_edges']}")
A("")
A("Per-slice node counts: " + ", ".join(f"`{k}`={v}" for k, v in sorted(by_slice.items())))
A("")
A("## Edge journal")
A("")
A(f"* entries: **{m['journal']['entries']}**")
A(f"* genesis: `{'0'*64}`")
A(f"* head: `{m['journal']['head']}`")
A("* construction: `hash_n = sha256(json({seq, from, to, jaccard, prev_hash}))`,")
A(" `prev_hash_0 = '0'*64` — an append-only chain.")
A("")
A("## Top edges by title similarity")
A("")
A("| jaccard | later (from) | earlier (to) | shared stems |")
A("|---|---|---|---|")
for e in edges[:15]:
A(f"| {e['jaccard']:.3f} | `{e['from']}` | `{e['to']}` | {', '.join(e['shared'][:6])} |")
A("")
A("## Verdicts")
A("")
A("Three independent checks, all green at build time:")
A("")
A("* `run_verdict.py` — 22 internal-integrity checks (proofs, chain, DAG, counts)")
A("* `indep_check.py` — re-fetches live arXiv by `id_list` and re-parses the frozen")
A(" sources with regex (not ElementTree); corroborates all 279 titles")
A("* `mutation_battery.py` — 11 single-field corruptions, all 11 caught")
A("")
A("Run everything with one command:")
A("")
A("```")
A("bash CHECK.sh")
A("```")
A("")
A("## Files")
A("")
A("| file | role |")
A("|---|---|")
A("| `build_corpus.py` | fetches arXiv, builds corpus + chained journal |")
A("| `corpus.json` | the manifest (nodes, proofs, edges, journal head) |")
A("| `edges.jsonl` | append-only hash-chained edge journal |")
A("| `source/arxiv-*.xml` | frozen raw API responses (sha256-pinned) |")
A("| `run_verdict.py` | internal integrity verifier (22 checks) |")
A("| `indep_check.py` | live-arXiv corroboration + second from-scratch parse |")
A("| `mutation_battery.py` | falsifiability harness (11 mutations) |")
A("| `CHECK.sh` | one-command verdict |")
open(os.path.join(HERE, "REPORT.md"), "w").write("\n".join(lines) + "\n")
print(f"[report] wrote REPORT.md ({len(lines)} lines)")
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,94 @@
{"from": "2610.32263", "hash": "c0d2785a6b02cceda12c72cdb8c61bdffe0f5b9a6e3d4bc89c21cae7b39f71ce", "jaccard": 0.5556, "prev_hash": "0000000000000000000000000000000000000000000000000000000000000000", "seq": 1, "to": "2610.27298"}
{"from": "2610.31205", "hash": "957657371b688031807cab83207a6051121fd0d07e22513004e3b1268efa0d8a", "jaccard": 0.4167, "prev_hash": "c0d2785a6b02cceda12c72cdb8c61bdffe0f5b9a6e3d4bc89c21cae7b39f71ce", "seq": 2, "to": "2610.25005"}
{"from": "2610.11592", "hash": "d3d880c2ee63701312dc2c3a0035bb9f29fb340acacd5c5cbca9ec1aad48a5a5", "jaccard": 0.3636, "prev_hash": "957657371b688031807cab83207a6051121fd0d07e22513004e3b1268efa0d8a", "seq": 3, "to": "2609.38137"}
{"from": "2610.32263", "hash": "2f510363a8d3e72a9ebaaea28bed3ddfd9b807a9fc72f045f566aec50c98247d", "jaccard": 0.3636, "prev_hash": "d3d880c2ee63701312dc2c3a0035bb9f29fb340acacd5c5cbca9ec1aad48a5a5", "seq": 4, "to": "2610.27904"}
{"from": "2609.36529", "hash": "3793a445542d163daa6a2173e63e81d5db0f7d43c75df2e69b6718e3a68ccdbd", "jaccard": 0.3333, "prev_hash": "2f510363a8d3e72a9ebaaea28bed3ddfd9b807a9fc72f045f566aec50c98247d", "seq": 5, "to": "2609.36314"}
{"from": "2610.17966", "hash": "0038bd6e8600148342b08e8755f689f199a34689a60dc6632d61a93dbca3065f", "jaccard": 0.3333, "prev_hash": "3793a445542d163daa6a2173e63e81d5db0f7d43c75df2e69b6718e3a68ccdbd", "seq": 6, "to": "2610.06479"}
{"from": "2610.19928", "hash": "2e6462261593b5b72d9198ddf318334558cbb08bc1c138d0238de8fd510a7d8a", "jaccard": 0.3333, "prev_hash": "0038bd6e8600148342b08e8755f689f199a34689a60dc6632d61a93dbca3065f", "seq": 7, "to": "2609.36935"}
{"from": "2610.26203", "hash": "0cedec4ce66fb53831be0a813144c0dc9b695f8243e280f1b822fa8d483d455d", "jaccard": 0.3333, "prev_hash": "2e6462261593b5b72d9198ddf318334558cbb08bc1c138d0238de8fd510a7d8a", "seq": 8, "to": "2609.25611"}
{"from": "2610.31304", "hash": "934ce7f3ef23532ceb51137a92bc147d577ecaf8b0f9c587c1e487caf5b8e31e", "jaccard": 0.3333, "prev_hash": "0cedec4ce66fb53831be0a813144c0dc9b695f8243e280f1b822fa8d483d455d", "seq": 9, "to": "2610.29642"}
{"from": "2610.31494", "hash": "e7f371b93a0ded3286fc076a551f61b28e4a62572072880f69c15a3ad92a7069", "jaccard": 0.3333, "prev_hash": "934ce7f3ef23532ceb51137a92bc147d577ecaf8b0f9c587c1e487caf5b8e31e", "seq": 10, "to": "2610.26826"}
{"from": "2610.32440", "hash": "b5d18bf5c1e5bc53a63294ef4e3ab6b236a8cfcccabf527962afe3793bd6e08a", "jaccard": 0.3333, "prev_hash": "e7f371b93a0ded3286fc076a551f61b28e4a62572072880f69c15a3ad92a7069", "seq": 11, "to": "2610.32220"}
{"from": "2610.27904", "hash": "1c652fa03156a18ad9d3e1b4bcbb6d2ef626e3c793a05979849a0c86821cae6c", "jaccard": 0.3077, "prev_hash": "b5d18bf5c1e5bc53a63294ef4e3ab6b236a8cfcccabf527962afe3793bd6e08a", "seq": 12, "to": "2610.27298"}
{"from": "2610.28626", "hash": "adf08df5b84c667b03651b920064149badabf7c549a0f613836d178916ba6a12", "jaccard": 0.3077, "prev_hash": "1c652fa03156a18ad9d3e1b4bcbb6d2ef626e3c793a05979849a0c86821cae6c", "seq": 13, "to": "2610.27782"}
{"from": "2610.31158", "hash": "e33be759dac78f2f14a18fbabf268d0fd746924c9af34627a2aa19f5007ccbd8", "jaccard": 0.3077, "prev_hash": "adf08df5b84c667b03651b920064149badabf7c549a0f613836d178916ba6a12", "seq": 14, "to": "2610.25926"}
{"from": "2610.32486", "hash": "2fad0dff9b4e857b50d2712561e70d824a42aa02b88cbedb24ee57f32200c80b", "jaccard": 0.3077, "prev_hash": "e33be759dac78f2f14a18fbabf268d0fd746924c9af34627a2aa19f5007ccbd8", "seq": 15, "to": "2610.31478"}
{"from": "2610.10114", "hash": "16b2afa9c818375c3c6e03efced15244f4e1a56711e36f4d18d09e02fc1451d2", "jaccard": 0.3, "prev_hash": "2fad0dff9b4e857b50d2712561e70d824a42aa02b88cbedb24ee57f32200c80b", "seq": 16, "to": "2610.07940"}
{"from": "2610.19026", "hash": "b36589cd0ba8ce14ac4de3b5747d1e965fcb82280a3f64b6e201444b9f24d7c3", "jaccard": 0.3, "prev_hash": "16b2afa9c818375c3c6e03efced15244f4e1a56711e36f4d18d09e02fc1451d2", "seq": 17, "to": "2610.00418"}
{"from": "2610.19928", "hash": "4d90c6fd47b9ad947d9d799e5914c1e04e6724c4f84cda18241ed7535707f276", "jaccard": 0.3, "prev_hash": "b36589cd0ba8ce14ac4de3b5747d1e965fcb82280a3f64b6e201444b9f24d7c3", "seq": 18, "to": "2609.38809"}
{"from": "2610.25247", "hash": "f770df43866ee02413278f2dc6f26707a071902da61723339d6d6fd5166e9b20", "jaccard": 0.3, "prev_hash": "4d90c6fd47b9ad947d9d799e5914c1e04e6724c4f84cda18241ed7535707f276", "seq": 19, "to": "2610.02181"}
{"from": "2610.26431", "hash": "18c69ce71cb111beb6f39bb142c3c17ec8cb6b3901c077b7bc7d9464ad691733", "jaccard": 0.3, "prev_hash": "f770df43866ee02413278f2dc6f26707a071902da61723339d6d6fd5166e9b20", "seq": 20, "to": "2609.38137"}
{"from": "2610.28282", "hash": "1c70d2dbe52563a357ea89f9696b5d3de7dad11c6399e03c163c97f849127fa6", "jaccard": 0.3, "prev_hash": "18c69ce71cb111beb6f39bb142c3c17ec8cb6b3901c077b7bc7d9464ad691733", "seq": 21, "to": "2609.38137"}
{"from": "2610.28282", "hash": "44ac30a03f25704e0a913230f09f778ee180d3e8eb303295663552861e5c0c4a", "jaccard": 0.3, "prev_hash": "1c70d2dbe52563a357ea89f9696b5d3de7dad11c6399e03c163c97f849127fa6", "seq": 22, "to": "2610.22067"}
{"from": "2610.30097", "hash": "8b10323bfeb2a1a3ae208c3c0e377b8f6fbfdc514833716145ec69cdd5c098dd", "jaccard": 0.3, "prev_hash": "44ac30a03f25704e0a913230f09f778ee180d3e8eb303295663552861e5c0c4a", "seq": 23, "to": "2609.38809"}
{"from": "2610.30132", "hash": "2a34229fb3839c81e001ddad840c1bf5c324d1c5493ae3c1635cf15d98ae2d00", "jaccard": 0.3, "prev_hash": "8b10323bfeb2a1a3ae208c3c0e377b8f6fbfdc514833716145ec69cdd5c098dd", "seq": 24, "to": "2609.38809"}
{"from": "2610.32213", "hash": "eff84b3e35cf2dac2882720c744d6ca3fbc4ce4ac2e4f32a235b9c4978818403", "jaccard": 0.3, "prev_hash": "2a34229fb3839c81e001ddad840c1bf5c324d1c5493ae3c1635cf15d98ae2d00", "seq": 25, "to": "2610.08463"}
{"from": "2610.32213", "hash": "b27eb9da95cfa254328cd9124b4ccd81d63f78d80bee254822c2b025b5bd1028", "jaccard": 0.3, "prev_hash": "eff84b3e35cf2dac2882720c744d6ca3fbc4ce4ac2e4f32a235b9c4978818403", "seq": 26, "to": "2610.26431"}
{"from": "2610.32263", "hash": "6820eabea4aa4e305bd6cbf2728026b7e7511f447def9b3511786a386bbda210", "jaccard": 0.3, "prev_hash": "b27eb9da95cfa254328cd9124b4ccd81d63f78d80bee254822c2b025b5bd1028", "seq": 27, "to": "2610.29495"}
{"from": "2610.32332", "hash": "5c820211d28f3a1ec2449771c05320dd864a6c0b97f56cbea271c2204bb86f90", "jaccard": 0.3, "prev_hash": "6820eabea4aa4e305bd6cbf2728026b7e7511f447def9b3511786a386bbda210", "seq": 28, "to": "2610.28282"}
{"from": "2610.32360", "hash": "37d47d49714c7e7a9acf52b9ff3ffc3b23e5de0abbbe07e3e86a68c5fc357b13", "jaccard": 0.3, "prev_hash": "5c820211d28f3a1ec2449771c05320dd864a6c0b97f56cbea271c2204bb86f90", "seq": 29, "to": "2610.31304"}
{"from": "2610.32784", "hash": "5fd9515cb74eebed9abdac418df344693632766c54937ea66318509d4ec82365", "jaccard": 0.3, "prev_hash": "37d47d49714c7e7a9acf52b9ff3ffc3b23e5de0abbbe07e3e86a68c5fc357b13", "seq": 30, "to": "2610.30089"}
{"from": "2610.06479", "hash": "84fd4315ba0aade327d63c8d49fab2ebcdeaa4bb5e607d4b46eb523c2976ec1a", "jaccard": 0.2857, "prev_hash": "5fd9515cb74eebed9abdac418df344693632766c54937ea66318509d4ec82365", "seq": 31, "to": "2610.03027"}
{"from": "2610.12338", "hash": "fb24a7a3248048c57d59bea19537f9103f9267fe4379836520375ce44dbfd9c4", "jaccard": 0.2857, "prev_hash": "84fd4315ba0aade327d63c8d49fab2ebcdeaa4bb5e607d4b46eb523c2976ec1a", "seq": 32, "to": "2610.06479"}
{"from": "2610.19247", "hash": "ebff381aeecc3ac4576a994a6637bbde9241d07be69a7adb1af2df5f9b388f87", "jaccard": 0.2857, "prev_hash": "fb24a7a3248048c57d59bea19537f9103f9267fe4379836520375ce44dbfd9c4", "seq": 33, "to": "2610.07940"}
{"from": "2610.26431", "hash": "1ddda6d6943dd7c06761372b9ecce4834e8da419665e90e3c56d78805c169ebc", "jaccard": 0.2857, "prev_hash": "ebff381aeecc3ac4576a994a6637bbde9241d07be69a7adb1af2df5f9b388f87", "seq": 34, "to": "2610.19247"}
{"from": "2610.26863", "hash": "602ea694f0f93b1e85cb8d9cf0c666d0da176f2414b467b31b73d316fb98b40d", "jaccard": 0.2857, "prev_hash": "1ddda6d6943dd7c06761372b9ecce4834e8da419665e90e3c56d78805c169ebc", "seq": 35, "to": "2610.19247"}
{"from": "2610.27341", "hash": "c2120f5946926ce98fffe15117674e31d8655514be584483f27c1299c9e65d74", "jaccard": 0.2857, "prev_hash": "602ea694f0f93b1e85cb8d9cf0c666d0da176f2414b467b31b73d316fb98b40d", "seq": 36, "to": "2610.25999"}
{"from": "2610.31098", "hash": "9fccd40736c7b5e89ee08812ed1733251a6d9a4b94bfebda4d34e4a0b844e4b3", "jaccard": 0.2857, "prev_hash": "c2120f5946926ce98fffe15117674e31d8655514be584483f27c1299c9e65d74", "seq": 37, "to": "2610.06479"}
{"from": "2610.10156", "hash": "99d27fc38f27967781b5c0f9f201de2fbb4c8a313609806f6d94b386dd15203d", "jaccard": 0.2727, "prev_hash": "9fccd40736c7b5e89ee08812ed1733251a6d9a4b94bfebda4d34e4a0b844e4b3", "seq": 38, "to": "2610.05940"}
{"from": "2610.19928", "hash": "c543fe695199079c3cc1ade4a8f5b80023ec1091e728b14a616e8ae234f15715", "jaccard": 0.2727, "prev_hash": "99d27fc38f27967781b5c0f9f201de2fbb4c8a313609806f6d94b386dd15203d", "seq": 39, "to": "2609.39740"}
{"from": "2610.22067", "hash": "38c4b463c6f66566aca397faf76abe2bf88d829414cd0e032f7e9a80fde87529", "jaccard": 0.2727, "prev_hash": "c543fe695199079c3cc1ade4a8f5b80023ec1091e728b14a616e8ae234f15715", "seq": 40, "to": "2610.13132"}
{"from": "2610.26431", "hash": "3a67e5d0dcb9b47ceb040ec9fd9b643d7617ff74a9a09a8b5261a300d3e2ec36", "jaccard": 0.2727, "prev_hash": "38c4b463c6f66566aca397faf76abe2bf88d829414cd0e032f7e9a80fde87529", "seq": 41, "to": "2610.11592"}
{"from": "2610.26585", "hash": "0a0273e86c10b1e2804025c04f3ac4aaff30a4fb23ba3281c0387c53b384d597", "jaccard": 0.2727, "prev_hash": "3a67e5d0dcb9b47ceb040ec9fd9b643d7617ff74a9a09a8b5261a300d3e2ec36", "seq": 42, "to": "2610.23250"}
{"from": "2610.27782", "hash": "10a976097e4050157ec738f66d31d4037253f92736df50ab16df8f90003d7e1e", "jaccard": 0.2727, "prev_hash": "0a0273e86c10b1e2804025c04f3ac4aaff30a4fb23ba3281c0387c53b384d597", "seq": 43, "to": "2610.26431"}
{"from": "2610.27782", "hash": "38026681ca472dc7468e92d93e57fbbc2cdcc305569aa11c6876dc45fe0aa18e", "jaccard": 0.2727, "prev_hash": "10a976097e4050157ec738f66d31d4037253f92736df50ab16df8f90003d7e1e", "seq": 44, "to": "2610.26863"}
{"from": "2610.28382", "hash": "70ee30e0d3cddcf4c72ac32acb90bc7dfc9d0172109e1c8792b5ea1d5bf95a68", "jaccard": 0.2727, "prev_hash": "38026681ca472dc7468e92d93e57fbbc2cdcc305569aa11c6876dc45fe0aa18e", "seq": 45, "to": "2609.31382"}
{"from": "2610.28382", "hash": "005ade964a2d46368dc92f9f96ba887eccbf2fbf28b27be5fdbba8bb82c09353", "jaccard": 0.2727, "prev_hash": "70ee30e0d3cddcf4c72ac32acb90bc7dfc9d0172109e1c8792b5ea1d5bf95a68", "seq": 46, "to": "2610.24378"}
{"from": "2610.29590", "hash": "c0eb47317b9cbd771441442397b5931eceb912d329bf00fb7fe23edf5d3f350b", "jaccard": 0.2727, "prev_hash": "005ade964a2d46368dc92f9f96ba887eccbf2fbf28b27be5fdbba8bb82c09353", "seq": 47, "to": "2610.26585"}
{"from": "2610.30108", "hash": "4c8fe3ba13ac91982ba93162e1d5371b8735c82ac88a1b6f00a2f827a8cfed21", "jaccard": 0.2727, "prev_hash": "c0eb47317b9cbd771441442397b5931eceb912d329bf00fb7fe23edf5d3f350b", "seq": 48, "to": "2610.24378"}
{"from": "2610.30132", "hash": "150fd76d53de2edd28901220b4a4894a0239cebda8fcb0948eea899c7a2dbc5b", "jaccard": 0.2727, "prev_hash": "4c8fe3ba13ac91982ba93162e1d5371b8735c82ac88a1b6f00a2f827a8cfed21", "seq": 49, "to": "2610.23167"}
{"from": "2610.32213", "hash": "4d0c7b7482c0357ecbb275497dcc8261f31cdf4d6842f17a589637b49f117aea", "jaccard": 0.2727, "prev_hash": "150fd76d53de2edd28901220b4a4894a0239cebda8fcb0948eea899c7a2dbc5b", "seq": 50, "to": "2609.38137"}
{"from": "2610.32311", "hash": "598fb0e3375c458e2c0f48af4e8a7372d1dd1e842819c4b99676ea366edfb96a", "jaccard": 0.2727, "prev_hash": "4d0c7b7482c0357ecbb275497dcc8261f31cdf4d6842f17a589637b49f117aea", "seq": 51, "to": "2610.32263"}
{"from": "2610.32332", "hash": "cfd8baaa540b55d0f007b463b03735faa3dd448d0b54dfe0225152d8987bad79", "jaccard": 0.2727, "prev_hash": "598fb0e3375c458e2c0f48af4e8a7372d1dd1e842819c4b99676ea366edfb96a", "seq": 52, "to": "2609.38137"}
{"from": "2610.32332", "hash": "16e71923cc9422066232dcc7cb19b2eb267c041f714b41139d7087ec5ed7defd", "jaccard": 0.2727, "prev_hash": "cfd8baaa540b55d0f007b463b03735faa3dd448d0b54dfe0225152d8987bad79", "seq": 53, "to": "2610.02472"}
{"from": "2610.32485", "hash": "4f94d0235636505efd115b49b31e114ffa43ea93326bdf55879fc53b3cf94ce9", "jaccard": 0.2727, "prev_hash": "16e71923cc9422066232dcc7cb19b2eb267c041f714b41139d7087ec5ed7defd", "seq": 54, "to": "2610.27360"}
{"from": "2610.32612", "hash": "4613acaa92879f528af026db5cc1ae79cd512de81d00167264a783820e17b029", "jaccard": 0.2727, "prev_hash": "4f94d0235636505efd115b49b31e114ffa43ea93326bdf55879fc53b3cf94ce9", "seq": 55, "to": "2610.29642"}
{"from": "2610.32784", "hash": "89ddfcccfde591d4a3fdd87b1b7bcab6b38cd4cfc1008adc51d79d0e354f5245", "jaccard": 0.2727, "prev_hash": "4613acaa92879f528af026db5cc1ae79cd512de81d00167264a783820e17b029", "seq": 56, "to": "2610.05940"}
{"from": "2610.21165", "hash": "90bec0558c471ccc576284a865566e1ad74da33ffa28e7a94f0d213d1a09c6ab", "jaccard": 0.2667, "prev_hash": "89ddfcccfde591d4a3fdd87b1b7bcab6b38cd4cfc1008adc51d79d0e354f5245", "seq": 57, "to": "2609.29370"}
{"from": "2610.28264", "hash": "1f15742a9fee9adf803f86fc277c01798e69c88002a9b47e65b7be2626ef5578", "jaccard": 0.2667, "prev_hash": "90bec0558c471ccc576284a865566e1ad74da33ffa28e7a94f0d213d1a09c6ab", "seq": 58, "to": "2610.25005"}
{"from": "2610.28264", "hash": "c96471d12ef21c73ae9f7b172e49474c3ec030336f14dde69773bbefc1da8f6f", "jaccard": 0.2667, "prev_hash": "1f15742a9fee9adf803f86fc277c01798e69c88002a9b47e65b7be2626ef5578", "seq": 59, "to": "2610.27782"}
{"from": "2610.31205", "hash": "a22e89c6c267f72015cc140bfdd1d878fca3cb53e9783db1c25670be327dd7df", "jaccard": 0.2667, "prev_hash": "c96471d12ef21c73ae9f7b172e49474c3ec030336f14dde69773bbefc1da8f6f", "seq": 60, "to": "2610.00969"}
{"from": "2610.32213", "hash": "d00a635764924e7402f8d5e1527f13c9dcb2498aa11cf568ce2c360256eef068", "jaccard": 0.2667, "prev_hash": "a22e89c6c267f72015cc140bfdd1d878fca3cb53e9783db1c25670be327dd7df", "seq": 61, "to": "2609.35831"}
{"from": "2610.00982", "hash": "1ef5f897e2ae1d8cd43e9e8ff1305b974a7bc92c6c20f02c3d5fafe2e0dcfa39", "jaccard": 0.25, "prev_hash": "d00a635764924e7402f8d5e1527f13c9dcb2498aa11cf568ce2c360256eef068", "seq": 62, "to": "2609.39740"}
{"from": "2610.06479", "hash": "deec88bc693b903ec5c0bbb0a1b0a9cefa518e861040505ffcf061583e82fa24", "jaccard": 0.25, "prev_hash": "1ef5f897e2ae1d8cd43e9e8ff1305b974a7bc92c6c20f02c3d5fafe2e0dcfa39", "seq": 63, "to": "2610.03198"}
{"from": "2610.06479", "hash": "c3cf55b318faeec630c7f432a743c35aeba9882d4ff8d2b5c6090818622d110d", "jaccard": 0.25, "prev_hash": "deec88bc693b903ec5c0bbb0a1b0a9cefa518e861040505ffcf061583e82fa24", "seq": 64, "to": "2610.06286"}
{"from": "2610.06479", "hash": "815513954e85fa177c422487ce87cad0341e81ee93206e27d8e0e5351e3ad2d6", "jaccard": 0.25, "prev_hash": "c3cf55b318faeec630c7f432a743c35aeba9882d4ff8d2b5c6090818622d110d", "seq": 65, "to": "2610.06927"}
{"from": "2610.10918", "hash": "a2da1eb421a8e7dec089f060793ce901c9b1ffd485c78b4a6a3840b3f908d1b3", "jaccard": 0.25, "prev_hash": "815513954e85fa177c422487ce87cad0341e81ee93206e27d8e0e5351e3ad2d6", "seq": 66, "to": "2610.00969"}
{"from": "2610.15542", "hash": "f58297056629f83f975b12c631fdc064ec0551e2de55822a727065431f360d5d", "jaccard": 0.25, "prev_hash": "a2da1eb421a8e7dec089f060793ce901c9b1ffd485c78b4a6a3840b3f908d1b3", "seq": 67, "to": "2610.15371"}
{"from": "2610.19247", "hash": "64f383299793eba4d60511d0cda1f68d242741e2b864f1d0b36aacd2f79d4296", "jaccard": 0.25, "prev_hash": "f58297056629f83f975b12c631fdc064ec0551e2de55822a727065431f360d5d", "seq": 68, "to": "2609.33465"}
{"from": "2610.19247", "hash": "9ca646214fa5543a4d53bd27c58988c6c47bb7a6ca674280c23cbd619a26100f", "jaccard": 0.25, "prev_hash": "64f383299793eba4d60511d0cda1f68d242741e2b864f1d0b36aacd2f79d4296", "seq": 69, "to": "2609.38137"}
{"from": "2610.25005", "hash": "128dd7f04609fa79711f7b42fbfc37c1c94f0d57581a7d77c829ac65ce62aa15", "jaccard": 0.25, "prev_hash": "9ca646214fa5543a4d53bd27c58988c6c47bb7a6ca674280c23cbd619a26100f", "seq": 70, "to": "2609.33465"}
{"from": "2610.25919", "hash": "3b3eb0a6d97dbf870162963e8cc643e50a658fa1b81bef333bb06f063dd4623b", "jaccard": 0.25, "prev_hash": "128dd7f04609fa79711f7b42fbfc37c1c94f0d57581a7d77c829ac65ce62aa15", "seq": 71, "to": "2609.38809"}
{"from": "2610.26431", "hash": "feb6146f2ab614aad76bd71aafa0c0f92909a5d2a0aa9669b9d04734dbb579df", "jaccard": 0.25, "prev_hash": "3b3eb0a6d97dbf870162963e8cc643e50a658fa1b81bef333bb06f063dd4623b", "seq": 72, "to": "2609.36529"}
{"from": "2610.26431", "hash": "c5c7f5dd3453bb9aa8b62300564fe1c495e6c0fd849a5972654e35be475733af", "jaccard": 0.25, "prev_hash": "feb6146f2ab614aad76bd71aafa0c0f92909a5d2a0aa9669b9d04734dbb579df", "seq": 73, "to": "2610.13041"}
{"from": "2610.26431", "hash": "45a6404233a651c60f113e4074bf13c8d81670e42aff4a4a6b774c046f388adc", "jaccard": 0.25, "prev_hash": "c5c7f5dd3453bb9aa8b62300564fe1c495e6c0fd849a5972654e35be475733af", "seq": 74, "to": "2610.21165"}
{"from": "2610.27341", "hash": "8aded827378b4919e216949f4683a680ed62735f0e7aadb045794b52ee27791d", "jaccard": 0.25, "prev_hash": "45a6404233a651c60f113e4074bf13c8d81670e42aff4a4a6b774c046f388adc", "seq": 75, "to": "2610.25247"}
{"from": "2610.27692", "hash": "c85e917aa8db9a3084489ff9b76d4dee86dc3d61da9a2c22b25f843bfcf116ae", "jaccard": 0.25, "prev_hash": "8aded827378b4919e216949f4683a680ed62735f0e7aadb045794b52ee27791d", "seq": 76, "to": "2610.05940"}
{"from": "2610.27692", "hash": "c5a5fdc4462100c095f40e8718b350cdf5738db128a46f84586098adba2133a2", "jaccard": 0.25, "prev_hash": "c85e917aa8db9a3084489ff9b76d4dee86dc3d61da9a2c22b25f843bfcf116ae", "seq": 77, "to": "2610.10156"}
{"from": "2610.27782", "hash": "01ea1d41b65164b086ac3d3f827dd498bd94e1b34430ff13e4fb5a43e8f80ab5", "jaccard": 0.25, "prev_hash": "c5a5fdc4462100c095f40e8718b350cdf5738db128a46f84586098adba2133a2", "seq": 78, "to": "2610.26585"}
{"from": "2610.29495", "hash": "12627418754763093e3e93d56d80951dc8575d3bcb66671ffacd4fa6a0d74262", "jaccard": 0.25, "prev_hash": "01ea1d41b65164b086ac3d3f827dd498bd94e1b34430ff13e4fb5a43e8f80ab5", "seq": 79, "to": "2610.27298"}
{"from": "2610.29590", "hash": "b7074562ff3565b1e588493c7ccf79d98e99cd1bb3739c84b57976cf159ab40c", "jaccard": 0.25, "prev_hash": "12627418754763093e3e93d56d80951dc8575d3bcb66671ffacd4fa6a0d74262", "seq": 80, "to": "2610.26022"}
{"from": "2610.29590", "hash": "5726c98bbed7af9235c858b1fc7dbb7235123daa4bf15d640b51967a5d149e78", "jaccard": 0.25, "prev_hash": "b7074562ff3565b1e588493c7ccf79d98e99cd1bb3739c84b57976cf159ab40c", "seq": 81, "to": "2610.27782"}
{"from": "2610.30206", "hash": "27ec221bbf7953fe92ff73955c22c44309f5a4050c00bd1043cb53418fc23908", "jaccard": 0.25, "prev_hash": "5726c98bbed7af9235c858b1fc7dbb7235123daa4bf15d640b51967a5d149e78", "seq": 82, "to": "2610.06479"}
{"from": "2610.30913", "hash": "b149f5d440aa18d85887aa57e8bfd2a322c95a0ff2cba6b38d3e7283f064bd1e", "jaccard": 0.25, "prev_hash": "27ec221bbf7953fe92ff73955c22c44309f5a4050c00bd1043cb53418fc23908", "seq": 83, "to": "2610.30592"}
{"from": "2610.31098", "hash": "6fb0e6797353702c763b2cd585f846c53675c8d115478eb7fc900c7a61e4d668", "jaccard": 0.25, "prev_hash": "b149f5d440aa18d85887aa57e8bfd2a322c95a0ff2cba6b38d3e7283f064bd1e", "seq": 84, "to": "2610.20582"}
{"from": "2610.31158", "hash": "f5c3b3d8264d7d5c17c31d857d3b7a1d159fbe3ed01af18731c3c7398ac061ef", "jaccard": 0.25, "prev_hash": "6fb0e6797353702c763b2cd585f846c53675c8d115478eb7fc900c7a61e4d668", "seq": 85, "to": "2610.25493"}
{"from": "2610.31205", "hash": "91d861f3113778c2ff32e8072a4f0383f99ffa9dd35267ad760a08472b06fed6", "jaccard": 0.25, "prev_hash": "f5c3b3d8264d7d5c17c31d857d3b7a1d159fbe3ed01af18731c3c7398ac061ef", "seq": 86, "to": "2610.28264"}
{"from": "2610.31945", "hash": "5df7dafd79141080d3ed7a0a03b6be33df2800b66188f8af31d2f771f3b73ec0", "jaccard": 0.25, "prev_hash": "91d861f3113778c2ff32e8072a4f0383f99ffa9dd35267ad760a08472b06fed6", "seq": 87, "to": "2610.15371"}
{"from": "2610.32177", "hash": "8001e13fe0582edf31cb651c0874c6848622c35de5082cc769d95f1b1318322c", "jaccard": 0.25, "prev_hash": "5df7dafd79141080d3ed7a0a03b6be33df2800b66188f8af31d2f771f3b73ec0", "seq": 88, "to": "2610.29811"}
{"from": "2610.32213", "hash": "678347bf306fe8863aabe0d1d718c07b1f190dff193a6d5fe0e3ff1a98e9dfe8", "jaccard": 0.25, "prev_hash": "8001e13fe0582edf31cb651c0874c6848622c35de5082cc769d95f1b1318322c", "seq": 89, "to": "2610.11592"}
{"from": "2610.32213", "hash": "b23e48e6f1f89d784d185d5cc6f9fcf95074fa86638228b1361c3541f31fb99d", "jaccard": 0.25, "prev_hash": "678347bf306fe8863aabe0d1d718c07b1f190dff193a6d5fe0e3ff1a98e9dfe8", "seq": 90, "to": "2610.19247"}
{"from": "2610.32213", "hash": "3661607cd21f889e0d783b76bb600101bfa264b6ce0988f793a9bd3e9dea3054", "jaccard": 0.25, "prev_hash": "b23e48e6f1f89d784d185d5cc6f9fcf95074fa86638228b1361c3541f31fb99d", "seq": 91, "to": "2610.25005"}
{"from": "2610.32263", "hash": "91023ceb7ce6f6e6bc55a55535e0aa6a2b21e94123ea262428713ce27d3a81ac", "jaccard": 0.25, "prev_hash": "3661607cd21f889e0d783b76bb600101bfa264b6ce0988f793a9bd3e9dea3054", "seq": 92, "to": "2610.28266"}
{"from": "2610.32360", "hash": "522e7d002efafb897aba326608d903c8d7028a06fb02552c65dba8ac79e007ca", "jaccard": 0.25, "prev_hash": "91023ceb7ce6f6e6bc55a55535e0aa6a2b21e94123ea262428713ce27d3a81ac", "seq": 93, "to": "2610.19247"}
{"from": "2610.32478", "hash": "2c0bf612e7c9db3cd0db0ac91cee031e4c2ef20d2e0b72722e4775f058e4988c", "jaccard": 0.25, "prev_hash": "522e7d002efafb897aba326608d903c8d7028a06fb02552c65dba8ac79e007ca", "seq": 94, "to": "2610.27360"}
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""indep_check.py -- a SECOND verifier, written from scratch.
Deliberately shares no code with run_verdict.py: it re-fetches the live arXiv
API and compares the archived corpus against what arXiv serves NOW, and it
re-walks the frozen sources using a different parsing strategy (regex over
raw bytes, not ElementTree).
Exit 0 iff the archived corpus is consistent with live arXiv and with itself.
python3 indep_check.py # exit 0 = independently corroborated
"""
import hashlib
import json
import os
import re
import sys
import urllib.parse
import urllib.request
HERE = os.path.dirname(os.path.abspath(__file__))
FAILS = []
def ck(name, cond, detail=""):
print(f" [{'PASS' if cond else 'FAIL'}] {name}" + (f" -- {detail}" if detail else ""))
if not cond:
FAILS.append(name)
def main():
corpus = json.load(open(os.path.join(HERE, "corpus.json")))
# --- A. live arXiv corroboration -------------------------------------
# Re-query arXiv for the exact ids we archived and confirm the titles and
# publication dates still match what we froze.
ids = sorted({nd["node"] for nd in corpus["nodes"]})
ck("corpus has nodes", len(ids) > 0, f"{len(ids)} nodes")
live = {}
B = 50
for i in range(0, len(ids), B):
chunk = ids[i:i + B]
q = urllib.parse.urlencode(
{"id_list": ",".join(chunk), "start": 0, "max_results": len(chunk)}
)
url = f"https://export.arxiv.org/api/query?{q}"
try:
req = urllib.request.Request(url, headers={"User-Agent": "sphinx-indep/1.0"})
body = urllib.request.urlopen(req, timeout=90).read().decode("utf-8", "replace")
except Exception as ex:
ck(f"live fetch chunk {i}", False, f"{type(ex).__name__}: {ex}")
continue
# regex parse, NOT ElementTree
for m in re.finditer(r"<entry>(.*?)</entry>", body, re.S):
blk = m.group(1)
idm = re.search(r"<id>http://arxiv\.org/abs/([^<]+)</id>", blk)
tm = re.search(r"<title>(.*?)</title>", blk, re.S)
pm = re.search(r"<published>([^<]+)</published>", blk)
if not (idm and tm):
continue
node = re.sub(r"v\d+$", "", idm.group(1).strip())
live[node] = {
"title": re.sub(r"\s+", " ", tm.group(1)).strip(),
"published": pm.group(1).strip() if pm else "",
}
ck(f"live fetch chunk {i} returned entries", len(live) > 0, f"cumulative {len(live)}")
print(f" [info] live arXiv returned {len(live)} of {len(ids)} archived ids")
mism = []
for nd in corpus["nodes"]:
lv = live.get(nd["node"])
if lv is None:
mism.append((nd["node"], "absent from live arXiv"))
elif lv["title"] != nd["title"]:
mism.append((nd["node"], "title differs"))
ck("archived titles match live arXiv", not mism, f"{len(mism)} mismatches")
for n, why in mism[:5]:
print(f" {n}: {why}")
# --- B. frozen source integrity, parsed by regex ---------------------
for fm in corpus["fetch"]:
p = os.path.join(HERE, fm["raw_path"])
if not os.path.exists(p):
ck(f"source exists {fm['label']}", False)
continue
raw = open(p, "rb").read()
ck(f"source sha256 {fm['label']}",
hashlib.sha256(raw).hexdigest() == fm["raw_sha256"])
# count <entry> blocks by regex and compare to the manifest
n_blocks = len(re.findall(rb"<entry>", raw))
ck(f"entry count {fm['label']}", n_blocks == fm["returned"],
f"regex={n_blocks} manifest={fm['returned']}")
# --- C. journal chain, recomputed with a different construction ------
rows = [json.loads(l) for l in open(os.path.join(HERE, "edges.jsonl")) if l.strip()]
ck("journal non-empty", len(rows) > 0, f"{len(rows)} rows")
prev = "0" * 64
bad = 0
for i, r in enumerate(rows, start=1):
expect = hashlib.sha256(
json.dumps(
{"seq": i, "from": r["from"], "to": r["to"],
"jaccard": r["jaccard"], "prev_hash": prev},
sort_keys=True, separators=(",", ":"),
).encode()
).hexdigest()
if r["hash"] != expect or r["prev_hash"] != prev:
bad += 1
prev = r["hash"]
ck("journal chain recomputes", bad == 0, f"{bad} bad")
ck("journal head matches manifest", prev == corpus["journal"]["head"])
# --- D. DAG by Kahn's algorithm (different method than run_verdict) --
nodes = [nd["node"] for nd in corpus["nodes"]]
pub = {nd["node"]: nd["published"] for nd in corpus["nodes"]}
indeg = {n: 0 for n in nodes}
adj = {n: [] for n in nodes}
for e in corpus["edges"]:
adj[e["from"]].append(e["to"])
indeg[e["to"]] = indeg.get(e["to"], 0) + 1
queue = [n for n in nodes if indeg[n] == 0]
seen = 0
while queue:
u = queue.pop()
seen += 1
for v in adj[u]:
indeg[v] -= 1
if indeg[v] == 0:
queue.append(v)
ck("Kahn topological sort consumes all nodes (acyclic)", seen == len(nodes),
f"consumed {seen}/{len(nodes)}")
# --- E. edge direction sanity ----------------------------------------
bad_dir = [e for e in corpus["edges"] if pub[e["to"]] > pub[e["from"]]]
ck("edges respect publication order", not bad_dir, f"{len(bad_dir)} violations")
print()
if FAILS:
print(f"VERDICT: FAIL -- {len(FAILS)} failed: {', '.join(FAILS)}")
return 1
print(f"VERDICT: PASS -- independently corroborated ({len(ids)} nodes vs live arXiv)")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""mutation_battery.py -- falsifiability harness for run_verdict.py.
Each mutation corrupts ONE thing in a copy of the archive; run_verdict.py must
FAIL on that copy. If any mutation goes undetected, the verdict is not a real
test. Exit 0 iff every mutation is caught.
python3 mutation_battery.py # exit 0 = all mutations caught
"""
import json
import os
import shutil
import subprocess
import sys
import tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
CAUGHT = []
MISSED = []
def run_verdict_in(d):
p = subprocess.run(
[sys.executable, os.path.join(d, "run_verdict.py")],
capture_output=True, text=True, cwd=d,
)
return p.returncode
def clone():
d = tempfile.mkdtemp(prefix="mut-")
for name in ("corpus.json", "run_verdict.py"):
shutil.copy(os.path.join(HERE, name), os.path.join(d, name))
os.makedirs(os.path.join(d, "source"), exist_ok=True)
for f in os.listdir(os.path.join(HERE, "source")):
shutil.copy(os.path.join(HERE, "source", f), os.path.join(d, "source", f))
os.makedirs(os.path.join(d, "source"), exist_ok=True)
shutil.copy(os.path.join(HERE, "edges.jsonl"), os.path.join(d, "edges.jsonl"))
return d
def mutate(name, fn):
d = clone()
fn(d)
rc = run_verdict_in(d)
ok = rc != 0
(CAUGHT if ok else MISSED).append(name)
print(f" [{'CAUGHT' if ok else 'MISSED'}] {name} (verdict rc={rc})")
shutil.rmtree(d, ignore_errors=True)
def load(d):
return json.load(open(os.path.join(d, "corpus.json")))
def save(d, m):
json.dump(m, open(os.path.join(d, "corpus.json"), "w"), indent=2, sort_keys=True)
def m_title(d):
m = load(d)
m["nodes"][0]["title"] = "TOTALLY FABRICATED TITLE"
save(d, m)
def m_proof_line(d):
m = load(d)
m["nodes"][0]["proof"]["line"] += 7
save(d, m)
def m_proof_hash(d):
m = load(d)
m["nodes"][1]["proof"]["block_sha256"] = "0" * 64
save(d, m)
def m_edge_jaccard(d):
m = load(d)
m["edges"][0]["jaccard"] = 0.9999
save(d, m)
def m_edge_reverse(d):
m = load(d)
e = m["edges"][0]
e["from"], e["to"] = e["to"], e["from"]
save(d, m)
def m_count(d):
m = load(d)
m["counts"]["distinct_nodes"] += 5
save(d, m)
def m_source_bytes(d):
p = os.path.join(d, "source", "arxiv-mm.xml")
b = open(p, "rb").read()
open(p, "wb").write(b + b"\n<!-- injected -->\n")
def m_node_missing(d):
m = load(d)
m["nodes"] = m["nodes"][:-1]
save(d, m)
def m_edge_endpoint(d):
m = load(d)
m["edges"][0]["from"] = "9999.99999"
save(d, m)
def m_journal_row(d):
p = os.path.join(d, "edges.jsonl")
lines = open(p).read().splitlines()
row = json.loads(lines[0])
row["jaccard"] = 0.1111
lines[0] = json.dumps(row, sort_keys=True)
open(p, "w").write("\n".join(lines) + "\n")
def m_drop_journal(d):
os.remove(os.path.join(d, "edges.jsonl"))
def main():
# baseline: unmutated copy must PASS
d0 = clone()
base = run_verdict_in(d0)
shutil.rmtree(d0, ignore_errors=True)
print(f" [{'PASS' if base == 0 else 'FAIL'}] baseline unmutated archive (rc={base})")
if base != 0:
print("VERDICT: FAIL -- baseline does not pass; mutations are meaningless")
return 1
mutate("title rewritten", m_title)
mutate("proof line number shifted", m_proof_line)
mutate("proof block hash zeroed", m_proof_hash)
mutate("edge jaccard inflated", m_edge_jaccard)
mutate("edge direction reversed", m_edge_reverse)
mutate("manifest node count inflated", m_count)
mutate("raw source bytes appended", m_source_bytes)
mutate("node dropped from manifest", m_node_missing)
mutate("edge endpoint set to unknown node", m_edge_endpoint)
mutate("journal row body altered", m_journal_row)
mutate("journal file deleted", m_drop_journal)
print()
if MISSED:
print(f"VERDICT: FAIL -- {len(MISSED)} mutations MISSED: {', '.join(MISSED)}")
return 1
print(f"VERDICT: PASS -- {len(CAUGHT)}/{len(CAUGHT)} mutations caught")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,12 @@
import glob, re, xml.etree.ElementTree as ET
A = "{http://www.w3.org/2005/Atom}"
for p in sorted(glob.glob("source/*.xml")):
b = open(p, "rb").read()
print("=====", p, len(b), "bytes; arXiv: mentions =",
len(re.findall(rb"arXiv:\s*\d{4}\.\d{4,5}", b, re.I)))
root = ET.fromstring(b)
for e in root.findall(A + "entry")[:3]:
aid = re.search(r"abs/(.+)$", e.findtext(A + "id", "")).group(1)
ab = re.sub(r"\s+", " ", (e.findtext(A + "summary", "") or ""))
print(" ", aid, "|", ab[:230])
@@ -0,0 +1,24 @@
"""probe_figs.py -- MEASURE multimodal asset availability in the corpus.
For the lane's core papers, fetch ar5iv HTML and count <img> / <figure> and
math elements. This decides the archive's multimodal dimension.
"""
import re
import urllib.request
IDS = ["2610.32213", "2610.32781", "2610.32967", "2610.32483", "2610.32612"]
for aid in IDS:
url = f"https://ar5iv.labs.arxiv.org/html/{aid}"
try:
req = urllib.request.Request(url, headers={"User-Agent": "sphinx/1.0"})
body = urllib.request.urlopen(req, timeout=60).read().decode("utf-8", "replace")
imgs = re.findall(r"<img[^>]+>", body)
figs = re.findall(r"<figure", body)
svgs = re.findall(r"<svg", body)
alts = re.findall(r'alt="([^"]{5,})"', body)
print(f"{aid} bytes={len(body):7d} <img>={len(imgs):3d} <figure>={len(figs):3d} <svg>={len(svgs):3d} alts={len(alts)}")
if alts:
print(" e.g.", alts[0][:100])
except Exception as ex:
print(f"{aid} FAIL {type(ex).__name__}: {ex}")
@@ -0,0 +1,14 @@
import urllib.parse, urllib.request, re
ids = ["2610.32213", "2610.32781", "2610.15371", "2609.25611"]
q = urllib.parse.urlencode({"search_query": "id_list=" + ",".join(ids), "start": 0, "max_results": 50})
url = "https://export.arxiv.org/api/query?" + q
print("URL:", url)
req = urllib.request.Request(url, headers={"User-Agent": "sphinx/1.0"})
body = urllib.request.urlopen(req, timeout=60).read().decode("utf-8", "replace")
print("bytes:", len(body))
print("entries:", len(re.findall(r"<entry>", body)))
print(body[:400])
print("...")
m = re.search(r"<title>(.*?)</title>", body, re.S)
print("feed title:", m.group(1)[:200] if m else None)
@@ -0,0 +1,28 @@
"""probe_kws.py -- MEASURE lane-keyword density across the fetched corpus."""
import glob
import re
import xml.etree.ElementTree as ET
A = "{http://www.w3.org/2005/Atom}"
KW = [
"multimodal", "multi-modal", "vision-language", "vlm", "mllm",
"long context", "long-context", "long context", "kv cache", "kv-cache",
"context window", "token", "retrieval-augmented", "rag", "benchmark",
"grounding", "hallucination", "attention", "fine-tuning", "rlhf",
]
recs = []
for p in sorted(glob.glob("source/*.xml")):
root = ET.fromstring(open(p, "rb").read())
for e in root.findall(A + "entry"):
aid = re.search(r"abs/(.+)$", e.findtext(A + "id", "")).group(1)
ab = re.sub(r"\s+", " ", (e.findtext(A + "summary", "") or "")).lower()
recs.append((aid, ab))
print("corpus:", len(recs))
counts = {k: 0 for k in KW}
for aid, ab in recs:
for k in KW:
if k in ab:
counts[k] += 1
for k in sorted(KW, key=lambda x: -counts[x]):
print(f" {counts[k]:3d}/{len(recs)} {k}")
@@ -0,0 +1,39 @@
"""probe_refs.py -- MEASURE the real citation signal.
Fetch the arXiv (ar5iv) HTML for a sample of the fetched papers and count
how many of their reference-list entries resolve to arXiv IDs that are also
in our slice. This decides whether the citation graph is a real artifact or
a tautological empty set.
"""
import os
import re
import urllib.request
IDS = ["2610.32213", "2610.32781", "2610.32967", "2610.32483", "2610.32612"]
SLICE = set()
for line in open("probe_titles.py"):
pass
# load slice ids from the saved XML
import glob
import xml.etree.ElementTree as ET
A = "{http://www.w3.org/2005/Atom}"
for p in sorted(glob.glob("source/*.xml")):
root = ET.fromstring(open(p, "rb").read())
for e in root.findall(A + "entry"):
SLICE.add(re.search(r"abs/(.+)$", e.findtext(A + "id", "")).group(1))
ARXIV_REF = re.compile(r"arXiv:\s*(\d{4}\.\d{4,5})", re.I)
for aid in IDS:
for host in ("ar5iv.labs.arxiv.org/html/", "arxiv.org/abs/"):
url = f"https://{host}{aid}"
try:
req = urllib.request.Request(url, headers={"User-Agent": "sphinx/1.0"})
body = urllib.request.urlopen(req, timeout=60).read()
n = len(ARXIV_REF.findall(body.decode("utf-8", "replace")))
print(f"{aid:14s} {host:32s} {len(body):8d} B arXiv-refs={n}")
break
except Exception as ex:
print(f"{aid:14s} {host:32s} FAIL {type(ex).__name__}: {ex}")
@@ -0,0 +1,58 @@
"""probe_titles.py -- MEASURE title-overlap density between fetched papers.
Question: is there a real, non-trivial graph signal between the two slices
(multimodal cs.CV and long-context cs.CL)? Titles are stripped of stopwords
and stemmed crudely; we report pairwise Jaccard similarity so we can pick a
threshold that yields a genuine graph rather than a hairball or an empty set.
"""
import glob
import re
import xml.etree.ElementTree as ET
from itertools import combinations
A = "{http://www.w3.org/2005/Atom}"
STOP = {
"a", "an", "the", "of", "for", "and", "or", "to", "in", "on", "with", "via",
"by", "from", "as", "at", "is", "are", "we", "our", "their", "its", "this",
"that", "these", "those", "using", "based", "towards", "through", "into",
"how", "what", "why", "when", "can", "do", "does", "be", "been", "than",
}
recs = []
for p in sorted(glob.glob("source/*.xml")):
root = ET.fromstring(open(p, "rb").read())
for e in root.findall(A + "entry"):
aid = re.search(r"abs/(.+)$", e.findtext(A + "id", "")).group(1)
t = re.sub(r"\s+", " ", (e.findtext(A + "title", "") or ""))
toks = set()
for w in re.findall(r"[A-Za-z][A-Za-z\-]+", t.lower()):
w = w.strip("-")
if len(w) < 3 or w in STOP:
continue
if len(w) > 4:
w = w[:4] # crude stem
toks.add(w)
recs.append((aid, t, toks))
print("records:", len(recs))
pairs = 0
buckets = {0.2: 0, 0.25: 0, 0.3: 0, 0.35: 0, 0.4: 0}
best = []
for (a1, t1, s1), (a2, t2, s2) in combinations(recs, 2):
if not s1 or not s2:
continue
j = len(s1 & s2) / len(s1 | s2)
if j > 0:
pairs += 1
for thr in buckets:
if j >= thr:
buckets[thr] += 1
best.append((j, a1, a2))
best.sort(reverse=True)
print("pairs with jaccard>0:", pairs)
for thr in sorted(buckets):
print(f" jaccard >= {thr}: {buckets[thr]} pairs")
print("top 12 overlaps:")
for j, a1, a2 in best[:12]:
print(f" {j:.3f} {a1} <-> {a2}")
@@ -0,0 +1,32 @@
"""probe_xref.py -- MEASURE whether real cross-references exist within the slice."""
import glob
import re
import xml.etree.ElementTree as ET
A = "{http://www.w3.org/2005/Atom}"
recs = []
for p in sorted(glob.glob("source/*.xml")):
root = ET.fromstring(open(p, "rb").read())
for e in root.findall(A + "entry"):
aid = re.search(r"abs/(.+)$", e.findtext(A + "id", "")).group(1)
ab = re.sub(r"\s+", " ", (e.findtext(A + "summary", "") or "")).lower()
recs.append((aid, ab))
# Jaccard overlap between abstract sets
def toks(s):
return set(re.findall(r"[a-z]{4,}", s))
nodes = {a: toks(ab) for a, ab in recs}
hits = []
ids = list(nodes)
for i in range(len(ids)):
for j in range(i + 1, len(ids)):
a, b = ids[i], ids[j]
inter = nodes[a] & nodes[b]
if len(inter) >= 8:
hits.append((len(inter), a, b, sorted(inter)[:8]))
hits.sort(reverse=True)
print("pairs with >=8 shared rare-ish tokens:", len(hits))
for n, a, b, ex in hits[:10]:
print(f" {n:3d} {a} <-> {b} {ex}")
@@ -0,0 +1,209 @@
#!/usr/bin/env python3
"""run_verdict.py -- sphinx Archive lane. THE VERDICT COMMAND.
Exit 0 iff the archive holds. Written independently of build_corpus.py:
it re-parses the frozen raw XML from scratch, re-derives every node and
every proof, re-walks the hash chain, and checks the DAG property.
python3 run_verdict.py # exit 0 = the archive holds
"""
import hashlib
import json
import os
import re
import sys
import xml.etree.ElementTree as ET
HERE = os.path.dirname(os.path.abspath(__file__))
ATOM = "{http://www.w3.org/2005/Atom}"
ARXIV = "{http://arxiv.org/schemas/atom}"
FAILS = []
def check(name, cond, detail=""):
print(f" [{'PASS' if cond else 'FAIL'}] {name}" + (f" -- {detail}" if detail else ""))
if not cond:
FAILS.append(name)
def canon(aid):
return re.sub(r"v\d+$", "", aid.strip())
def h(b):
return hashlib.sha256(b).hexdigest()
def main():
corpus = json.load(open(os.path.join(HERE, "corpus.json")))
nodes = corpus["nodes"]
print(f"archive: {corpus['artifact']} built {corpus['built_utc']}")
print(f"nodes={len(nodes)} edges={len(corpus['edges'])}")
# ---- 1. raw sources exist and match their recorded hash ----
for fm in corpus["fetch"]:
p = os.path.join(HERE, fm["raw_path"])
ok = os.path.exists(p)
check(f"source present: {fm['raw_path']}", ok)
if not ok:
continue
raw = open(p, "rb").read()
check(f"source sha256 matches: {fm['label']}", h(raw) == fm["raw_sha256"],
f"recorded={fm['raw_sha256'][:12]} actual={h(raw)[:12]}")
check(f"source byte count matches: {fm['label']}", len(raw) == fm["raw_bytes"])
# ---- 2. every node's proof line + block hash re-derive from raw XML ----
raw_by_label = {}
for fm in corpus["fetch"]:
raw_by_label[fm["label"]] = open(os.path.join(HERE, fm["raw_path"]), "rb").read()
bad_line = bad_hash = bad_title = 0
for nd in nodes:
raw = raw_by_label[nd["slice"]]
lines = raw.split(b"\n")
ln = nd["proof"]["line"]
if not (1 <= ln <= len(lines)) or lines[ln - 1].strip() != b"<entry>":
bad_line += 1
continue
# re-derive block
starts = [i for i, x in enumerate(lines) if x.strip() == b"<entry>"]
idx = starts.index(ln - 1)
end = starts[idx + 1] if idx + 1 < len(starts) else len(lines)
block = b"\n".join(lines[ln - 1:end])
if h(block) != nd["proof"]["block_sha256"] or len(block) != nd["proof"]["block_bytes"]:
bad_hash += 1
# re-derive the title from the block itself
m = re.search(rb"<title[^>]*>(.*?)</title>", block, re.S)
if m:
t = re.sub(r"\s+", " ", m.group(1).decode("utf-8", "replace")).strip()
if t != nd["title"]:
bad_title += 1
check("proof line numbers all point at <entry>", bad_line == 0, f"{bad_line} bad")
check("proof block hashes all re-derive", bad_hash == 0, f"{bad_hash} bad")
check("titles re-derive from proof block", bad_title == 0, f"{bad_title} bad")
# ---- 3. dedupe: node ids unique, and every node is present in its raw slice ----
ids = [nd["node"] for nd in nodes]
check("node ids unique", len(ids) == len(set(ids)))
present = set()
for label, raw in raw_by_label.items():
root = ET.fromstring(raw)
for e in root.findall(f"{ATOM}entry"):
aid = re.search(r"abs/([^/]+)$", e.findtext(f"{ATOM}id", "").strip())
if aid:
present.add(canon(aid.group(1)))
check("every node exists in frozen raw XML", set(ids) <= present,
f"{len(set(ids) - present)} missing")
# ---- 4. hash-chained append-only edge journal ----
prev = "0" * 64
bad_chain = 0
rows = [json.loads(l) for l in open(os.path.join(HERE, corpus["journal"]["path"])) if l.strip()]
for i, row in enumerate(rows, start=1):
if row["seq"] != i or row["prev_hash"] != prev:
bad_chain += 1
continue
body = {k: row[k] for k in ("seq", "from", "to", "jaccard", "prev_hash")}
if h(json.dumps(body, sort_keys=True, separators=(",", ":")).encode()) != row["hash"]:
bad_chain += 1
prev = row["hash"]
check("edge journal hash chain intact", bad_chain == 0, f"{bad_chain} bad rows")
check("journal head matches manifest", prev == corpus["journal"]["head"])
check("journal entry count matches manifest", len(rows) == corpus["journal"]["entries"])
# ---- 5. DAG: every edge points later-published -> earlier-published ----
pub = {nd["node"]: nd["published"] for nd in nodes}
bad_dir = [e for e in corpus["edges"] if not (pub[e["to"]] <= pub[e["from"]])]
check("all edges point to an earlier-or-equal publication date", not bad_dir,
f"{len(bad_dir)} violations")
# ---- 6. cycle check by longest-path (a DAG has longest path <= node count) ----
adj = {}
for e in corpus["edges"]:
adj.setdefault(e["from"], set()).add(e["to"])
# iterative DFS longest path with memo; detect cycle via recursion stack
WHITE, GREY, BLACK = 0, 1, 2
colour = {n: WHITE for n in ids}
cycle = []
def dfs(u, stack):
colour[u] = GREY
for v in sorted(adj.get(u, ())):
if colour.get(v, WHITE) == GREY:
cycle.append(stack + [v])
elif colour.get(v, WHITE) == WHITE:
dfs(v, stack + [v])
colour[u] = BLACK
for n in ids:
if colour[n] == WHITE:
dfs(n, [n])
check("graph is acyclic", not cycle, f"{len(cycle)} cycles")
# ---- 7. edge endpoints are real nodes ----
known = set(ids)
bad_ep = [e for e in corpus["edges"] if e["from"] not in known or e["to"] not in known]
check("all edge endpoints are corpus nodes", not bad_ep, f"{len(bad_ep)} bad")
# ---- 7b. edges RE-DERIVE from the archived titles (no trust in corpus.json) ----
STOP = {
"a", "an", "the", "of", "for", "and", "or", "to", "in", "on", "with", "via",
"by", "from", "as", "at", "is", "are", "we", "our", "their", "its", "this",
"that", "these", "those", "using", "based", "towards", "through", "into",
"how", "what", "why", "when", "can", "do", "does", "be", "been", "than",
}
title_by_node = {nd["node"]: nd["title"] for nd in nodes}
thr = corpus["counts"]["threshold"]
def tt(s):
out = set()
for w in re.findall(r"[A-Za-z][A-Za-z\-]+", s.lower()):
w = w.strip("-")
if len(w) < 3 or w in STOP:
continue
out.add(w[:4] if len(w) > 4 else w)
return out
bad_jac = 0
rederived = set()
for e in corpus["edges"]:
sa, sb = tt(title_by_node[e["from"]]), tt(title_by_node[e["to"]])
if not sa or not sb:
bad_jac += 1
continue
j = round(len(sa & sb) / len(sa | sb), 4)
if abs(j - e["jaccard"]) > 1e-9:
bad_jac += 1
rederived.add((e["from"], e["to"]))
check("edge jaccard re-derives from archived titles", bad_jac == 0, f"{bad_jac} bad")
# no edge may be missing: recompute the full edge set independently
expected = set()
for i in range(len(ids)):
for j in range(i + 1, len(ids)):
a, b = ids[i], ids[j]
sa, sb = tt(title_by_node[a]), tt(title_by_node[b])
if not sa or not sb:
continue
if len(sa & sb) / len(sa | sb) >= thr:
expected.add((b, a))
missing = expected - rederived
extra = rederived - expected
check("edge set is exactly the recomputed edge set", not missing and not extra,
f"{len(missing)} missing, {len(extra)} extra")
# ---- 8. counts in the manifest are honest ----
c = corpus["counts"]
check("manifest node count honest", c["distinct_nodes"] == len(nodes))
check("manifest edge count honest", c["similarity_edges"] == len(corpus["edges"]))
print()
if FAILS:
print(f"VERDICT: FAIL ({len(FAILS)} checks failed: {', '.join(FAILS)})")
return 1
print(f"VERDICT: PASS -- {len(nodes)} nodes, {len(corpus['edges'])} edges, all checks green")
return 0
if __name__ == "__main__":
sys.exit(main())
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff