"""Optional historical report migration; production only reads existing journals.""" import hashlib import json import shutil from pathlib import Path from tools.rag.common import DATA_DIR from tools.runtime.report_files import CORE_JSON, run_id def migration_plan(data_dir: Path = DATA_DIR) -> list[dict]: moves = [] for path in sorted(data_dir.iterdir()): if not path.is_file() or path.name.startswith(".") or path.name in CORE_JSON: continue if path.suffix == ".json": target = data_dir / "reports/archive/legacy" / path.name elif path.suffix == ".log": target = data_dir / "logs/archive/legacy" / path.name else: continue moves.append( { "source": str(path.absolute()), "destination": str(target.absolute()), "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), } ) return moves def retention_plan(data_dir: Path = DATA_DIR, *, keep: int = 5) -> list[dict]: if keep < 1: raise ValueError("Keep at least one verification") completed = [] for path in (data_dir / "reports/verification").glob("*.json"): try: data = json.loads(path.read_text()) if isinstance(data, dict) and type(data.get("passed")) is bool: completed.append(path) except (ValueError, OSError): continue moves = [] for path in sorted(completed, key=lambda p: (p.stat().st_mtime_ns, p.name), reverse=True)[ keep: ]: target = data_dir / "reports/archive/verification" / path.name moves.append( { "source": str(path.absolute()), "destination": str(target.absolute()), "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), } ) log = data_dir / "logs/verification" / path.with_suffix(".pytest.log").name if log.exists(): target = data_dir / "logs/archive/verification" / log.name moves.append( { "source": str(log.absolute()), "destination": str(target.absolute()), "sha256": hashlib.sha256(log.read_bytes()).hexdigest(), } ) return moves def apply_moves(moves: list[dict], *, data_dir: Path = DATA_DIR) -> Path: # Validate the entire plan before moving anything. Never replace existing files. for move in moves: source, destination = Path(move["source"]), Path(move["destination"]) if ( destination.exists() or hashlib.sha256(source.read_bytes()).hexdigest() != move["sha256"] ): raise RuntimeError("Report migration conflict; no files moved") journal = data_dir / "reports/migrations" / (run_id() + ".json") journal.parent.mkdir(parents=True, exist_ok=True) record = { "method": "Move without rewriting content or deleting historical evidence", "moves": [], } def write_journal(): temporary = journal.with_suffix(".tmp") temporary.write_text(json.dumps(record, indent=2) + "\n") temporary.replace(journal) write_journal() for move in moves: destination = Path(move["destination"]) destination.parent.mkdir(parents=True, exist_ok=True) shutil.move(move["source"], destination) record["moves"].append(move) write_journal() return journal