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 }