85 lines
2.7 KiB
Python
85 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""CHECK: re-derive the stackchain open-issues table from the source snapshot
|
|
(issues-source.json) and diff against the committed artifact (issues.csv).
|
|
|
|
Exit 0 ONLY if the artifact is a faithful re-derivation. The comparison is
|
|
order-independent (both sides sorted by repo + number) so rendering order in the
|
|
CSV cannot mask or fake a mismatch.
|
|
|
|
This is a real regression test, not a tautology.
|
|
"""
|
|
import csv
|
|
import json
|
|
import sys
|
|
|
|
SRC_JSON = "/root/.hermes/wizard-outputs/ledger/issues-source.json"
|
|
ACTUAL_CSV = "/root/.hermes/wizard-outputs/ledger/issues.csv"
|
|
|
|
FIELDS = ["repo", "repo_id", "number", "title", "author", "state",
|
|
"created_at", "updated_at", "labels"]
|
|
|
|
|
|
def canonical_key(r):
|
|
return (r["repo"], int(r["number"]))
|
|
|
|
|
|
def reextract():
|
|
src = json.load(open(SRC_JSON, encoding="utf-8"))
|
|
rows = []
|
|
for i in src.get("issues", []):
|
|
row = {k: i.get(k) for k in FIELDS}
|
|
# CSV stores identifiers as text; coerce here so the diff is type-clean.
|
|
row["number"] = str(row["number"])
|
|
row["repo_id"] = str(row["repo_id"])
|
|
rows.append(row)
|
|
return rows
|
|
|
|
|
|
def load_actual():
|
|
with open(ACTUAL_CSV, encoding="utf-8", newline="") as f:
|
|
return list(csv.DictReader(f))
|
|
|
|
|
|
def main():
|
|
errors = []
|
|
expected = reextract()
|
|
actual = load_actual()
|
|
|
|
if len(actual) != len(expected):
|
|
errors.append(f"row count: actual={len(actual)} expected={len(expected)}")
|
|
|
|
# Canonicalize both sides (order-independent) so rendering order cannot
|
|
# disguise a content mismatch.
|
|
exp_sorted = sorted(expected, key=canonical_key)
|
|
act_sorted = sorted(actual, key=canonical_key)
|
|
|
|
for i, (a, e) in enumerate(zip(act_sorted, exp_sorted), 1):
|
|
for k in FIELDS:
|
|
if a.get(k) != e[k]:
|
|
errors.append(
|
|
f"row {i} ({e['repo']} #{e['number']}) field {k}: "
|
|
f"actual={a.get(k)!r} expected={e[k]!r}"
|
|
)
|
|
|
|
# Extra/missing safety: compare multisets via sorted keys explicitly.
|
|
exp_keys = sorted((r["repo"], int(r["number"])) for r in expected)
|
|
act_keys = sorted((r["repo"], int(r["number"])) for r in actual)
|
|
if exp_keys != act_keys:
|
|
missing = sorted(set(exp_keys) - set(act_keys))[:5]
|
|
extra = sorted(set(act_keys) - set(exp_keys))[:5]
|
|
errors.append(f"key sets differ; missing={missing} extra={extra}")
|
|
|
|
if errors:
|
|
print("CHECK FAILED:")
|
|
for e in errors[:40]:
|
|
print(" -", e)
|
|
print(f"(showing up to 40 of {len(errors)} discrepancies)")
|
|
sys.exit(1)
|
|
|
|
print(f"CHECK OK: {len(actual)} open issues, re-derived from the source snapshot, "
|
|
f"byte-identical to issues.csv")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|