"""CLI subcommand: ``dataforge revert ``.""" from __future__ import annotations import json from pathlib import Path from typing import Annotated import typer from rich.console import Console from rich.panel import Panel from dataforge.transactions.log import sha256_file, verify_transaction_log from dataforge.transactions.revert import TransactionRevertError, revert_transaction _console = Console(stderr=True) def revert( txn_id: Annotated[ str, typer.Argument(help="Transaction identifier to revert."), ], search_root: Annotated[ Path | None, typer.Option( "--search-root", help="Root directory used to locate the transaction log.", exists=True, file_okay=False, dir_okay=True, readable=True, ), ] = None, json_output: Annotated[ bool, typer.Option("--json", help="Print the revert receipt as JSON."), ] = False, ) -> None: """Revert a previously applied DataForge repair transaction.""" try: transaction = revert_transaction(txn_id, search_root=search_root) except TransactionRevertError as exc: if json_output: typer.echo( json.dumps( { "schema_version": "repair_revert_receipt_v1", "ok": False, "txn_id": txn_id, "error": str(exc), }, indent=2, sort_keys=True, ) ) raise typer.Exit(code=1) from exc _console.print(Panel(f"[bold red]{exc}[/bold red]", title="Revert Error", style="red")) raise typer.Exit(code=1) from exc except Exception as exc: if json_output: typer.echo( json.dumps( { "schema_version": "repair_revert_receipt_v1", "ok": False, "txn_id": txn_id, "error": str(exc), }, indent=2, sort_keys=True, ) ) raise typer.Exit(code=2) from exc _console.print(Panel(f"[bold red]{exc}[/bold red]", title="Revert Error", style="red")) raise typer.Exit(code=2) from exc audit_report = verify_transaction_log(txn_id, search_root=search_root) if json_output: if transaction.source_kind == "table_store": typer.echo( json.dumps( { "schema_version": "repair_revert_receipt_v1", "ok": True, "txn_id": transaction.txn_id, "source_kind": transaction.source_kind, "backend": transaction.backend, "source_path": transaction.source_path, "expected_source_sha256": transaction.source_sha256, "restored_source_sha256": None, "reverted_at": transaction.reverted_at.isoformat() if transaction.reverted_at is not None else None, "audit_verdict": audit_report.verdict.value, "revert_event_sha256": audit_report.head_sha256, }, indent=2, sort_keys=True, ) ) raise typer.Exit(code=0) source_path = Path(transaction.source_path) typer.echo( json.dumps( { "schema_version": "repair_revert_receipt_v1", "ok": True, "txn_id": transaction.txn_id, "source_path": transaction.source_path, "expected_source_sha256": transaction.source_sha256, "restored_source_sha256": sha256_file(source_path), "reverted_at": transaction.reverted_at.isoformat() if transaction.reverted_at is not None else None, "audit_verdict": audit_report.verdict.value, "revert_event_sha256": audit_report.head_sha256, }, indent=2, sort_keys=True, ) ) raise typer.Exit(code=0) title = ( "Table Store Revert Complete" if transaction.source_kind == "table_store" else "Revert Complete" ) Console().print( Panel( ( f"[green]Source restored successfully.[/green]\n" f"Transaction: [bold]{transaction.txn_id}[/bold]" ), title=title, style="green", ) )