refactor(qbz-app): move runtime core

This commit is contained in:
vicrodh committed 2026-05-14 11:45:59 -06:00
1 parent 4f008cd219
commit d16ef003a5
2 files changed
+409 -405

No files matched your search

+404 -1
View File
@@ -1 +1,404 @@
//! Framework-agnostic runtime primitives.
//! Runtime Session Contract
//!
//! Implements the UI-agnostic runtime lifecycle as per ADR_RUNTIME_SESSION_CONTRACT.md
//!
//! Key concepts:
//! - Single bootstrap entrypoint
//! - Canonical state machine (Uninitialized -> Ready)
//! - Typed errors (no string matching)
//! - Command gating in backend
//! - Lifecycle events
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
/// Canonical runtime states
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "state", content = "data")]
pub enum RuntimeState {
/// Initial state - nothing initialized
Uninitialized,
/// Client initialized but no authentication
InitializedNoAuth,
/// Authenticated but per-user session not activated (transitional)
AuthenticatedNoUserSession { user_id: u64 },
/// Fully ready - all systems operational
Ready { user_id: u64 },
/// Degraded state - something is broken
Degraded { reason: DegradedReason },
}
impl Default for RuntimeState {
fn default() -> Self {
Self::Uninitialized
}
}
/// Reasons for degraded state
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "code", content = "message")]
pub enum DegradedReason {
/// Bundle token extraction failed
BundleExtractionFailed(String),
/// CoreBridge initialization failed
CoreBridgeInitFailed(String),
/// Network connectivity issues
NetworkError(String),
/// Database/storage issues
StorageError(String),
/// Session activation failed (per-user stores not initialized)
SessionActivationFailed(String),
}
/// Full runtime status returned by runtime_get_status and runtime_bootstrap
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeStatus {
/// Current state
pub state: RuntimeState,
/// User ID if authenticated (None if not logged in)
pub user_id: Option<u64>,
/// Whether the API client is initialized (bundle tokens extracted)
pub client_initialized: bool,
/// Whether legacy auth is active
pub legacy_auth: bool,
/// Whether CoreBridge/V2 auth is active
pub corebridge_auth: bool,
/// Whether per-user session is activated
pub session_activated: bool,
/// Degraded reason if state is Degraded
pub degraded_reason: Option<DegradedReason>,
}
impl Default for RuntimeStatus {
fn default() -> Self {
Self {
state: RuntimeState::Uninitialized,
user_id: None,
client_initialized: false,
legacy_auth: false,
corebridge_auth: false,
session_activated: false,
degraded_reason: None,
}
}
}
/// Typed runtime errors - no string matching in clients
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "code", content = "details")]
pub enum RuntimeError {
/// Runtime not initialized - call runtime_bootstrap first
RuntimeNotInitialized,
/// Authentication required for this operation
AuthRequired,
/// Per-user session not activated - call activate_user_session
UserSessionNotActivated,
/// CoreBridge/V2 auth missing - V2 commands won't work
CoreBridgeAuthMissing,
/// Runtime is in degraded state
RuntimeDegraded(DegradedReason),
/// Invalid user ID (e.g., 0)
InvalidUserId,
/// Bootstrap already in progress
BootstrapInProgress,
/// V2 CoreBridge authentication failed
V2AuthFailed(String),
/// V2 CoreBridge not initialized
V2NotInitialized,
/// Manual offline mode is on and the requested track is not available
/// in any local cache. Emitted instead of silently streaming from the
/// network (see issue #279).
TrackNotAvailableOffline,
/// Internal error
Internal(String),
}
impl std::fmt::Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RuntimeNotInitialized => write!(f, "Runtime not initialized"),
Self::AuthRequired => write!(f, "Authentication required"),
Self::UserSessionNotActivated => write!(f, "User session not activated"),
Self::CoreBridgeAuthMissing => write!(f, "CoreBridge authentication missing"),
Self::RuntimeDegraded(reason) => write!(f, "Runtime degraded: {:?}", reason),
Self::InvalidUserId => write!(f, "Invalid user ID"),
Self::BootstrapInProgress => write!(f, "Bootstrap already in progress"),
Self::V2AuthFailed(msg) => write!(f, "V2 authentication failed: {}", msg),
Self::V2NotInitialized => write!(f, "V2 CoreBridge not initialized"),
Self::TrackNotAvailableOffline => {
write!(f, "Track not available in offline cache while in offline mode")
}
Self::Internal(msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl std::error::Error for RuntimeError {}
/// Command prerequisites - what each command requires
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandRequirement {
/// No requirements (public endpoints)
None,
/// Requires client to be initialized (bundle tokens)
RequiresClientInit,
/// Requires authentication (logged in)
RequiresAuth,
/// Requires per-user session to be activated
RequiresUserSession,
/// Requires CoreBridge/V2 auth (for V2 commands)
RequiresCoreBridgeAuth,
}
/// Runtime state manager - thread-safe, holds canonical state
pub struct RuntimeManager {
state: Arc<RwLock<RuntimeStatus>>,
bootstrap_in_progress: Arc<RwLock<bool>>,
/// Tracks which Mixtape/Collection the current queue was built from.
/// Set by v2_enqueue_collection (replace mode), cleared by any
/// non-Mixtape queue replacement (set_queue / clear_queue).
/// Append-style ops (add/add_next/bulk) preserve this value.
/// Persistence is in-memory only; session_queue_state table will
/// add the source_collection_id column when it lands.
queue_source_collection_id: RwLock<Option<String>>,
}
impl RuntimeManager {
pub fn new() -> Self {
Self {
state: Arc::new(RwLock::new(RuntimeStatus::default())),
bootstrap_in_progress: Arc::new(RwLock::new(false)),
queue_source_collection_id: RwLock::new(None),
}
}
/// Set (or clear) which collection the current queue was built from.
pub async fn set_queue_source_collection(&self, id: Option<String>) {
*self.queue_source_collection_id.write().await = id;
}
/// Return the collection ID that seeded the current queue, if any.
pub async fn get_queue_source_collection(&self) -> Option<String> {
self.queue_source_collection_id.read().await.clone()
}
/// Get current runtime status
pub async fn get_status(&self) -> RuntimeStatus {
self.state.read().await.clone()
}
/// Update runtime state
pub async fn set_state(&self, new_state: RuntimeState) {
let mut status = self.state.write().await;
status.state = new_state.clone();
// Update derived fields based on state
match &new_state {
RuntimeState::Uninitialized => {
status.client_initialized = false;
status.legacy_auth = false;
status.corebridge_auth = false;
status.session_activated = false;
status.user_id = None;
status.degraded_reason = None;
}
RuntimeState::InitializedNoAuth => {
status.client_initialized = true;
status.legacy_auth = false;
status.corebridge_auth = false;
status.session_activated = false;
status.user_id = None;
status.degraded_reason = None;
}
RuntimeState::AuthenticatedNoUserSession { user_id } => {
status.client_initialized = true;
status.legacy_auth = true;
status.session_activated = false;
status.user_id = Some(*user_id);
status.degraded_reason = None;
}
RuntimeState::Ready { user_id } => {
status.client_initialized = true;
status.legacy_auth = true;
status.corebridge_auth = true;
status.session_activated = true;
status.user_id = Some(*user_id);
status.degraded_reason = None;
}
RuntimeState::Degraded { reason } => {
status.degraded_reason = Some(reason.clone());
}
}
log::info!("[Runtime] State changed to: {:?}", new_state);
}
/// Mark client as initialized
pub async fn set_client_initialized(&self, initialized: bool) {
let mut status = self.state.write().await;
status.client_initialized = initialized;
if initialized && status.state == RuntimeState::Uninitialized {
status.state = RuntimeState::InitializedNoAuth;
}
}
/// Mark legacy auth status
pub async fn set_legacy_auth(&self, auth: bool, user_id: Option<u64>) {
let mut status = self.state.write().await;
status.legacy_auth = auth;
if auth {
if let Some(uid) = user_id {
status.user_id = Some(uid);
if !status.session_activated {
status.state = RuntimeState::AuthenticatedNoUserSession { user_id: uid };
}
}
} else {
status.user_id = None;
status.corebridge_auth = false;
status.session_activated = false;
status.state = RuntimeState::InitializedNoAuth;
}
}
/// Mark CoreBridge auth status
pub async fn set_corebridge_auth(&self, auth: bool) {
let mut status = self.state.write().await;
status.corebridge_auth = auth;
}
/// Mark session as activated
pub async fn set_session_activated(&self, activated: bool, user_id: u64) {
let mut status = self.state.write().await;
status.session_activated = activated;
if activated && status.legacy_auth && status.corebridge_auth {
status.state = RuntimeState::Ready { user_id };
status.user_id = Some(user_id);
}
}
/// Check if bootstrap is in progress
pub async fn is_bootstrap_in_progress(&self) -> bool {
*self.bootstrap_in_progress.read().await
}
/// Set bootstrap in progress flag
pub async fn set_bootstrap_in_progress(&self, in_progress: bool) {
*self.bootstrap_in_progress.write().await = in_progress;
}
/// Validate command requirements against current state
pub async fn check_requirements(&self, req: CommandRequirement) -> Result<(), RuntimeError> {
let status = self.state.read().await;
match req {
CommandRequirement::None => Ok(()),
CommandRequirement::RequiresClientInit => {
if !status.client_initialized {
Err(RuntimeError::RuntimeNotInitialized)
} else {
Ok(())
}
}
CommandRequirement::RequiresAuth => {
if !status.client_initialized {
Err(RuntimeError::RuntimeNotInitialized)
} else if !status.legacy_auth {
Err(RuntimeError::AuthRequired)
} else {
Ok(())
}
}
CommandRequirement::RequiresUserSession => {
// RequiresUserSession only checks session_activated, not legacy_auth.
// This allows both:
// - Online sessions: session_activated=true via activate_session()
// - Offline sessions: session_activated=true via activate_offline_session()
//
// Commands needing Qobuz API should use RequiresAuth or RequiresCoreBridgeAuth.
if !status.session_activated {
Err(RuntimeError::UserSessionNotActivated)
} else {
Ok(())
}
}
CommandRequirement::RequiresCoreBridgeAuth => {
if !status.client_initialized {
Err(RuntimeError::RuntimeNotInitialized)
} else if !status.legacy_auth {
Err(RuntimeError::AuthRequired)
} else if !status.session_activated {
Err(RuntimeError::UserSessionNotActivated)
} else if !status.corebridge_auth {
Err(RuntimeError::CoreBridgeAuthMissing)
} else {
Ok(())
}
}
}
}
/// Check if in degraded state
pub async fn is_degraded(&self) -> bool {
matches!(self.state.read().await.state, RuntimeState::Degraded { .. })
}
/// Set degraded state
pub async fn set_degraded(&self, reason: DegradedReason) {
self.set_state(RuntimeState::Degraded { reason }).await;
}
}
impl Default for RuntimeManager {
fn default() -> Self {
Self::new()
}
}
/// Events emitted during runtime lifecycle
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event", content = "data")]
pub enum RuntimeEvent {
/// Runtime initialized (client ready)
RuntimeInitialized,
/// Authentication state changed
AuthChanged {
logged_in: bool,
user_id: Option<u64>,
},
/// Per-user session activated
UserSessionActivated { user_id: u64 },
/// Per-user session deactivated
UserSessionDeactivated,
/// CoreBridge auth failed
CoreBridgeAuthFailed { error: String },
/// Runtime entered degraded state
RuntimeDegraded { reason: DegradedReason },
/// Runtime fully ready
RuntimeReady { user_id: u64 },
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mixtape_context_set_clear() {
let mgr = RuntimeManager::new();
// Initially None
assert_eq!(mgr.get_queue_source_collection().await, None);
// Set Some
mgr.set_queue_source_collection(Some("col-abc".to_string())).await;
assert_eq!(
mgr.get_queue_source_collection().await,
Some("col-abc".to_string())
);
// Clear to None
mgr.set_queue_source_collection(None).await;
assert_eq!(mgr.get_queue_source_collection().await, None);
}
}
+5 -404
View File
@@ -1,360 +1,10 @@
//! Runtime Session Contract
//!
//! Implements the UI-agnostic runtime lifecycle as per ADR_RUNTIME_SESSION_CONTRACT.md
//!
//! Key concepts:
//! - Single bootstrap entrypoint
//! - Canonical state machine (Uninitialized -> Ready)
//! - Typed errors (no string matching)
//! - Command gating in backend
//! - Lifecycle events
//! Tauri runtime state wrapper.
use serde::{Deserialize, Serialize};
pub use qbz_app::runtime::{
CommandRequirement, DegradedReason, RuntimeError, RuntimeEvent, RuntimeManager, RuntimeState,
RuntimeStatus,
};
use std::sync::Arc;
use tokio::sync::RwLock;
/// Canonical runtime states
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "state", content = "data")]
pub enum RuntimeState {
/// Initial state - nothing initialized
Uninitialized,
/// Client initialized but no authentication
InitializedNoAuth,
/// Authenticated but per-user session not activated (transitional)
AuthenticatedNoUserSession { user_id: u64 },
/// Fully ready - all systems operational
Ready { user_id: u64 },
/// Degraded state - something is broken
Degraded { reason: DegradedReason },
}
impl Default for RuntimeState {
fn default() -> Self {
Self::Uninitialized
}
}
/// Reasons for degraded state
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "code", content = "message")]
pub enum DegradedReason {
/// Bundle token extraction failed
BundleExtractionFailed(String),
/// CoreBridge initialization failed
CoreBridgeInitFailed(String),
/// Network connectivity issues
NetworkError(String),
/// Database/storage issues
StorageError(String),
/// Session activation failed (per-user stores not initialized)
SessionActivationFailed(String),
}
/// Full runtime status returned by runtime_get_status and runtime_bootstrap
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeStatus {
/// Current state
pub state: RuntimeState,
/// User ID if authenticated (None if not logged in)
pub user_id: Option<u64>,
/// Whether the API client is initialized (bundle tokens extracted)
pub client_initialized: bool,
/// Whether legacy auth is active
pub legacy_auth: bool,
/// Whether CoreBridge/V2 auth is active
pub corebridge_auth: bool,
/// Whether per-user session is activated
pub session_activated: bool,
/// Degraded reason if state is Degraded
pub degraded_reason: Option<DegradedReason>,
}
impl Default for RuntimeStatus {
fn default() -> Self {
Self {
state: RuntimeState::Uninitialized,
user_id: None,
client_initialized: false,
legacy_auth: false,
corebridge_auth: false,
session_activated: false,
degraded_reason: None,
}
}
}
/// Typed runtime errors - no string matching in clients
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "code", content = "details")]
pub enum RuntimeError {
/// Runtime not initialized - call runtime_bootstrap first
RuntimeNotInitialized,
/// Authentication required for this operation
AuthRequired,
/// Per-user session not activated - call activate_user_session
UserSessionNotActivated,
/// CoreBridge/V2 auth missing - V2 commands won't work
CoreBridgeAuthMissing,
/// Runtime is in degraded state
RuntimeDegraded(DegradedReason),
/// Invalid user ID (e.g., 0)
InvalidUserId,
/// Bootstrap already in progress
BootstrapInProgress,
/// V2 CoreBridge authentication failed
V2AuthFailed(String),
/// V2 CoreBridge not initialized
V2NotInitialized,
/// Manual offline mode is on and the requested track is not available
/// in any local cache. Emitted instead of silently streaming from the
/// network (see issue #279).
TrackNotAvailableOffline,
/// Internal error
Internal(String),
}
impl std::fmt::Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RuntimeNotInitialized => write!(f, "Runtime not initialized"),
Self::AuthRequired => write!(f, "Authentication required"),
Self::UserSessionNotActivated => write!(f, "User session not activated"),
Self::CoreBridgeAuthMissing => write!(f, "CoreBridge authentication missing"),
Self::RuntimeDegraded(reason) => write!(f, "Runtime degraded: {:?}", reason),
Self::InvalidUserId => write!(f, "Invalid user ID"),
Self::BootstrapInProgress => write!(f, "Bootstrap already in progress"),
Self::V2AuthFailed(msg) => write!(f, "V2 authentication failed: {}", msg),
Self::V2NotInitialized => write!(f, "V2 CoreBridge not initialized"),
Self::TrackNotAvailableOffline => {
write!(f, "Track not available in offline cache while in offline mode")
}
Self::Internal(msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl std::error::Error for RuntimeError {}
/// Command prerequisites - what each command requires
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandRequirement {
/// No requirements (public endpoints)
None,
/// Requires client to be initialized (bundle tokens)
RequiresClientInit,
/// Requires authentication (logged in)
RequiresAuth,
/// Requires per-user session to be activated
RequiresUserSession,
/// Requires CoreBridge/V2 auth (for V2 commands)
RequiresCoreBridgeAuth,
}
/// Runtime state manager - thread-safe, holds canonical state
pub struct RuntimeManager {
state: Arc<RwLock<RuntimeStatus>>,
bootstrap_in_progress: Arc<RwLock<bool>>,
/// Tracks which Mixtape/Collection the current queue was built from.
/// Set by v2_enqueue_collection (replace mode), cleared by any
/// non-Mixtape queue replacement (set_queue / clear_queue).
/// Append-style ops (add/add_next/bulk) preserve this value.
/// Persistence is in-memory only; session_queue_state table will
/// add the source_collection_id column when it lands.
queue_source_collection_id: RwLock<Option<String>>,
}
impl RuntimeManager {
pub fn new() -> Self {
Self {
state: Arc::new(RwLock::new(RuntimeStatus::default())),
bootstrap_in_progress: Arc::new(RwLock::new(false)),
queue_source_collection_id: RwLock::new(None),
}
}
/// Set (or clear) which collection the current queue was built from.
pub async fn set_queue_source_collection(&self, id: Option<String>) {
*self.queue_source_collection_id.write().await = id;
}
/// Return the collection ID that seeded the current queue, if any.
pub async fn get_queue_source_collection(&self) -> Option<String> {
self.queue_source_collection_id.read().await.clone()
}
/// Get current runtime status
pub async fn get_status(&self) -> RuntimeStatus {
self.state.read().await.clone()
}
/// Update runtime state
pub async fn set_state(&self, new_state: RuntimeState) {
let mut status = self.state.write().await;
status.state = new_state.clone();
// Update derived fields based on state
match &new_state {
RuntimeState::Uninitialized => {
status.client_initialized = false;
status.legacy_auth = false;
status.corebridge_auth = false;
status.session_activated = false;
status.user_id = None;
status.degraded_reason = None;
}
RuntimeState::InitializedNoAuth => {
status.client_initialized = true;
status.legacy_auth = false;
status.corebridge_auth = false;
status.session_activated = false;
status.user_id = None;
status.degraded_reason = None;
}
RuntimeState::AuthenticatedNoUserSession { user_id } => {
status.client_initialized = true;
status.legacy_auth = true;
status.session_activated = false;
status.user_id = Some(*user_id);
status.degraded_reason = None;
}
RuntimeState::Ready { user_id } => {
status.client_initialized = true;
status.legacy_auth = true;
status.corebridge_auth = true;
status.session_activated = true;
status.user_id = Some(*user_id);
status.degraded_reason = None;
}
RuntimeState::Degraded { reason } => {
status.degraded_reason = Some(reason.clone());
}
}
log::info!("[Runtime] State changed to: {:?}", new_state);
}
/// Mark client as initialized
pub async fn set_client_initialized(&self, initialized: bool) {
let mut status = self.state.write().await;
status.client_initialized = initialized;
if initialized && status.state == RuntimeState::Uninitialized {
status.state = RuntimeState::InitializedNoAuth;
}
}
/// Mark legacy auth status
pub async fn set_legacy_auth(&self, auth: bool, user_id: Option<u64>) {
let mut status = self.state.write().await;
status.legacy_auth = auth;
if auth {
if let Some(uid) = user_id {
status.user_id = Some(uid);
if !status.session_activated {
status.state = RuntimeState::AuthenticatedNoUserSession { user_id: uid };
}
}
} else {
status.user_id = None;
status.corebridge_auth = false;
status.session_activated = false;
status.state = RuntimeState::InitializedNoAuth;
}
}
/// Mark CoreBridge auth status
pub async fn set_corebridge_auth(&self, auth: bool) {
let mut status = self.state.write().await;
status.corebridge_auth = auth;
}
/// Mark session as activated
pub async fn set_session_activated(&self, activated: bool, user_id: u64) {
let mut status = self.state.write().await;
status.session_activated = activated;
if activated && status.legacy_auth && status.corebridge_auth {
status.state = RuntimeState::Ready { user_id };
status.user_id = Some(user_id);
}
}
/// Check if bootstrap is in progress
pub async fn is_bootstrap_in_progress(&self) -> bool {
*self.bootstrap_in_progress.read().await
}
/// Set bootstrap in progress flag
pub async fn set_bootstrap_in_progress(&self, in_progress: bool) {
*self.bootstrap_in_progress.write().await = in_progress;
}
/// Validate command requirements against current state
pub async fn check_requirements(&self, req: CommandRequirement) -> Result<(), RuntimeError> {
let status = self.state.read().await;
match req {
CommandRequirement::None => Ok(()),
CommandRequirement::RequiresClientInit => {
if !status.client_initialized {
Err(RuntimeError::RuntimeNotInitialized)
} else {
Ok(())
}
}
CommandRequirement::RequiresAuth => {
if !status.client_initialized {
Err(RuntimeError::RuntimeNotInitialized)
} else if !status.legacy_auth {
Err(RuntimeError::AuthRequired)
} else {
Ok(())
}
}
CommandRequirement::RequiresUserSession => {
// RequiresUserSession only checks session_activated, not legacy_auth.
// This allows both:
// - Online sessions: session_activated=true via activate_session()
// - Offline sessions: session_activated=true via activate_offline_session()
//
// Commands needing Qobuz API should use RequiresAuth or RequiresCoreBridgeAuth.
if !status.session_activated {
Err(RuntimeError::UserSessionNotActivated)
} else {
Ok(())
}
}
CommandRequirement::RequiresCoreBridgeAuth => {
if !status.client_initialized {
Err(RuntimeError::RuntimeNotInitialized)
} else if !status.legacy_auth {
Err(RuntimeError::AuthRequired)
} else if !status.session_activated {
Err(RuntimeError::UserSessionNotActivated)
} else if !status.corebridge_auth {
Err(RuntimeError::CoreBridgeAuthMissing)
} else {
Ok(())
}
}
}
}
/// Check if in degraded state
pub async fn is_degraded(&self) -> bool {
matches!(self.state.read().await.state, RuntimeState::Degraded { .. })
}
/// Set degraded state
pub async fn set_degraded(&self, reason: DegradedReason) {
self.set_state(RuntimeState::Degraded { reason }).await;
}
}
impl Default for RuntimeManager {
fn default() -> Self {
Self::new()
}
}
/// Tauri state wrapper
pub struct RuntimeManagerState(pub Arc<RuntimeManager>);
@@ -374,52 +24,3 @@ impl Default for RuntimeManagerState {
Self::new()
}
}
// ==================== Lifecycle Events ====================
/// Events emitted during runtime lifecycle
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event", content = "data")]
pub enum RuntimeEvent {
/// Runtime initialized (client ready)
RuntimeInitialized,
/// Authentication state changed
AuthChanged {
logged_in: bool,
user_id: Option<u64>,
},
/// Per-user session activated
UserSessionActivated { user_id: u64 },
/// Per-user session deactivated
UserSessionDeactivated,
/// CoreBridge auth failed
CoreBridgeAuthFailed { error: String },
/// Runtime entered degraded state
RuntimeDegraded { reason: DegradedReason },
/// Runtime fully ready
RuntimeReady { user_id: u64 },
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mixtape_context_set_clear() {
let mgr = RuntimeManager::new();
// Initially None
assert_eq!(mgr.get_queue_source_collection().await, None);
// Set Some
mgr.set_queue_source_collection(Some("col-abc".to_string())).await;
assert_eq!(
mgr.get_queue_source_collection().await,
Some("col-abc".to_string())
);
// Clear to None
mgr.set_queue_source_collection(None).await;
assert_eq!(mgr.get_queue_source_collection().await, None);
}
}