feat(v2): add guarded execution state transitions
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
ACTIVE_STATES = frozenset({"queued", "preparing", "running", "verifying", "cancelling"})
|
||||||
|
TERMINAL_STATES = frozenset({"committed", "cancelled", "failed"})
|
||||||
|
|
||||||
|
_ALLOWED: Mapping[str, frozenset[str]] = {
|
||||||
|
"queued": frozenset({"preparing", "cancelled", "failed"}),
|
||||||
|
"preparing": frozenset({"running", "cancelling", "failed"}),
|
||||||
|
"running": frozenset({"verifying", "cancelling", "failed"}),
|
||||||
|
"verifying": frozenset({"committed", "failed"}),
|
||||||
|
"cancelling": frozenset({"cancelled", "failed"}),
|
||||||
|
"committed": frozenset(),
|
||||||
|
"cancelled": frozenset(),
|
||||||
|
"failed": frozenset(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TransitionError(ValueError):
|
||||||
|
code = "invalid_transition"
|
||||||
|
|
||||||
|
|
||||||
|
def transition(current: str, target: str) -> str:
|
||||||
|
"""Validate and return one durable execution-state transition."""
|
||||||
|
if target not in _ALLOWED.get(current, frozenset()):
|
||||||
|
raise TransitionError(f"Cannot transition execution from {current!r} to {target!r}.")
|
||||||
|
return target
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from backup_tool.execution import ACTIVE_STATES, TERMINAL_STATES, TransitionError, transition
|
||||||
|
|
||||||
|
|
||||||
|
def test_transitions_are_monotonic_and_terminal_states_cannot_change() -> None:
|
||||||
|
assert transition("queued", "preparing") == "preparing"
|
||||||
|
assert transition("preparing", "running") == "running"
|
||||||
|
assert transition("running", "verifying") == "verifying"
|
||||||
|
assert transition("verifying", "committed") == "committed"
|
||||||
|
assert "committed" in TERMINAL_STATES
|
||||||
|
assert "queued" in ACTIVE_STATES
|
||||||
|
try:
|
||||||
|
transition("committed", "cancelled")
|
||||||
|
except TransitionError as error:
|
||||||
|
assert error.code == "invalid_transition"
|
||||||
|
else:
|
||||||
|
raise AssertionError("terminal execution changed state")
|
||||||
|
|
||||||
|
|
||||||
|
def test_cancellation_is_cooperative() -> None:
|
||||||
|
assert transition("running", "cancelling") == "cancelling"
|
||||||
|
assert transition("cancelling", "cancelled") == "cancelled"
|
||||||
Reference in New Issue
Block a user