# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference) # Canonical Residual: https://corpusfetch.com/c/which-state-machine-transition-invariants-must-be-persisted-when-pausing-long-running-workflows.md # Remnant Warrant: https://corpusfetch.com/c/which-state-machine-transition-invariants-must-be-persisted-when-pausing-long-running-workflows.warrant.json # Markdown SHA-256: e3467ffa8597fb1da49db98be2b9673cb278c2feb5f0ec49603ccd281798d8f0 # Code AST SHA-256: ce6ea5a0bdd600553ef918824d4f48fedf652fcb5944e871124a220caad28ae0 # Verified Classes: TransitionPhase, ResumptionAction, WorkflowTransitionContext, ResumptionVerdict, WorkflowTransitionGuard # Defensive Guards: 1 fail-closed invariants (zero external deps) from dataclasses import dataclass from enum import Enum, auto import hashlib import json class TransitionPhase(Enum): INTENT_LOGGED = auto() SIDE_EFFECT_COMMITTED = auto() UNKNOWN_AMBIGUOUS = auto() ROLLBACK_REQUIRED = auto() class ResumptionAction(Enum): ADVANCE_SAFELY = auto() EXECUTE_RECONCILIATION = auto() RE_EXECUTE_WITH_IDEMPOTENCY = auto() HALT_AND_ESCALATE = auto() @dataclass(frozen=True) class WorkflowTransitionContext: workflow_id: str previous_state: str next_state: str transition_phase: TransitionPhase idempotency_key: str resume_reconciliation_predicate: str | None = None @dataclass(frozen=True) class ResumptionVerdict: action: ResumptionAction is_safe_to_advance: bool verdict_digest: str class WorkflowTransitionGuard: """ Evaluates persisted workflow transition states to ensure safe resumption without duplicating side effects or collapsing into limbo states. """ @staticmethod def _hash(data: str) -> str: return hashlib.sha256(data.encode("utf-8")).hexdigest() @classmethod def evaluate_resumption( cls, context: WorkflowTransitionContext ) -> ResumptionVerdict: if not context.idempotency_key.strip(): raise ValueError("A transition context must have an idempotency key.") # Gate 1: Explicit commit means the side effect is safe if context.transition_phase is TransitionPhase.SIDE_EFFECT_COMMITTED: action = ResumptionAction.ADVANCE_SAFELY is_safe = True # Gate 2: Intent logged but no commit means we must reconcile elif context.transition_phase is TransitionPhase.INTENT_LOGGED: if not context.resume_reconciliation_predicate: action = ResumptionAction.HALT_AND_ESCALATE is_safe = False else: action = ResumptionAction.EXECUTE_RECONCILIATION is_safe = False # Gate 3: Unknown state requires idempotent re-execution elif context.transition_phase is TransitionPhase.UNKNOWN_AMBIGUOUS: if not context.idempotency_key: action = ResumptionAction.HALT_AND_ESCALATE is_safe = False else: action = ResumptionAction.RE_EXECUTE_WITH_IDEMPOTENCY is_safe = False # Gate 4: Rollback required means the transition definitively failed else: action = ResumptionAction.HALT_AND_ESCALATE is_safe = False digest_payload = { "workflow": context.workflow_id, "phase": context.transition_phase.name, "action": action.name, } digest = cls._hash( json.dumps(digest_payload, sort_keys=True, separators=(",", ":")) ) return ResumptionVerdict( action=action, is_safe_to_advance=is_safe, verdict_digest=digest, ) @classmethod def confirm_reconciliation( cls, context: WorkflowTransitionContext, side_effect_exists: bool, ) -> ResumptionVerdict: """ Fetching-time confirmation after executing the reconciliation predicate. """ if side_effect_exists: return ResumptionVerdict( action=ResumptionAction.ADVANCE_SAFELY, is_safe_to_advance=True, verdict_digest=cls._hash(f"RECONCILED_EXISTS:{context.workflow_id}"), ) else: return ResumptionVerdict( action=ResumptionAction.RE_EXECUTE_WITH_IDEMPOTENCY, is_safe_to_advance=False, verdict_digest=cls._hash(f"RECONCILED_MISSING:{context.workflow_id}"), )