Add commitment state machines

This commit is contained in:
2026-05-25 12:52:19 -04:00
parent b89351727e
commit 3fe7131f13
+169 -1
View File
@@ -1 +1,169 @@
// Placeholder for upcoming state_machine module. use crate::domain::{CommitmentState, RuntimeState};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RuntimeAction {
EnterPlanning,
CancelOrTimeout,
Activate { policy_accepted: bool },
StartTransition,
CompleteForReview,
SevereViolation,
ReturnToActive,
SwitchTask,
EndTransitionForReview,
ContinuePlanning,
EndWorkPeriod,
EnterAdminOverride,
ExitAdminOverride(RuntimeState),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CommitmentAction {
Activate,
PauseForTransition,
ReturnFromPause,
Complete,
Abandon,
Violate,
RecoverExplicitly,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TransitionError {
PolicyRejected,
IllegalRuntimeTransition,
IllegalCommitmentTransition,
}
impl std::fmt::Display for TransitionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TransitionError::PolicyRejected => write!(f, "policy was rejected"),
TransitionError::IllegalRuntimeTransition => write!(f, "illegal runtime transition"),
TransitionError::IllegalCommitmentTransition => {
write!(f, "illegal commitment transition")
}
}
}
}
impl std::error::Error for TransitionError {}
pub fn transition_runtime(
current: RuntimeState,
action: RuntimeAction,
) -> Result<RuntimeState, TransitionError> {
use RuntimeAction::*;
use RuntimeState::*;
match (current, action) {
(Locked, EnterPlanning) => Ok(Planning),
(Planning, Activate { policy_accepted: true }) => Ok(Active),
(Planning, Activate { policy_accepted: false }) => Err(TransitionError::PolicyRejected),
(Planning, CancelOrTimeout) => Ok(Locked),
(Active, StartTransition) => Ok(Transition),
(Active, CompleteForReview) => Ok(Review),
(Active, SevereViolation) => Ok(Locked),
(Transition, ReturnToActive) => Ok(Active),
(Transition, SwitchTask) => Ok(Planning),
(Transition, EndTransitionForReview) => Ok(Review),
(Transition, CancelOrTimeout) => Ok(Locked),
(Review, ContinuePlanning) => Ok(Planning),
(Review, EndWorkPeriod) => Ok(Locked),
(_, EnterAdminOverride) => Ok(AdminOverride),
(AdminOverride, ExitAdminOverride(next)) => Ok(next),
_ => Err(TransitionError::IllegalRuntimeTransition),
}
}
pub fn transition_commitment(
current: CommitmentState,
action: CommitmentAction,
) -> Result<CommitmentState, TransitionError> {
use CommitmentAction::*;
use CommitmentState::*;
match (current, action) {
(Draft, Activate) => Ok(Active),
(Active, PauseForTransition) => Ok(Paused),
(Paused, ReturnFromPause) => Ok(Active),
(Active, Complete) => Ok(Completed),
(Active, Abandon) => Ok(Abandoned),
(Active, Violate) => Ok(Violated),
(Paused, Abandon) => Ok(Abandoned),
(Violated, RecoverExplicitly) => Ok(Active),
(Violated, Abandon) => Ok(Abandoned),
_ => Err(TransitionError::IllegalCommitmentTransition),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn planning_can_only_activate_with_valid_policy() {
assert_eq!(
transition_runtime(
RuntimeState::Planning,
RuntimeAction::Activate {
policy_accepted: true,
}
),
Ok(RuntimeState::Active)
);
assert_eq!(
transition_runtime(
RuntimeState::Planning,
RuntimeAction::Activate {
policy_accepted: false,
}
),
Err(TransitionError::PolicyRejected)
);
}
#[test]
fn active_can_enter_transition_or_review_or_locked() {
assert_eq!(
transition_runtime(RuntimeState::Active, RuntimeAction::StartTransition),
Ok(RuntimeState::Transition)
);
assert_eq!(
transition_runtime(RuntimeState::Active, RuntimeAction::CompleteForReview),
Ok(RuntimeState::Review)
);
assert_eq!(
transition_runtime(RuntimeState::Active, RuntimeAction::SevereViolation),
Ok(RuntimeState::Locked)
);
}
#[test]
fn locked_cannot_directly_become_active() {
assert_eq!(
transition_runtime(
RuntimeState::Locked,
RuntimeAction::Activate {
policy_accepted: true,
}
),
Err(TransitionError::IllegalRuntimeTransition)
);
}
#[test]
fn commitment_violation_recovery_is_explicit() {
assert_eq!(
transition_commitment(
CommitmentState::Violated,
CommitmentAction::RecoverExplicitly
),
Ok(CommitmentState::Active)
);
assert_eq!(
transition_commitment(CommitmentState::Violated, CommitmentAction::ReturnFromPause),
Err(TransitionError::IllegalCommitmentTransition)
);
}
}