78 lines
3.1 KiB
Go
78 lines
3.1 KiB
Go
package runtime
|
|
|
|
// transitionKey stores one `(from, to)` pair in the allowed-transitions
|
|
// table.
|
|
type transitionKey struct {
|
|
from Status
|
|
to Status
|
|
}
|
|
|
|
// allowedTransitions enumerates the runtime-status transitions Game
|
|
// Master is allowed to apply. The set mirrors the lifecycle flows frozen
|
|
// in `galaxy/gamemaster/README.md §Lifecycles`:
|
|
//
|
|
// - starting → running: register-runtime CAS after a successful
|
|
// engine /admin/init.
|
|
// - running → generation_in_progress: scheduler ticker or admin
|
|
// force-next-turn enters turn generation.
|
|
// - generation_in_progress → running: turn generation succeeded with
|
|
// `finished=false`.
|
|
// - generation_in_progress → generation_failed: engine timeout or
|
|
// 5xx during turn generation.
|
|
// - generation_in_progress → finished: engine returned
|
|
// `finished=true`; the state is terminal.
|
|
// - generation_failed → generation_in_progress: admin force-next-turn
|
|
// after manual recovery.
|
|
// - running → engine_unreachable: runtime:health_events observed an
|
|
// engine container failure (Stage 18 consumer).
|
|
// - engine_unreachable → running: runtime:health_events observed a
|
|
// recovery; reserved for the Stage 18 consumer; declared here so
|
|
// Stage 18 needs no transitions edit.
|
|
// - running → stopped, generation_in_progress → stopped,
|
|
// generation_failed → stopped, engine_unreachable → stopped: admin
|
|
// stop is allowed from every non-terminal status (README §Stop:
|
|
// «CAS `runtime_records.status: * → stopped`»).
|
|
var allowedTransitions = map[transitionKey]struct{}{
|
|
{StatusStarting, StatusRunning}: {},
|
|
|
|
{StatusRunning, StatusGenerationInProgress}: {},
|
|
|
|
{StatusGenerationInProgress, StatusRunning}: {},
|
|
{StatusGenerationInProgress, StatusGenerationFailed}: {},
|
|
{StatusGenerationInProgress, StatusFinished}: {},
|
|
{StatusGenerationFailed, StatusGenerationInProgress}: {},
|
|
|
|
{StatusRunning, StatusEngineUnreachable}: {},
|
|
{StatusEngineUnreachable, StatusRunning}: {},
|
|
|
|
{StatusRunning, StatusStopped}: {},
|
|
{StatusGenerationInProgress, StatusStopped}: {},
|
|
{StatusGenerationFailed, StatusStopped}: {},
|
|
{StatusEngineUnreachable, StatusStopped}: {},
|
|
}
|
|
|
|
// AllowedTransitions returns a copy of the `(from, to)` allowed
|
|
// transitions table used by Transition. The returned map is safe to
|
|
// mutate; callers should not rely on iteration order.
|
|
func AllowedTransitions() map[Status][]Status {
|
|
result := make(map[Status][]Status)
|
|
for key := range allowedTransitions {
|
|
result[key.from] = append(result[key.from], key.to)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Transition reports whether from may transition to next. The function
|
|
// returns nil when the pair is permitted, and an *InvalidTransitionError
|
|
// wrapping ErrInvalidTransition otherwise. It does not touch any store
|
|
// and is safe to call from any layer.
|
|
func Transition(from Status, next Status) error {
|
|
if !from.IsKnown() || !next.IsKnown() {
|
|
return &InvalidTransitionError{From: from, To: next}
|
|
}
|
|
if _, ok := allowedTransitions[transitionKey{from: from, to: next}]; !ok {
|
|
return &InvalidTransitionError{From: from, To: next}
|
|
}
|
|
return nil
|
|
}
|