sphinx: dual-copy lane gate for the arXiv fulltext archive — subset comparison (commit publishes exactly what its manifest declares, byte-identical in live) plus two sound negative controls: injected code caught by the prose legs alone (manifest digest recomputed, digest legs pass) and an undeclared published file caught
This commit is contained in:
1 parent
b82b023215
commit
04e20bd850
4 files changed
+437
-24
No files matched your search
@@ -1,15 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# sphinx lane gate for the arXiv fulltext archive (2026-10-25).
|
||||
#
|
||||
# 0 every leg held
|
||||
# 1 a graded fault (a leg failed, or the negative control did not hold)
|
||||
# 2 could not grade (not a checkout, python3 missing)
|
||||
# 0 every leg held, and both negative controls failed as they must
|
||||
# 1 a graded fault
|
||||
# 2 could not grade (no python3)
|
||||
#
|
||||
# The gate's own exit code IS the verdict. It resolves its own directory, so a
|
||||
# copy in a clone grades that clone.
|
||||
#
|
||||
# Two copies are graded, not one:
|
||||
# COMMIT this directory (1 run: the published subset)
|
||||
# LIVE /root/.hermes/wizard-outputs/sphinx/arxiv_fulltext (3 runs)
|
||||
# The committed copy is the published subset of the live working copy, so live
|
||||
# may hold runs and source bytes the commit does not -- the two are not equal as
|
||||
# trees, and a whole-tree `diff -r` is the wrong test. compare_copies.py states
|
||||
# the real claim: the commit publishes exactly the files its run manifest
|
||||
# declares, and every one of them is byte-identical in live.
|
||||
set -u
|
||||
|
||||
d=$(dirname "$(readlink -f "$0")")
|
||||
live=/root/.hermes/wizard-outputs/sphinx/arxiv_fulltext
|
||||
repo=/root/troupe-standup
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
@@ -17,36 +27,92 @@ if ! command -v python3 >/dev/null 2>&1; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "== self-tests (prose.py fixtures)"
|
||||
python3 "$d/prose.py" --markdown "$d/runs/1706.03762v7/paper.md" >/dev/null
|
||||
echo " rc=$?"
|
||||
|
||||
echo "== gate: run_checks.py (5 legs)"
|
||||
echo "== gate on the COMMITTED copy: $d"
|
||||
python3 "$d/run_checks.py"
|
||||
gate=$?
|
||||
echo " gate rc=$gate"
|
||||
[ "$gate" -eq 0 ] || { echo " FAULT: committed gate failed" >&2; exit 1; }
|
||||
|
||||
echo "== negative control: the gate must FAIL on a sabotaged archive"
|
||||
echo
|
||||
echo "== gate on the LIVE copy (superset, 3 runs): $live"
|
||||
python3 "$live/run_checks.py"
|
||||
live_gate=$?
|
||||
echo " gate rc=$live_gate"
|
||||
[ "$live_gate" -eq 0 ] || { echo " FAULT: live gate failed" >&2; exit 1; }
|
||||
|
||||
echo
|
||||
echo "== committed copy == live, for everything the commit publishes"
|
||||
python3 "$d/compare_copies.py" "$d" "$live"
|
||||
cmp_rc=$?
|
||||
echo " compare rc=$cmp_rc"
|
||||
[ "$cmp_rc" -eq 0 ] || exit 1
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Negative controls. A control that only ever fails proves nothing, so each is
|
||||
# run twice: on a faithful copy (must pass) and on a sabotaged copy (must fail).
|
||||
# --------------------------------------------------------------------------- #
|
||||
work=$(mktemp -d)
|
||||
mkdir -p "$work/runs/1706.03762v7"
|
||||
cp "$d/prose.py" "$d/run_checks.py" "$d/test_arxiv_fulltext.py" \
|
||||
"$d/test_units.py" "$d/test_prose.py" "$d/arxiv_fulltext.py" "$work/"
|
||||
cp "$d/runs/1706.03762v7/paper.md" "$work/runs/1706.03762v7/paper.md"
|
||||
# Inject a code block into the real archived paper and recompute its manifest,
|
||||
# so every structural check still passes and ONLY the prose leg can catch it.
|
||||
cp "$d"/prose.py "$d"/run_checks.py "$d"/test_arxiv_fulltext.py \
|
||||
"$d"/test_units.py "$d"/test_prose.py "$d"/arxiv_fulltext.py "$work/"
|
||||
cp "$d"/runs/1706.03762v7/* "$work/runs/1706.03762v7/"
|
||||
|
||||
echo
|
||||
echo "== NC1 positive: a faithful copy must grade clean"
|
||||
python3 "$work/run_checks.py" >/dev/null 2>&1
|
||||
pos=$?
|
||||
echo " faithful copy rc=$pos (expected 0)"
|
||||
[ "$pos" -eq 0 ] || { echo " NC1 POSITIVE FAILED: cannot grade a faithful copy" >&2; exit 1; }
|
||||
|
||||
echo
|
||||
echo "== NC1 negative: code injected into the archived paper must be caught"
|
||||
printf '\n```python\nimport os\nos.system("curl http://example.invalid")\n```\n' \
|
||||
>> "$work/runs/1706.03762v7/paper.md"
|
||||
# Recompute the manifest digest the injection changed, so the ONLY legs that can
|
||||
# fail are the prose legs. Without this the control passes for the wrong reason
|
||||
# (a digest mismatch, not a caught injection).
|
||||
python3 - "$work/runs/1706.03762v7/manifest.json" <<'PY'
|
||||
import hashlib, json, sys
|
||||
from pathlib import Path
|
||||
man_path = Path(sys.argv[1])
|
||||
man = json.loads(man_path.read_text(encoding="utf-8"))
|
||||
b = (man_path.parent / "paper.md").read_bytes()
|
||||
man["artifacts"]["paper.md"] = {"path": "paper.md", "bytes": len(b),
|
||||
"sha256": hashlib.sha256(b).hexdigest()}
|
||||
man_path.write_text(json.dumps(man, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
print(" manifest digest recomputed for the injected paper.md")
|
||||
PY
|
||||
python3 "$work/run_checks.py" >/dev/null 2>&1
|
||||
sabotage=$?
|
||||
echo " sabotaged gate rc=$sabotage (expected 1)"
|
||||
[ "$sabotage" -eq 1 ] || { echo " NEGATIVE CONTROL FAILED: sabotage not detected" >&2; exit 1; }
|
||||
neg=$?
|
||||
echo " sabotaged copy rc=$neg (expected 1)"
|
||||
[ "$neg" -eq 1 ] || { echo " NC1 NEGATIVE FAILED: injection not detected" >&2; exit 1; }
|
||||
|
||||
echo
|
||||
echo "== NC2: a published file the manifest does not declare must be caught"
|
||||
work2=$(mktemp -d)
|
||||
mkdir -p "$work2/runs/1706.03762v7"
|
||||
cp "$d"/prose.py "$d"/run_checks.py "$d"/test_arxiv_fulltext.py \
|
||||
"$d"/test_units.py "$d"/test_prose.py "$d"/arxiv_fulltext.py \
|
||||
"$d"/CHECK.sh "$d"/README-prose.md "$d"/compare_copies.py "$work2/"
|
||||
cp "$d"/runs/1706.03762v7/* "$work2/runs/1706.03762v7/"
|
||||
cp "$work2/runs/1706.03762v7/paper.md" "$work2/runs/1706.03762v7/notes.txt"
|
||||
python3 "$d/compare_copies.py" "$work2" "$live" >/dev/null 2>&1
|
||||
nc2=$?
|
||||
echo " undeclared-file copy rc=$nc2 (expected 1)"
|
||||
[ "$nc2" -eq 1 ] || { echo " NC2 FAILED: undeclared published file not detected" >&2; exit 1; }
|
||||
rm -f "$work2/runs/1706.03762v7/notes.txt"
|
||||
python3 "$d/compare_copies.py" "$work2" "$live" >/dev/null 2>&1
|
||||
nc2b=$?
|
||||
echo " restored copy rc=$nc2b (expected 0)"
|
||||
[ "$nc2b" -eq 0 ] || { echo " NC2 POSITIVE FAILED: faithful copy not graded clean" >&2; exit 1; }
|
||||
|
||||
echo
|
||||
echo "== git leg: artifact committed"
|
||||
git -C "$repo" log --oneline -1 -- wizard-work/sphinx/arxiv-fulltext-2026-10-25/ >/dev/null 2>&1
|
||||
gitrc=$?
|
||||
echo " git rc=$gitrc"
|
||||
[ "$gitrc" -eq 0 ] || { echo " artifact not committed" >&2; exit 1; }
|
||||
[ "$gitrc" -eq 0 ] || { echo " FAULT: artifact not committed" >&2; exit 1; }
|
||||
|
||||
echo
|
||||
[ "$gate" -eq 0 ] && echo "VERIFIED" || { echo "FAULT" >&2; exit 1; }
|
||||
echo "VERIFIED"
|
||||
exit 0
|
||||
@@ -0,0 +1,211 @@
|
||||
EVIDENCE — arXiv fulltext archive dual-copy gate (sphinx lane)
|
||||
captured: 2026-10-26T09:08:49Z
|
||||
host: WorkBench python: Python 3.14.7
|
||||
|
||||
COMMIT = /root/troupe-standup/wizard-work/sphinx/arxiv-fulltext-2026-10-25 (1 run)
|
||||
LIVE = /root/.hermes/wizard-outputs/sphinx/arxiv_fulltext (3 runs)
|
||||
|
||||
The committed copy is the published SUBSET of the live working copy, so live
|
||||
carries runs and source bytes the commit does not. A whole-tree `diff -r` is
|
||||
therefore the wrong test (it fails on bytes the commit never claimed to
|
||||
publish). compare_copies.py states the real claim: the commit publishes exactly
|
||||
the files its run manifest declares, and every one of them is byte-identical in
|
||||
live.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
1. THE GATE: bash CHECK.sh (its exit code IS the verdict: 0)
|
||||
--------------------------------------------------------------------
|
||||
== gate on the COMMITTED copy: /root/troupe-standup/wizard-work/sphinx/arxiv-fulltext-2026-10-25
|
||||
== L1 real-archive regression test
|
||||
|
||||
== L1 real-archive regression: python3 test_arxiv_fulltext.py
|
||||
|
||||
25 passed, 0 failed
|
||||
ALL CHECKS PASSED
|
||||
PASS L1 real-archive regression exits 0
|
||||
|
||||
== L2 fixture unit test
|
||||
|
||||
== L2 fixture units: python3 test_units.py
|
||||
|
||||
32 passed, 0 failed
|
||||
ALL UNIT CHECKS PASSED
|
||||
PASS L2 fixture units exits 0
|
||||
|
||||
== L3 prose partition + mutation negative control
|
||||
|
||||
== L3 prose partition: python3 test_prose.py
|
||||
|
||||
26 passed, 0 failed
|
||||
ALL PROSE CHECKS PASSED
|
||||
PASS L3 prose partition exits 0
|
||||
|
||||
== L4 published manifest digests match the bytes on disk
|
||||
PASS at least one manifest found
|
||||
PASS 1706.03762v7/raw.html digest+size match
|
||||
PASS 1706.03762v7/paper.md digest+size match
|
||||
|
||||
== L5 prose verdict on every real run
|
||||
PASS 1706.03762v7/paper.md grades prose
|
||||
|
||||
7 passed, 0 failed
|
||||
ALL CHECKS PASSED
|
||||
gate rc=0
|
||||
|
||||
== gate on the LIVE copy (superset, 3 runs): /root/.hermes/wizard-outputs/sphinx/arxiv_fulltext
|
||||
== L1 real-archive regression test
|
||||
|
||||
== L1 real-archive regression: python3 test_arxiv_fulltext.py
|
||||
|
||||
29 passed, 0 failed
|
||||
ALL CHECKS PASSED
|
||||
PASS L1 real-archive regression exits 0
|
||||
|
||||
== L2 fixture unit test
|
||||
|
||||
== L2 fixture units: python3 test_units.py
|
||||
|
||||
32 passed, 0 failed
|
||||
ALL UNIT CHECKS PASSED
|
||||
PASS L2 fixture units exits 0
|
||||
|
||||
== L3 prose partition + mutation negative control
|
||||
|
||||
== L3 prose partition: python3 test_prose.py
|
||||
|
||||
28 passed, 0 failed
|
||||
ALL PROSE CHECKS PASSED
|
||||
PASS L3 prose partition exits 0
|
||||
|
||||
== L4 published manifest digests match the bytes on disk
|
||||
PASS at least one manifest found
|
||||
PASS 1706.03762v7/raw.html digest+size match
|
||||
PASS 1706.03762v7/paper.md digest+size match
|
||||
PASS pdf_fallback_1706.03762v7/raw.pdf digest+size match
|
||||
PASS pdf_fallback_1706.03762v7/paper.md digest+size match
|
||||
|
||||
== L5 prose verdict on every real run
|
||||
PASS 1706.03762v7/paper.md grades prose
|
||||
PASS pdf_fallback_1706.03762v7/paper.md grades prose
|
||||
PASS pdf_ocr_1706.03762v7/paper.md grades prose
|
||||
|
||||
11 passed, 0 failed
|
||||
ALL CHECKS PASSED
|
||||
gate rc=0
|
||||
|
||||
== committed copy == live, for everything the commit publishes
|
||||
9 shared tool/test/gate files byte-identical
|
||||
runs/1706.03762v7: 4 files, all declared, byte-identical to live
|
||||
compare rc=0
|
||||
|
||||
== NC1 positive: a faithful copy must grade clean
|
||||
faithful copy rc=0 (expected 0)
|
||||
|
||||
== NC1 negative: code injected into the archived paper must be caught
|
||||
manifest digest recomputed for the injected paper.md
|
||||
sabotaged copy rc=1 (expected 1)
|
||||
|
||||
== NC2: a published file the manifest does not declare must be caught
|
||||
undeclared-file copy rc=1 (expected 1)
|
||||
restored copy rc=0 (expected 0)
|
||||
|
||||
== git leg: artifact committed
|
||||
git rc=0
|
||||
|
||||
VERIFIED
|
||||
|
||||
--------------------------------------------------------------------
|
||||
2. NC1 POSITIVE — a faithful copy must grade clean (expected rc=0)
|
||||
--------------------------------------------------------------------
|
||||
== L1 real-archive regression test
|
||||
|
||||
== L1 real-archive regression: python3 test_arxiv_fulltext.py
|
||||
|
||||
25 passed, 0 failed
|
||||
ALL CHECKS PASSED
|
||||
PASS L1 real-archive regression exits 0
|
||||
|
||||
== L2 fixture unit test
|
||||
|
||||
== L2 fixture units: python3 test_units.py
|
||||
|
||||
32 passed, 0 failed
|
||||
ALL UNIT CHECKS PASSED
|
||||
PASS L2 fixture units exits 0
|
||||
|
||||
== L3 prose partition + mutation negative control
|
||||
|
||||
== L3 prose partition: python3 test_prose.py
|
||||
|
||||
26 passed, 0 failed
|
||||
ALL PROSE CHECKS PASSED
|
||||
PASS L3 prose partition exits 0
|
||||
|
||||
== L4 published manifest digests match the bytes on disk
|
||||
PASS at least one manifest found
|
||||
PASS 1706.03762v7/raw.html digest+size match
|
||||
PASS 1706.03762v7/paper.md digest+size match
|
||||
|
||||
== L5 prose verdict on every real run
|
||||
PASS 1706.03762v7/paper.md grades prose
|
||||
|
||||
7 passed, 0 failed
|
||||
ALL CHECKS PASSED
|
||||
|
||||
--------------------------------------------------------------------
|
||||
3. NC1 NEGATIVE — a fenced Python block injected into the real archived
|
||||
paper.md, with the manifest digest RECOMPUTED so that only the prose legs
|
||||
can fail. Expected rc=1. MEASURED rc=1, and the digest legs PASS.
|
||||
--------------------------------------------------------------------
|
||||
recomputed digest for 1706.03762v7/paper.md (42536 bytes, sha256 3cdb5d3407a935af...)
|
||||
|
||||
== L1 real-archive regression test
|
||||
|
||||
== L1 real-archive regression: python3 test_arxiv_fulltext.py
|
||||
|
||||
25 passed, 0 failed
|
||||
ALL CHECKS PASSED
|
||||
PASS L1 real-archive regression exits 0
|
||||
|
||||
== L2 fixture unit test
|
||||
|
||||
== L2 fixture units: python3 test_units.py
|
||||
|
||||
32 passed, 0 failed
|
||||
ALL UNIT CHECKS PASSED
|
||||
PASS L2 fixture units exits 0
|
||||
|
||||
== L3 prose partition + mutation negative control
|
||||
|
||||
== L3 prose partition: python3 test_prose.py
|
||||
- bare maths assignment does not flip verdict ['code_fence_present=1']
|
||||
- short untagged fence does not flip verdict ['code_fence_present=1']
|
||||
- 1706.03762v7: graded prose ['code_fence_present=1']
|
||||
FAIL L3 prose partition exits 0 rc=1 err=
|
||||
|
||||
== L4 published manifest digests match the bytes on disk
|
||||
PASS at least one manifest found
|
||||
PASS 1706.03762v7/raw.html digest+size match
|
||||
PASS 1706.03762v7/paper.md digest+size match
|
||||
|
||||
== L5 prose verdict on every real run
|
||||
FAIL 1706.03762v7/paper.md grades prose [' PROSE: no (code_fence_present=1)']
|
||||
|
||||
5 passed, 2 failed
|
||||
|
||||
FAILURES:
|
||||
- L3 prose partition exits 0 rc=1 err=
|
||||
- 1706.03762v7/paper.md grades prose [' PROSE: no (code_fence_present=1)']
|
||||
|
||||
READ THIS AS: the injected code is caught by the PROSE legs alone. L4 (the digest
|
||||
legs) passes on the sabotaged copy because the manifest digest was recomputed,
|
||||
so the failure cannot be attributed to a digest mismatch.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
4. ARTIFACT DIGESTS (sha256, bytes on disk)
|
||||
--------------------------------------------------------------------
|
||||
b2adf45ac52f6af62c3f93316059550a0201da8768da4107196a7af5111d962f CHECK.sh
|
||||
ae74e8f530be4f7609aa56680041d888a7ea96da03a239950513267f429b21af compare_copies.py
|
||||
086299ddce23716b03bf0a3c0e67a67bb2e2d92abfccec2bc08c5194f5926878 README-prose.md
|
||||
e815d4934dc15ef483437ba860b260c20a08d966affc30b1f84022fcf851c89f run_checks.py
|
||||
e2fd595302fab61cb6458d74e8f2d0ef2a11d1ba5977fb5019a472bdbb35908e prose.py
|
||||
@@ -3,7 +3,8 @@
|
||||
## What this is
|
||||
|
||||
A deterministic prose/code partition for the archive lane's arXiv fulltext
|
||||
pipeline, plus the lane gate that runs it.
|
||||
pipeline, plus the lane gate that runs it — graded against **two** copies: the
|
||||
committed subset and the live working copy.
|
||||
|
||||
## Why it exists
|
||||
|
||||
@@ -23,7 +24,9 @@ were recomputed would pass every pre-existing check.
|
||||
| `prose.py` | the partition: `fence_blocks`, `code_blocks`, `prose_stats`, `is_prose_document`, `find_injected_code` |
|
||||
| `test_prose.py` | 28 checks: fixtures + a negative control on a real archived paper |
|
||||
| `run_checks.py` | the gate: 5 legs, 11 assertions, exit 0 only if all hold |
|
||||
| `compare_copies.py` | grades the committed copy against the live copy |
|
||||
| `CHECK.sh` | the one-command lane gate, exit code = verdict |
|
||||
| `EVIDENCE-dualcopy.txt` | captured run output of the gate below |
|
||||
|
||||
## The rules (all deterministic, no model)
|
||||
|
||||
@@ -38,17 +41,47 @@ were recomputed would pass every pre-existing check.
|
||||
`find_injected_code` reports every code fence with its line range, so a block is
|
||||
visible even when the document-level verdict is unaffected.
|
||||
|
||||
## Two copies, one claim
|
||||
|
||||
The commit publishes **one** run (`runs/1706.03762v7`); the live working copy
|
||||
holds **three** (`1706.03762v7`, `pdf_fallback_1706.03762v7`,
|
||||
`pdf_ocr_1706.03762v7`) and carries source bytes the commit does not. So the two
|
||||
are *not* equal as trees, and a whole-tree `diff -r` is the wrong test — it
|
||||
fails on bytes the commit never claimed to publish.
|
||||
|
||||
`compare_copies.py` states the real claim instead:
|
||||
|
||||
- the shared tool/test/gate files are byte-identical between the copies;
|
||||
- every run the commit publishes is present in live;
|
||||
- the commit publishes **exactly** the files its run manifest declares
|
||||
(`index.md`, `manifest.json`, and every manifest artifact) and nothing else —
|
||||
so the commit carries no artifact its own manifest does not vouch for;
|
||||
- every one of those published files is byte-identical in live.
|
||||
|
||||
## Negative controls (both run twice)
|
||||
|
||||
A control that only ever fails proves nothing, so each is graded on a faithful
|
||||
copy first and a sabotaged copy second:
|
||||
|
||||
- **NC1** copies the real run, injects a fenced Python block into `paper.md`,
|
||||
**recomputes the manifest digest the injection changed**, and requires the
|
||||
gate to exit 1. The digest recompute is what makes the control sound: without
|
||||
it the gate fails on a digest mismatch rather than on the caught injection.
|
||||
Measured: faithful copy `rc=0`, sabotaged copy `rc=1`, and the digest legs
|
||||
pass on the sabotaged copy — the failures are the prose legs
|
||||
(`code_fence_present=1`).
|
||||
- **NC2** copies the real run, drops an undeclared `notes.txt` into it, and
|
||||
requires `compare_copies.py` to exit 1; removing it must restore `rc=0`.
|
||||
|
||||
## Evidence
|
||||
|
||||
python3 run_checks.py # 11 passed, 0 failed, exit 0
|
||||
bash CHECK.sh # VERIFIED, exit 0
|
||||
|
||||
The negative control in `CHECK.sh` copies the real run, injects a fenced Python
|
||||
block into `paper.md`, recomputes the manifest digest, and requires the gate to
|
||||
exit 1. It does.
|
||||
Captured run output is in `EVIDENCE-dualcopy.txt`.
|
||||
|
||||
## Verdict
|
||||
|
||||
`VERDICT: the archive's published markdown is graded as prose by a deterministic partition, and code injection into it is caught; 11/11 gate assertions hold.`
|
||||
`VERDICT: the archive's published markdown is graded as prose by a deterministic partition; code injection is caught by the prose legs alone (digest recomputed), an undeclared published file is caught by the copy comparison, and both the committed and live copies grade green and are byte-identical on everything the commit publishes.`
|
||||
|
||||
`CHECK: bash /root/troupe-standup/wizard-work/sphinx/arxiv-fulltext-2026-10-25/CHECK.sh`
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""compare_copies.py -- grade the committed copy of the arXiv fulltext archive
|
||||
against the live working copy (sphinx lane, 2026-10-26).
|
||||
|
||||
COMMIT is the directory the gate runs from; LIVE is the published working copy.
|
||||
COMMIT is a SUBSET of LIVE -- live may hold runs and source bytes the commit
|
||||
does not. So the two are not equal as trees, and a whole-tree `diff -r` is the
|
||||
wrong test. What must hold is:
|
||||
|
||||
* the shared tool/tests/gate files are byte-identical between the copies, and
|
||||
* every run the commit publishes is present in live, and
|
||||
* the commit publishes exactly the files its run manifest declares
|
||||
(`index.md` + `manifest.json` + every manifest artifact) and nothing else,
|
||||
so the commit carries no artifact its own manifest does not vouch for, and
|
||||
* every one of those published files is byte-identical in live.
|
||||
|
||||
Exit 0 = held. Exit 1 = a named fault. Exit 2 = could not grade.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SHARED = [
|
||||
"prose.py",
|
||||
"test_prose.py",
|
||||
"run_checks.py",
|
||||
"test_arxiv_fulltext.py",
|
||||
"test_units.py",
|
||||
"arxiv_fulltext.py",
|
||||
"CHECK.sh",
|
||||
"README-prose.md",
|
||||
"compare_copies.py",
|
||||
]
|
||||
|
||||
|
||||
def fault(msg: str) -> int:
|
||||
print(f" FAULT: {msg}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def grade(commit: Path, live: Path) -> int:
|
||||
if not commit.is_dir() or not live.is_dir():
|
||||
print(" could not grade: a directory does not exist", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
for f in SHARED:
|
||||
a, b = commit / f, live / f
|
||||
if not a.is_file():
|
||||
return fault(f"{f} missing from the committed copy")
|
||||
if not b.is_file():
|
||||
return fault(f"live has no {f}")
|
||||
if a.read_bytes() != b.read_bytes():
|
||||
return fault(f"{f} differs between commit and live")
|
||||
print(f" {len(SHARED)} shared tool/test/gate files byte-identical")
|
||||
|
||||
for r in sorted((commit / "runs").iterdir()):
|
||||
if not r.is_dir():
|
||||
continue
|
||||
name = r.name
|
||||
lr = live / "runs" / name
|
||||
if not lr.is_dir():
|
||||
return fault(f"live has no runs/{name}")
|
||||
|
||||
man = json.loads((r / "manifest.json").read_text(encoding="utf-8"))
|
||||
declared = {"index.md", "manifest.json"} | {
|
||||
a["path"] for a in man.get("artifacts", {}).values()
|
||||
}
|
||||
published = {p.name for p in r.iterdir() if p.is_file()}
|
||||
|
||||
extra = published - declared
|
||||
if extra:
|
||||
return fault(
|
||||
f"runs/{name} publishes files its manifest does not declare: "
|
||||
f"{sorted(extra)}"
|
||||
)
|
||||
missing = declared - published
|
||||
if missing:
|
||||
return fault(
|
||||
f"runs/{name} is missing files its manifest declares: {sorted(missing)}"
|
||||
)
|
||||
|
||||
for n in sorted(published):
|
||||
b = lr / n
|
||||
if not b.is_file():
|
||||
return fault(f"runs/{name}/{n} absent from live")
|
||||
if (r / n).read_bytes() != b.read_bytes():
|
||||
return fault(f"runs/{name}/{n} differs between commit and live")
|
||||
print(f" runs/{name}: {len(published)} files, all declared, byte-identical to live")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
if len(argv) != 3:
|
||||
print("usage: compare_copies.py <commit_dir> <live_dir>", file=sys.stderr)
|
||||
return 2
|
||||
return grade(Path(argv[1]), Path(argv[2]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Reference in new issue
Block a user