#!/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()