// Package statemachine holds the pure runtime and commitment transition // functions. No I/O, no shared state. package statemachine import ( "fmt" "keel/internal/mode/focus/domain" ) // RuntimeAction enumerates runtime transitions. Activate's policy acceptance // and admin-override exit targets are flattened into distinct actions so the // action type stays a simple value. type RuntimeAction string const ( EnterPlanning RuntimeAction = "enter_planning" CancelOrTimeout RuntimeAction = "cancel_or_timeout" ActivateAccepted RuntimeAction = "activate_accepted" ActivateRejected RuntimeAction = "activate_rejected" StartTransition RuntimeAction = "start_transition" CompleteForReview RuntimeAction = "complete_for_review" SevereViolation RuntimeAction = "severe_violation" ReturnToActive RuntimeAction = "return_to_active" SwitchTask RuntimeAction = "switch_task" EndTransitionForReview RuntimeAction = "end_transition_for_review" ContinuePlanning RuntimeAction = "continue_planning" EndWorkPeriod RuntimeAction = "end_work_period" EnterAdminOverride RuntimeAction = "enter_admin_override" ExitAdminOverrideToLocked RuntimeAction = "exit_admin_override_to_locked" ExitAdminOverrideToPlanning RuntimeAction = "exit_admin_override_to_planning" ) type CommitmentAction string const ( CommitmentActivate CommitmentAction = "activate" PauseForTransition CommitmentAction = "pause_for_transition" ReturnFromPause CommitmentAction = "return_from_pause" Complete CommitmentAction = "complete" Abandon CommitmentAction = "abandon" Violate CommitmentAction = "violate" RecoverExplicitly CommitmentAction = "recover_explicitly" ) // IllegalTransitionError reports a rejected transition. type IllegalTransitionError struct { From string Action string } func (e IllegalTransitionError) Error() string { return fmt.Sprintf("illegal transition from %s via %s", e.From, e.Action) } // PolicyRejectedError reports an activation attempted with an unaccepted policy. type PolicyRejectedError struct{} func (PolicyRejectedError) Error() string { return "policy was rejected" } func TransitionRuntime(current domain.RuntimeState, action RuntimeAction) (domain.RuntimeState, error) { type key struct { s domain.RuntimeState a RuntimeAction } table := map[key]domain.RuntimeState{ {domain.RuntimeLocked, EnterPlanning}: domain.RuntimePlanning, {domain.RuntimePlanning, ActivateAccepted}: domain.RuntimeActive, {domain.RuntimePlanning, CancelOrTimeout}: domain.RuntimeLocked, {domain.RuntimeActive, StartTransition}: domain.RuntimeTransition, {domain.RuntimeActive, CompleteForReview}: domain.RuntimeReview, {domain.RuntimeActive, SevereViolation}: domain.RuntimeLocked, {domain.RuntimeTransition, ReturnToActive}: domain.RuntimeActive, {domain.RuntimeTransition, SwitchTask}: domain.RuntimePlanning, {domain.RuntimeTransition, EndTransitionForReview}: domain.RuntimeReview, {domain.RuntimeTransition, CancelOrTimeout}: domain.RuntimeLocked, {domain.RuntimeReview, ContinuePlanning}: domain.RuntimePlanning, {domain.RuntimeReview, EndWorkPeriod}: domain.RuntimeLocked, {domain.RuntimeAdminOverride, ExitAdminOverrideToLocked}: domain.RuntimeLocked, {domain.RuntimeAdminOverride, ExitAdminOverrideToPlanning}: domain.RuntimePlanning, } if action == ActivateRejected && current == domain.RuntimePlanning { return current, PolicyRejectedError{} } if action == EnterAdminOverride { return domain.RuntimeAdminOverride, nil } if next, ok := table[key{current, action}]; ok { return next, nil } return current, IllegalTransitionError{From: string(current), Action: string(action)} } func TransitionCommitment(current domain.CommitmentState, action CommitmentAction) (domain.CommitmentState, error) { type key struct { s domain.CommitmentState a CommitmentAction } table := map[key]domain.CommitmentState{ {domain.CommitmentDraft, CommitmentActivate}: domain.CommitmentActive, {domain.CommitmentActive, PauseForTransition}: domain.CommitmentPaused, {domain.CommitmentPaused, ReturnFromPause}: domain.CommitmentActive, {domain.CommitmentActive, Complete}: domain.CommitmentCompleted, {domain.CommitmentActive, Abandon}: domain.CommitmentAbandoned, {domain.CommitmentActive, Violate}: domain.CommitmentViolated, {domain.CommitmentPaused, Abandon}: domain.CommitmentAbandoned, {domain.CommitmentViolated, RecoverExplicitly}: domain.CommitmentActive, {domain.CommitmentViolated, Abandon}: domain.CommitmentAbandoned, } if next, ok := table[key{current, action}]; ok { return next, nil } return current, IllegalTransitionError{From: string(current), Action: string(action)} }