Commit atomic domain and recovery state together
This commit is contained in:
@@ -105,6 +105,56 @@ class DurableRecoveryOperation:
|
||||
session.commit()
|
||||
self.closed = True
|
||||
|
||||
def commit_atomic_success(
|
||||
self,
|
||||
session: Session,
|
||||
*,
|
||||
evidence: dict[str, Any],
|
||||
) -> None:
|
||||
"""Commit domain writes and verified success in one DB transaction."""
|
||||
|
||||
self._commit_atomic_terminal(
|
||||
session,
|
||||
status=RecoveryStatus.SUCCEEDED,
|
||||
summary="Operation effects and authoritative state were verified",
|
||||
kind="verified-success",
|
||||
evidence=evidence,
|
||||
)
|
||||
|
||||
def commit_atomic_failure(
|
||||
self,
|
||||
session: Session,
|
||||
*,
|
||||
summary: str,
|
||||
evidence: dict[str, Any],
|
||||
) -> None:
|
||||
"""Commit domain failure evidence and the terminal state atomically."""
|
||||
|
||||
self._commit_atomic_terminal(
|
||||
session,
|
||||
status=RecoveryStatus.FAILED,
|
||||
summary=summary,
|
||||
kind="verified-failure",
|
||||
evidence=evidence,
|
||||
)
|
||||
|
||||
def commit_atomic_rejection(
|
||||
self,
|
||||
session: Session,
|
||||
*,
|
||||
summary: str,
|
||||
evidence: dict[str, Any],
|
||||
) -> None:
|
||||
"""Commit a definitive rejection and its domain evidence atomically."""
|
||||
|
||||
self._commit_atomic_terminal(
|
||||
session,
|
||||
status=RecoveryStatus.REJECTED,
|
||||
summary=summary,
|
||||
kind="verified-rejection",
|
||||
evidence=evidence,
|
||||
)
|
||||
|
||||
def fail(self, *, summary: str, evidence: dict[str, Any]) -> None:
|
||||
"""Finish a verified, ordinary failure that needs no recovery."""
|
||||
|
||||
@@ -325,6 +375,49 @@ class DurableRecoveryOperation:
|
||||
self.lease_claim = claim
|
||||
return operation, claim
|
||||
|
||||
def _commit_atomic_terminal(
|
||||
self,
|
||||
session: Session,
|
||||
*,
|
||||
status: RecoveryStatus,
|
||||
summary: str,
|
||||
kind: str,
|
||||
evidence: dict[str, Any],
|
||||
) -> None:
|
||||
if status not in {
|
||||
RecoveryStatus.SUCCEEDED,
|
||||
RecoveryStatus.FAILED,
|
||||
RecoveryStatus.REJECTED,
|
||||
}:
|
||||
raise ValueError("Unsupported atomic terminal recovery status")
|
||||
try:
|
||||
operation, claim = self._locked_and_renewed(session)
|
||||
if operation.mode != RecoveryMode.ATOMIC.value:
|
||||
raise RecoveryGuaranteeError(
|
||||
"Atomic terminal commits require an atomic recovery plan"
|
||||
)
|
||||
transition_recovery_operation(
|
||||
session,
|
||||
operation,
|
||||
status=status,
|
||||
kind=kind,
|
||||
summary=summary,
|
||||
evidence=evidence,
|
||||
failure_summary=(
|
||||
summary
|
||||
if status in {RecoveryStatus.FAILED, RecoveryStatus.REJECTED}
|
||||
else None
|
||||
),
|
||||
lease_claim=claim,
|
||||
)
|
||||
self._verify_chain(session)
|
||||
release_lease(session, claim)
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
raise
|
||||
self.closed = True
|
||||
|
||||
def _verify_chain(self, session: Session) -> None:
|
||||
if not verify_recovery_evidence_chain(session, self.operation_id):
|
||||
raise RecoveryGuaranteeError(
|
||||
|
||||
Reference in New Issue
Block a user