Praneshrajan15's picture
Deploy DataForge playground API
791c076 verified
"""Revert an applied DataForge transaction."""
from __future__ import annotations
from pathlib import Path
from dataforge.transactions.files import (
SourceLockError,
atomic_write_bytes,
source_path_lock,
)
from dataforge.transactions.log import (
TransactionAuditVerdict,
append_reverted_event,
find_transaction_log,
load_transaction,
sha256_file,
verify_transaction_log,
)
from dataforge.transactions.txn import RepairTransaction
class TransactionRevertError(Exception):
"""Raised when a transaction cannot be safely reverted."""
def revert_transaction(txn_id: str, *, search_root: Path | None = None) -> RepairTransaction:
"""Revert a previously applied transaction by restoring its source snapshot.
Args:
txn_id: Canonical transaction identifier.
search_root: Optional root directory used to locate the transaction log.
Returns:
The replayed transaction state after appending the revert event.
Raises:
TransactionRevertError: If the transaction is not revertible or hash checks fail.
"""
log_path = find_transaction_log(txn_id, search_root=search_root)
audit_report = verify_transaction_log(txn_id, log_path=log_path)
if audit_report.verdict not in {
TransactionAuditVerdict.VERIFIED,
TransactionAuditVerdict.LEGACY_UNVERIFIED,
}:
details = "; ".join(audit_report.errors) or audit_report.verdict.value
raise TransactionRevertError(
f"Refusing to revert because transaction audit verification failed: {details}"
)
transaction = load_transaction(log_path)
if not transaction.applied or transaction.post_sha256 is None:
raise TransactionRevertError(
f"Transaction '{txn_id}' was recorded but never applied, so there is nothing to revert."
)
if transaction.reverted_at is not None:
raise TransactionRevertError(f"Transaction '{txn_id}' has already been reverted.")
source_path = Path(transaction.source_path)
snapshot_path = Path(transaction.source_snapshot_path)
if not source_path.exists():
raise TransactionRevertError(f"Source file not found: '{source_path}'.")
if not snapshot_path.exists():
raise TransactionRevertError(
f"Source snapshot not found for transaction '{txn_id}': '{snapshot_path}'."
)
try:
with source_path_lock(source_path):
current_bytes = source_path.read_bytes()
current_sha256 = sha256_file(source_path)
if current_sha256 != transaction.post_sha256:
raise TransactionRevertError(
"Refusing to revert because the current file no longer matches the recorded "
"post-state hash. The file may have been edited after apply."
)
atomic_write_bytes(source_path, snapshot_path.read_bytes())
reverted_sha256 = sha256_file(source_path)
if reverted_sha256 != transaction.source_sha256:
atomic_write_bytes(source_path, current_bytes)
raise TransactionRevertError(
f"Revert failed integrity verification for transaction '{txn_id}'."
)
try:
append_reverted_event(log_path, txn_id)
except Exception:
atomic_write_bytes(source_path, current_bytes)
raise
except SourceLockError as exc:
raise TransactionRevertError(str(exc)) from exc
return load_transaction(log_path)