Files
mtgodot-poc/.agents/skills/metin2-40250-parity-audit/scripts/audit_ledger.py
T
shenleiandClaude Opus 5 55f733fda6 audit: phase 0 tooling for file-by-file 40250 porting
- refroot.py: one reference-root resolver (MT_40250_SOURCE, then manifest)
  used by every audit script; missing tree or fingerprint input is an error
- port_map.py: per-function inventory of active 40250 units plus the
  40250 Python root, status/queue/init/show/check, 40250: tag scan
- manifest: fix 18 wrong reference paths (Python UI now points at the
  40250 Client/Eternexus/root, not the m2dev assets/root), drop 2 deleted
  implementation files, move 64 prose test entries to evidence.commands
- first port-map entry: PythonPlayerEventHandler.cpp
- roadmap replaced by a batch/unit queue

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:17:49 +09:00

278 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate, refresh, and summarize the repository's 40250 parity ledger."""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
import refroot # script directory is on sys.path when run directly
STATUSES = {
"UNMAPPED", "PARTIAL", "MAPPED", "STATIC_VERIFIED", "TEST_VERIFIED",
"IN_PROGRESS", "STALE", "REGRESSION", "BLOCKED", "EXCLUDED",
}
PRIORITIES = {"P0", "P1", "P2", "P3"}
PENDING = {"UNMAPPED", "PARTIAL", "MAPPED", "IN_PROGRESS", "STALE", "REGRESSION", "BLOCKED"}
EQUIVALENCE_FIELDS = {
"preconditions",
"branch_structure",
"algorithms_formulas",
"state_transition_order",
"constants_units",
"timing_event_sources",
"resource_data_sources",
"protocol_side_effects",
"interruption_failure_cleanup",
}
def repo_root() -> Path:
here = Path(__file__).resolve()
for parent in here.parents:
if (parent / ".git").exists():
return parent
raise SystemExit("cannot locate repository root")
def manifest_path(root: Path) -> Path:
return root / "audit" / "manifest.json"
def load_manifest(root: Path) -> dict:
path = manifest_path(root)
if not path.exists():
raise SystemExit(f"missing audit ledger: {path}")
return json.loads(path.read_text(encoding="utf-8"))
def write_manifest(root: Path, data: dict) -> None:
manifest_path(root).write_text(
json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
def append_history(root: Path, event: dict) -> None:
event = {"time": datetime.now(timezone.utc).isoformat(), **event}
with (root / "audit" / "history.jsonl").open("a", encoding="utf-8") as handle:
handle.write(json.dumps(event, ensure_ascii=False, separators=(",", ":")) + "\n")
def resolve_files(root: Path, data: dict, section: str) -> list[Path]:
contract = data
files = contract.get(section, {}).get("files", []) if section != "tests" else contract.get("evidence", {}).get("tests", [])
base = root
if section == "reference":
base = refroot.reference_root(root)
return [(base / item).resolve() for item in files]
def fingerprint(paths: list[Path]) -> str:
digest = hashlib.sha256()
for path in sorted(paths, key=lambda p: str(p)):
digest.update(str(path).encode())
if not path.is_file():
raise SystemExit(f"fingerprint input missing: {path}")
digest.update(hashlib.sha256(path.read_bytes()).digest())
return digest.hexdigest()
def validation_errors(root: Path, manifest: dict) -> list[str]:
errors: list[str] = []
seen: set[str] = set()
if manifest.get("schema_version") != 1:
errors.append("schema_version must be 1")
if not isinstance(manifest.get("contracts"), list):
return errors + ["contracts must be an array"]
for index, contract in enumerate(manifest["contracts"]):
label = contract.get("id", f"contracts[{index}]")
for field in ("id", "title", "subsystem", "priority", "status"):
if not contract.get(field):
errors.append(f"{label}: missing {field}")
if label in seen:
errors.append(f"{label}: duplicate id")
seen.add(label)
if contract.get("priority") not in PRIORITIES:
errors.append(f"{label}: invalid priority {contract.get('priority')}")
if contract.get("status") not in STATUSES:
errors.append(f"{label}: invalid status {contract.get('status')}")
if contract.get("status") == "EXCLUDED" and not contract.get("exclusion_reason"):
errors.append(f"{label}: EXCLUDED requires exclusion_reason")
if contract.get("status") in {"STATIC_VERIFIED", "TEST_VERIFIED"}:
reference = contract.get("reference", {}).get("files", [])
implementation = contract.get("implementation", {}).get("files", [])
evidence = contract.get("evidence", {})
if not reference or not implementation:
errors.append(f"{label}: verified status requires mapped files")
contract_path = evidence.get("contract", "")
if not contract_path or not (root / contract_path).is_file():
errors.append(f"{label}: verified status requires an existing contract document")
equivalence = contract.get("equivalence", {})
missing_equivalence = sorted(
field for field in EQUIVALENCE_FIELDS if equivalence.get(field) != "VERIFIED"
)
if missing_equivalence:
errors.append(
f"{label}: verified status requires VERIFIED implementation equivalence for "
+ ", ".join(missing_equivalence)
)
adaptations = contract.get("platform_adaptations", [])
if not isinstance(adaptations, list):
errors.append(f"{label}: platform_adaptations must be an array")
else:
for adaptation_index, adaptation in enumerate(adaptations):
missing = [
field for field in ("reference", "implementation", "invariant", "tests")
if not adaptation.get(field)
]
if missing:
errors.append(
f"{label}: platform_adaptations[{adaptation_index}] missing "
+ ", ".join(missing)
)
for test in adaptation.get("tests", []):
if not (root / test).is_file():
errors.append(
f"{label}: platform adaptation test does not exist: {test}"
)
if contract.get("remaining"):
errors.append(f"{label}: verified status cannot have remaining material branches")
if contract.get("status") == "TEST_VERIFIED":
evidence = contract.get("evidence", {})
tests = evidence.get("tests", [])
if not tests or any(not (root / test).is_file() for test in tests):
errors.append(f"{label}: TEST_VERIFIED requires existing tests")
if evidence.get("last_test_result") != "PASS":
errors.append(f"{label}: TEST_VERIFIED requires last_test_result PASS")
return errors
def command_validate(root: Path, manifest: dict, _args: argparse.Namespace) -> int:
errors = validation_errors(root, manifest)
if errors:
for error in errors:
print(f"ERROR: {error}")
return 1
print(f"PASS: audit ledger is valid ({len(manifest['contracts'])} contracts)")
return 0
def command_report(root: Path, manifest: dict, args: argparse.Namespace) -> int:
contracts = manifest["contracts"]
by_status = Counter(item["status"] for item in contracts)
by_priority = Counter(item["priority"] for item in contracts)
lines = ["# 40250 parity audit coverage", "", f"Total contracts: {len(contracts)}", "", "## Status", ""]
for status in sorted(STATUSES):
lines.append(f"- {status}: {by_status[status]}")
lines.extend(["", "## Priority", ""])
for priority in sorted(PRIORITIES):
lines.append(f"- {priority}: {by_priority[priority]}")
pending = sorted(
(item for item in contracts if item["status"] in PENDING),
key=lambda item: (item["priority"], item["status"], item["id"]),
)
pending_by_priority = Counter()
pending_items_total = 0
for item in pending:
remaining = item.get("remaining", [])
if not isinstance(remaining, list):
continue
count = len(remaining)
pending_items_total += count
pending_by_priority[item["priority"]] += count
lines.extend(["", "## Pending branch items", ""])
lines.append(f"- Total: {pending_items_total}")
for priority in sorted(PRIORITIES):
lines.append(f"- {priority}: {pending_by_priority[priority]}")
lines.append("- Routing: see `audit/remediation-roadmap.md`")
lines.extend(["", "## Pending", ""])
lines.extend(f"- [{item['priority']}] {item['id']}: {item['status']}" for item in pending)
if not pending:
lines.append("- None")
report = "\n".join(lines) + "\n"
if args.write:
out = root / "audit" / "reports" / "coverage.md"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(report, encoding="utf-8")
print(out)
else:
print(report, end="")
return 0
def command_next(_root: Path, manifest: dict, args: argparse.Namespace) -> int:
pending = sorted(
(item for item in manifest["contracts"] if item["status"] in PENDING),
key=lambda item: (item["priority"], item["status"], item["id"]),
)
for item in pending[: args.limit]:
print(f"{item['priority']}\t{item['status']}\t{item['id']}\t{item['title']}")
return 0
def command_refresh(root: Path, manifest: dict, args: argparse.Namespace) -> int:
changed = 0
for contract in manifest["contracts"]:
working = contract
current = {
"reference": fingerprint(resolve_files(root, working, "reference")),
"implementation": fingerprint(resolve_files(root, working, "implementation")),
"tests": fingerprint(resolve_files(root, working, "tests")),
}
old = contract.get("fingerprints", {})
contract_changed = old != current
reasons = [name for name, value in current.items() if old.get(name) and old[name] != value]
if reasons and contract.get("status") in {"STATIC_VERIFIED", "TEST_VERIFIED"}:
previous = contract["status"]
if args.write:
contract["status"] = "STALE"
contract["stale_reasons"] = reasons
append_history(root, {
"event": "status_changed", "id": contract["id"], "from": previous,
"to": "STALE", "reason": "fingerprint changed: " + ",".join(reasons),
})
if contract_changed:
changed += 1
if args.write:
contract["fingerprints"] = current
if args.write and changed:
write_manifest(root, manifest)
print(f"refresh: {changed} change(s){' written' if args.write else ' detected'}")
return 0
def main() -> int:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("validate")
report = sub.add_parser("report")
report.add_argument("--write", action="store_true")
next_parser = sub.add_parser("next")
next_parser.add_argument("--limit", type=int, default=20)
refresh = sub.add_parser("refresh")
refresh.add_argument("--write", action="store_true")
args = parser.parse_args()
root = repo_root()
manifest = load_manifest(root)
errors = validation_errors(root, manifest)
if errors and args.command != "validate":
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1
return {
"validate": command_validate,
"report": command_report,
"next": command_next,
"refresh": command_refresh,
}[args.command](root, manifest, args)
if __name__ == "__main__":
raise SystemExit(main())