#!/usr/bin/env python3 """Deterministic reproduction for OpenReview hgMZraPlSv. This audit combines exhaustive finite semantics with exact execution of the authors' benchmark exporter and a byte-level audit of the initial-release baseline records. Lean itself is intentionally not claimed as executed: the Lean/Pantograph implementation and paper proofs are pinned and structurally audited, while every numerical or finite claim below is recomputed here. """ from __future__ import annotations import argparse import hashlib import importlib.util import json import math import tarfile import tempfile from collections import defaultdict from pathlib import Path PAPER_ID = "hgMZraPlSv" CURRENT_COMMIT = "3e8bafd18a080fe01263ff93b9c3db78d993e1a1" INITIAL_COMMIT = "39489d1f0c32b521845429e1cb26c48639d8f823" EXPECTED = { "paper_pdf": "bba3d0293dda50476bd07d7dab987d4ed5ded4fa21132837f35c4feff72403e3", "paper_source": "b08c84c35b90d0128fc6ff512b60b07ead29ef6de847c8834948df660b00034a", "current_archive": "b144a1051b840f4b7f59428151ba8bf6f27e7665a3b9f950d165c39055ef2315", "initial_archive": "cef6b9340b797355ab6e3fb425ee02a1e6c2cc7b76eff6fc77d79bf6e99753fe", } BENCHMARKS = { "formal_math500": ("FormalMath500", 387), "minif2f_solving": ("MiniF2FSolving", 375), "putnam_solving": ("PutnamBenchSolving", 324), } REQUIRED_SCHEMA = { "conclusions", "formal_answer", "formal_answer_type", "header", "hypotheses", "independent_variables", "informal_answer", "informal_problem", "metainfo", } def sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for block in iter(lambda: handle.read(1 << 20), b""): digest.update(block) return digest.hexdigest() def dump(path: Path, value: object) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8") def extract(archive: Path, destination: Path) -> Path: with tarfile.open(archive, "r:gz") as handle: # GitHub's snapshot contains one developer-machine absolute symlink at # ``.lake/packages``. It is neither evidence nor portable. Extract # regular files/directories only and retain the stdlib traversal guard. members = [member for member in handle.getmembers() if member.isfile() or member.isdir()] handle.extractall(destination, members=members, filter="data") roots = [p for p in destination.iterdir() if p.is_dir()] if len(roots) != 1: raise RuntimeError(f"expected one archive root in {archive}, got {roots}") return roots[0] def assert_tokens(text: str, tokens: list[str], label: str) -> dict: missing = [token for token in tokens if token not in text] if missing: raise RuntimeError(f"{label} lacks pinned tokens: {missing}") return {"artifact": label, "required_tokens": len(tokens), "missing": missing} def deterministic_mdp() -> dict: """Exhaust all 256 hole/goal states under eight deterministic actions.""" def transition(state: tuple[int, int], action: int) -> tuple[int, int]: holes, goals = state if action < 4: return holes & ~(1 << action), goals return holes, goals & ~(1 << (action - 4)) states = [(holes, goals) for holes in range(16) for goals in range(16)] actions = list(range(8)) comparisons = 0 terminal_rewards = 0 for state in states: for action in actions: expected = transition(state, action) for _ in range(4): comparisons += 1 if transition(state, action) != expected: raise RuntimeError("nominal transition is not deterministic") terminal_rewards += int(expected == (0, 0)) calls = defaultdict(int) def hidden_history_mutant(state: tuple[int, int], action: int) -> tuple[int, int]: calls[(state, action)] += 1 nominal = transition(state, action) if calls[(state, action)] % 2: return nominal return nominal[0] ^ 1, nominal[1] mutant_detected = hidden_history_mutant((7, 11), 2) != hidden_history_mutant((7, 11), 2) return { "states": len(states), "actions": len(actions), "repeated_transition_comparisons": comparisons, "deterministic_violations": 0, "terminal_reward_hits": terminal_rewards, "hidden_history_mutant_detected": mutant_detected, } def predicate_semantics() -> dict: """Exhaust D-FPS forward/backward implications over domains 0..7.""" total = forward = backward = iff = forward_only = 0 by_n = [] for n in range(8): limit = 1 << n row = {"n": n, "pairs": 0, "forward_complete": 0, "backward_sound": 0, "iff": 0} for truth in range(limit): for answer in range(limit): row["pairs"] += 1 total += 1 complete = truth & ~answer == 0 sound = answer & ~truth == 0 forward += int(complete) backward += int(sound) iff += int(complete and sound) forward_only += int(complete and not sound) row["forward_complete"] += int(complete) row["backward_sound"] += int(sound) row["iff"] += int(complete and sound) by_n.append(row) return { "domains": list(range(8)), "predicate_pairs": total, "forward_complete_pairs": forward, "backward_sound_pairs": backward, "complete_and_sound_pairs": iff, "forward_only_complete_but_unsound": forward_only, "expected_closed_forms": { "pairs": sum(4**n for n in range(8)), "one_direction": sum(3**n for n in range(8)), "iff": sum(2**n for n in range(8)), }, "by_domain": by_n, } def fps_soundness() -> dict: total = accepted = rejected = violations = 0 mutant_false_accepts = 0 by_n = [] for n in range(1, 10): row = {"n": n, "answer_predicate_cases": 0, "accepted": 0, "rejected": 0} for predicate in range(1 << n): for answer in range(n): truth = bool(predicate & (1 << answer)) proof_exists = truth total += 1 accepted += int(proof_exists) rejected += int(not proof_exists) violations += int(proof_exists and not truth) mutant_false_accepts += int(not truth) # mutant omits Proof : P Answer row["answer_predicate_cases"] += 1 row["accepted"] += int(proof_exists) row["rejected"] += int(not proof_exists) by_n.append(row) return { "domains": list(range(1, 10)), "answer_predicate_cases": total, "accepted_true_memberships": accepted, "rejected_false_memberships": rejected, "soundness_violations": violations, "proof_field_omission_mutant_false_accepts": mutant_false_accepts, "by_domain": by_n, } def load_jsonl(path: Path) -> list[dict]: return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] def benchmark_export_audit(current: Path, scratch: Path) -> dict: data_root = current / "data" project = data_root / "formal_problem_solving" exporter_path = current / "common/export_fps_benchmark.py" spec = importlib.util.spec_from_file_location("official_export_fps_benchmark", exporter_path) if spec is None or spec.loader is None: raise RuntimeError("cannot import official exporter") exporter = importlib.util.module_from_spec(spec) spec.loader.exec_module(exporter) records_total = 0 exact_exports = 0 schemas_ok = 0 unique_total = 0 details = [] all_fingerprints: set[str] = set() for key, (lean_dir, expected_count) in BENCHMARKS.items(): records = load_jsonl(data_root / f"{key}.jsonl") if len(records) != expected_count: raise RuntimeError(f"{key} count drift: {len(records)}") fingerprints = [] for record in records: schemas_ok += int(REQUIRED_SCHEMA.issubset(record)) fingerprint = hashlib.sha256( json.dumps( {name: record[name] for name in sorted(REQUIRED_SCHEMA)}, sort_keys=True, ensure_ascii=False, ).encode() ).hexdigest() fingerprints.append(fingerprint) all_fingerprints.add(fingerprint) if len(set(fingerprints)) != expected_count: raise RuntimeError(f"duplicate benchmark record in {key}") unique_total += len(set(fingerprints)) exporter.export_benchmark(data_root, scratch, key) mismatches = [] for index in range(1, expected_count + 1): produced = scratch / "FormalProblemSolving" / lean_dir / f"{index}.lean" official = project / "FormalProblemSolving" / lean_dir / f"{index}.lean" if produced.read_bytes() == official.read_bytes(): exact_exports += 1 else: mismatches.append(index) records_total += expected_count details.append({"benchmark": key, "records": expected_count, "byte_mismatches": mismatches}) mutation_target = scratch / "FormalProblemSolving" / "FormalMath500" / "1.lean" official_target = project / "FormalProblemSolving" / "FormalMath500" / "1.lean" mutated = mutation_target.read_bytes() + b"-- destructive mutation\n" export_mutant_detected = hashlib.sha256(mutated).digest() != hashlib.sha256(official_target.read_bytes()).digest() return { "benchmark_counts": {key: count for key, (_, count) in BENCHMARKS.items()}, "records_total": records_total, "records_with_required_schema": schemas_ok, "unique_within_benchmarks": unique_total, "unique_global_fingerprints": len(all_fingerprints), "lean_files_regenerated_byte_exact": exact_exports, "lean_files_expected": records_total, "export_mutant_detected": export_mutant_detected, "details": details, } def baseline_table_audit(initial: Path) -> dict: files = sorted((initial / "baseline_results").glob("**/solving.*.jsonl")) if len(files) != 18: raise RuntimeError(f"expected 18 initial-release baseline files, got {len(files)}") rows_total = 0 methods = [] maxima: dict[str, dict] = {} submission_maxima: dict[str, int] = defaultdict(int) for path in files: benchmark = path.name.removeprefix("solving.").removesuffix(".jsonl") denominator = BENCHMARKS[benchmark][1] rows = load_jsonl(path) solved = sum(bool(row.get("submission")) and row.get("eq_proof") is not None for row in rows) submitted = sum(bool(row.get("submission")) for row in rows) rows_total += len(rows) method = str(path.parent.relative_to(initial / "baseline_results")) rate = 100.0 * solved / denominator methods.append( { "method": method, "benchmark": benchmark, "records_present": len(rows), "denominator": denominator, "solved": solved, "solved_percent": round(rate, 12), "submitted_without_equivalence": submitted - solved, } ) if benchmark not in maxima or solved > maxima[benchmark]["solved"]: maxima[benchmark] = {"method": method, "solved": solved, "denominator": denominator} submission_maxima[benchmark] = max(submission_maxima[benchmark], submitted) for benchmark, row in maxima.items(): row["percent"] = round(100.0 * row["solved"] / row["denominator"], 2) return { "files": len(files), "method_problem_records_present": rows_total, "method_problem_slots": 6 * sum(count for _, count in BENCHMARKS.values()), "missing_records_counted_unsolved": 6 * sum(count for _, count in BENCHMARKS.values()) - rows_total, "methods": methods, "maxima": maxima, "expected_maxima": { "formal_math500": {"solved": 92, "denominator": 387, "percent": 23.77}, "minif2f_solving": {"solved": 103, "denominator": 375, "percent": 27.47}, "putnam_solving": {"solved": 1, "denominator": 324, "percent": 0.31}, }, "submission_only_mutant_maxima": dict(submission_maxima), "solving_rule_mutant_detected": any( submission_maxima[key] != maxima[key]["solved"] for key in BENCHMARKS ), } def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--paper-pdf", type=Path, required=True) parser.add_argument("--paper-source", type=Path, required=True) parser.add_argument("--current-archive", type=Path, required=True) parser.add_argument("--initial-archive", type=Path, required=True) parser.add_argument("--out", type=Path, required=True) args = parser.parse_args() inputs = { "paper_pdf": args.paper_pdf, "paper_source": args.paper_source, "current_archive": args.current_archive, "initial_archive": args.initial_archive, } observed = {name: sha256(path) for name, path in inputs.items()} if observed != EXPECTED: raise RuntimeError(f"pinned input drift: {observed}") with tempfile.TemporaryDirectory() as temporary: temp = Path(temporary) paper = extract(args.paper_source, temp / "paper") if False else None # The arXiv e-print has files at archive root rather than a single # directory, so extract it separately and validate named source files. paper_root = temp / "paper_source" paper_root.mkdir() with tarfile.open(args.paper_source, "r:gz") as handle: handle.extractall(paper_root, filter="data") current = extract(args.current_archive, temp / "current") initial = extract(args.initial_archive, temp / "initial") main_tex = (paper_root / "main.tex").read_text(encoding="utf-8") method_tex = (paper_root / "method.tex").read_text(encoding="utf-8") evaluation_tex = (paper_root / "evaluation.tex").read_text(encoding="utf-8") experiment_tex = (paper_root / "experiment.tex").read_text(encoding="utf-8") proofs_tex = (paper_root / "appendix.proofs.tex").read_text(encoding="utf-8") basic = (current / "data/formal_problem_solving/FormalProblemSolving/Basic.lean").read_text(encoding="utf-8") solving = (current / "common/pantograph/solving_server.py").read_text(encoding="utf-8") source_audits = [ assert_tokens(method_tex, ["deterministic MDP", "solution state", "solution step", "FPS is \\textbf{sound}", "D-FPS is \\textbf{complete}", "D-FPS is \\textbf{sound}"], "paper/method.tex"), assert_tokens(proofs_tex, ["Soundness of FPS", "Completeness of D-FPS", "Soundness of D-FPS", "P(\\hat a)"], "paper/appendix.proofs.tex"), assert_tokens(basic, ["structure ProblemSol", "Answer : α", "Proof : P Answer", "namespace FPS", "apply ProblemSol.mk", "namespace DFPS", "refine @Iff.intro"], "official/Basic.lean"), assert_tokens(solving, ["init_forward_solving_state_async", "Forward", "Backward", "apply Exists.intro ?Answer ?Proof"], "official/solving_server.py"), assert_tokens(evaluation_tex, ["FormalMath500", "387 data points", "375 data points", "324 data points"], "paper/evaluation.tex"), assert_tokens(experiment_tex, ["23.77\\%", "27.47\\%", "0.31\\%"], "paper/experiment.tex"), assert_tokens(main_tex, ["deterministic Markov decision process", "at most $23.77\\%$", "$27.47\\%$", "$0.31\\%$"], "paper/main.tex"), ] mdp = deterministic_mdp() predicates = predicate_semantics() soundness = fps_soundness() benchmark = benchmark_export_audit(current, temp / "regenerated") baseline = baseline_table_audit(initial) expected_maxima = baseline["expected_maxima"] maxima_ok = all( baseline["maxima"][key][field] == expected_maxima[key][field] for key in expected_maxima for field in ("solved", "denominator", "percent") ) gates = [ ("pinned_input_hashes", observed == EXPECTED), ("paper_and_code_tokens", all(not row["missing"] for row in source_audits)), ("mdp_exhaustive_determinism", mdp["deterministic_violations"] == 0 and mdp["repeated_transition_comparisons"] == 8192), ("mdp_hidden_history_control", mdp["hidden_history_mutant_detected"]), ("dfps_all_predicate_pairs", predicates["predicate_pairs"] == 21845), ("dfps_forward_completeness", predicates["forward_complete_pairs"] == 3280), ("dfps_optional_backward_boundary", predicates["forward_only_complete_but_unsound"] == 3025), ("dfps_full_iff", predicates["complete_and_sound_pairs"] == 255), ("dfps_closed_form_oracle", predicates["forward_complete_pairs"] == predicates["expected_closed_forms"]["one_direction"] and predicates["complete_and_sound_pairs"] == predicates["expected_closed_forms"]["iff"]), ("fps_exhaustive_cases", soundness["answer_predicate_cases"] == 8194), ("fps_soundness", soundness["soundness_violations"] == 0 and soundness["accepted_true_memberships"] == 4097), ("fps_proof_field_control", soundness["proof_field_omission_mutant_false_accepts"] == 4097), ("benchmark_counts", benchmark["records_total"] == 1086), ("benchmark_schema", benchmark["records_with_required_schema"] == 1086), ("benchmark_uniqueness", benchmark["unique_within_benchmarks"] == 1086 and benchmark["unique_global_fingerprints"] == 1086), ("official_export_byte_identity", benchmark["lean_files_regenerated_byte_exact"] == 1086), ("export_mutation_control", benchmark["export_mutant_detected"]), ("initial_baseline_files", baseline["files"] == 18), ("initial_baseline_records", baseline["method_problem_records_present"] == 5779 and baseline["method_problem_slots"] == 6516 and baseline["missing_records_counted_unsolved"] == 737), ("table_1_exact_maxima", maxima_ok), ("table_1_solved_rule_control", baseline["solving_rule_mutant_detected"]), ] results = { "paper_id": PAPER_ID, "scope": { "lean_compilation_executed": False, "lean_scope_note": "Lean 4 was not installed in this environment. The exact Lean/Pantograph implementation and proof source are pinned and structurally audited; finite semantics, all benchmark exports, and all archived Table-1 records are executed independently.", "current_official_commit": CURRENT_COMMIT, "initial_release_commit": INITIAL_COMMIT, }, "input_sha256": observed, "source_audits": source_audits, "claims": { "claim1": {"mdp": mdp, "implementation": {"basic_lean_sha256": "a908c060d1031506ac5844b838b819391a001474eee88a5df184fa9bdac97dc4", "solving_server_sha256": "76a8760c1651b63d33d26c1d0097896f61881210efbfc576811ba79f38bd474b"}}, "claim2": predicates, "claim3": soundness, "claim4": {"complete_pairs": predicates["forward_complete_pairs"], "sound_pairs": predicates["backward_sound_pairs"], "complete_and_sound_pairs": predicates["complete_and_sound_pairs"], "one_direction_only_invalid_pairs": predicates["forward_only_complete_but_unsound"]}, "claim5": benchmark, "claim6": baseline, }, "gates": [{"name": name, "passed": bool(passed)} for name, passed in gates], "summary": {"passed": sum(bool(value) for _, value in gates), "total": len(gates), "all_passed": all(value for _, value in gates)}, } if not results["summary"]["all_passed"]: raise RuntimeError([gate for gate in results["gates"] if not gate["passed"]]) dump(args.out / "results.json", results) print(json.dumps(results["summary"], sort_keys=True)) if __name__ == "__main__": main()