mockforge_http/handlers/
consistency.rs

1//! Consistency engine API handlers
2//!
3//! This module provides HTTP handlers for managing unified state across protocols.
4
5use axum::{
6    extract::{Path, Query, State},
7    http::StatusCode,
8    response::Json,
9};
10// ChaosScenario is now serde_json::Value to avoid circular dependency
11use mockforge_core::consistency::{
12    enrich_order_response, enrich_user_response, get_user_orders_via_graph, ConsistencyEngine,
13    EntityState, UnifiedState,
14};
15use mockforge_core::reality::RealityLevel;
16use mockforge_data::{LifecyclePreset, LifecycleState, PersonaLifecycle, PersonaProfile};
17use serde::Deserialize;
18use serde_json::Value as JsonValue;
19use serde_json::Value;
20use std::sync::Arc;
21use tracing::{error, info};
22
23/// State for consistency handlers
24#[derive(Clone)]
25pub struct ConsistencyState {
26    /// Consistency engine
27    pub engine: Arc<ConsistencyEngine>,
28}
29
30/// Request to set active persona
31#[derive(Debug, Deserialize)]
32pub struct SetPersonaRequest {
33    /// Persona profile
34    pub persona: PersonaProfile,
35}
36
37/// Request to set active scenario
38#[derive(Debug, Deserialize)]
39pub struct SetScenarioRequest {
40    /// Scenario ID
41    pub scenario_id: String,
42}
43
44/// Request to set reality level
45#[derive(Debug, Deserialize)]
46pub struct SetRealityLevelRequest {
47    /// Reality level (1-5)
48    pub level: u8,
49}
50
51/// Request to set reality ratio
52#[derive(Debug, Deserialize)]
53pub struct SetRealityRatioRequest {
54    /// Reality ratio (0.0-1.0)
55    pub ratio: f64,
56}
57
58/// Request to register an entity
59#[derive(Debug, Deserialize)]
60pub struct RegisterEntityRequest {
61    /// Entity type
62    pub entity_type: String,
63    /// Entity ID
64    pub entity_id: String,
65    /// Entity data (JSON)
66    pub data: Value,
67    /// Optional persona ID
68    pub persona_id: Option<String>,
69}
70
71/// Request to activate chaos rule
72#[derive(Debug, Deserialize)]
73pub struct ActivateChaosRuleRequest {
74    /// Chaos scenario
75    pub rule: JsonValue, // ChaosScenario as JSON value
76}
77
78/// Request to deactivate chaos rule
79#[derive(Debug, Deserialize)]
80pub struct DeactivateChaosRuleRequest {
81    /// Rule name
82    pub rule_name: String,
83}
84
85/// Request to set persona lifecycle state
86#[derive(Debug, Deserialize)]
87pub struct SetPersonaLifecycleRequest {
88    /// Persona ID
89    pub persona_id: String,
90    /// Initial lifecycle state
91    pub initial_state: String,
92}
93
94/// Request to apply a lifecycle preset to a persona
95#[derive(Debug, Deserialize)]
96pub struct ApplyLifecyclePresetRequest {
97    /// Persona ID
98    pub persona_id: String,
99    /// Lifecycle preset name (e.g., "subscription", "loan", "order_fulfillment", "user_engagement")
100    pub preset: String,
101}
102
103/// Query parameters for workspace operations
104#[derive(Debug, Deserialize)]
105pub struct WorkspaceQuery {
106    /// Workspace ID (defaults to "default" if not provided)
107    #[serde(default = "default_workspace")]
108    pub workspace: String,
109}
110
111fn default_workspace() -> String {
112    "default".to_string()
113}
114
115/// Get unified state for a workspace
116///
117/// GET /api/v1/consistency/state?workspace={workspace_id}
118pub async fn get_state(
119    State(state): State<ConsistencyState>,
120    Query(params): Query<WorkspaceQuery>,
121) -> Result<Json<UnifiedState>, StatusCode> {
122    let unified_state = state.engine.get_state(&params.workspace).await.ok_or_else(|| {
123        error!("State not found for workspace: {}", params.workspace);
124        StatusCode::NOT_FOUND
125    })?;
126
127    Ok(Json(unified_state))
128}
129
130/// Set active persona for a workspace
131///
132/// POST /api/v1/consistency/persona?workspace={workspace_id}
133pub async fn set_persona(
134    State(state): State<ConsistencyState>,
135    Query(params): Query<WorkspaceQuery>,
136    Json(request): Json<SetPersonaRequest>,
137) -> Result<Json<Value>, StatusCode> {
138    // Clone persona_id before moving request.persona
139    let persona_id = request.persona.id.clone();
140
141    state
142        .engine
143        .set_active_persona(&params.workspace, request.persona)
144        .await
145        .map_err(|e| {
146            error!("Failed to set persona: {}", e);
147            StatusCode::INTERNAL_SERVER_ERROR
148        })?;
149
150    // Record pillar usage for persona activation
151    mockforge_core::pillar_tracking::record_reality_usage(
152        Some(params.workspace.clone()),
153        None,
154        "smart_personas_usage",
155        serde_json::json!({
156            "persona_id": persona_id,
157            "action": "activated"
158        }),
159    )
160    .await;
161
162    info!("Set persona for workspace: {}", params.workspace);
163    Ok(Json(serde_json::json!({
164        "success": true,
165        "workspace": params.workspace,
166    })))
167}
168
169/// Set active scenario for a workspace
170///
171/// POST /api/v1/consistency/scenario?workspace={workspace_id}
172pub async fn set_scenario(
173    State(state): State<ConsistencyState>,
174    Query(params): Query<WorkspaceQuery>,
175    Json(request): Json<SetScenarioRequest>,
176) -> Result<Json<Value>, StatusCode> {
177    state
178        .engine
179        .set_active_scenario(&params.workspace, request.scenario_id)
180        .await
181        .map_err(|e| {
182            error!("Failed to set scenario: {}", e);
183            StatusCode::INTERNAL_SERVER_ERROR
184        })?;
185
186    info!("Set scenario for workspace: {}", params.workspace);
187    Ok(Json(serde_json::json!({
188        "success": true,
189        "workspace": params.workspace,
190    })))
191}
192
193/// Set reality level for a workspace
194///
195/// POST /api/v1/consistency/reality-level?workspace={workspace_id}
196pub async fn set_reality_level(
197    State(state): State<ConsistencyState>,
198    Query(params): Query<WorkspaceQuery>,
199    Json(request): Json<SetRealityLevelRequest>,
200) -> Result<Json<Value>, StatusCode> {
201    let level = RealityLevel::from_value(request.level).ok_or_else(|| {
202        error!("Invalid reality level: {}", request.level);
203        StatusCode::BAD_REQUEST
204    })?;
205
206    state.engine.set_reality_level(&params.workspace, level).await.map_err(|e| {
207        error!("Failed to set reality level: {}", e);
208        StatusCode::INTERNAL_SERVER_ERROR
209    })?;
210
211    info!("Set reality level for workspace: {}", params.workspace);
212    Ok(Json(serde_json::json!({
213        "success": true,
214        "workspace": params.workspace,
215        "level": request.level,
216    })))
217}
218
219/// Set reality continuum ratio for a workspace
220///
221/// POST /api/v1/consistency/reality-ratio?workspace={workspace_id}
222pub async fn set_reality_ratio(
223    State(state): State<ConsistencyState>,
224    Query(params): Query<WorkspaceQuery>,
225    Json(request): Json<SetRealityRatioRequest>,
226) -> Result<Json<Value>, StatusCode> {
227    if !(0.0..=1.0).contains(&request.ratio) {
228        return Err(StatusCode::BAD_REQUEST);
229    }
230
231    state
232        .engine
233        .set_reality_ratio(&params.workspace, request.ratio)
234        .await
235        .map_err(|e| {
236            error!("Failed to set reality ratio: {}", e);
237            StatusCode::INTERNAL_SERVER_ERROR
238        })?;
239
240    info!("Set reality ratio for workspace: {}", params.workspace);
241    Ok(Json(serde_json::json!({
242        "success": true,
243        "workspace": params.workspace,
244        "ratio": request.ratio,
245    })))
246}
247
248/// Register or update an entity
249///
250/// POST /api/v1/consistency/entities?workspace={workspace_id}
251pub async fn register_entity(
252    State(state): State<ConsistencyState>,
253    Query(params): Query<WorkspaceQuery>,
254    Json(request): Json<RegisterEntityRequest>,
255) -> Result<Json<Value>, StatusCode> {
256    let mut entity = EntityState::new(request.entity_type, request.entity_id, request.data);
257    if let Some(persona_id) = request.persona_id {
258        entity.persona_id = Some(persona_id);
259    }
260
261    state
262        .engine
263        .register_entity(&params.workspace, entity.clone())
264        .await
265        .map_err(|e| {
266            error!("Failed to register entity: {}", e);
267            StatusCode::INTERNAL_SERVER_ERROR
268        })?;
269
270    info!(
271        "Registered entity {}:{} for workspace: {}",
272        entity.entity_type, entity.entity_id, params.workspace
273    );
274    Ok(Json(serde_json::json!({
275        "success": true,
276        "workspace": params.workspace,
277        "entity": entity,
278    })))
279}
280
281/// Get entity by type and ID
282///
283/// GET /api/v1/consistency/entities/{entity_type}/{entity_id}?workspace={workspace_id}
284pub async fn get_entity(
285    State(state): State<ConsistencyState>,
286    Path((entity_type, entity_id)): Path<(String, String)>,
287    Query(params): Query<WorkspaceQuery>,
288) -> Result<Json<EntityState>, StatusCode> {
289    let entity = state
290        .engine
291        .get_entity(&params.workspace, &entity_type, &entity_id)
292        .await
293        .ok_or_else(|| {
294            error!(
295                "Entity not found: {}:{} in workspace: {}",
296                entity_type, entity_id, params.workspace
297            );
298            StatusCode::NOT_FOUND
299        })?;
300
301    Ok(Json(entity))
302}
303
304/// List all entities for a workspace
305///
306/// GET /api/v1/consistency/entities?workspace={workspace_id}
307pub async fn list_entities(
308    State(state): State<ConsistencyState>,
309    Query(params): Query<WorkspaceQuery>,
310) -> Result<Json<Value>, StatusCode> {
311    let unified_state = state.engine.get_state(&params.workspace).await.ok_or_else(|| {
312        error!("State not found for workspace: {}", params.workspace);
313        StatusCode::NOT_FOUND
314    })?;
315
316    let entities: Vec<&EntityState> = unified_state.entity_state.values().collect();
317    Ok(Json(serde_json::json!({
318        "workspace": params.workspace,
319        "entities": entities,
320        "count": entities.len(),
321    })))
322}
323
324/// Activate a chaos rule
325///
326/// POST /api/v1/consistency/chaos/activate?workspace={workspace_id}
327pub async fn activate_chaos_rule(
328    State(state): State<ConsistencyState>,
329    Query(params): Query<WorkspaceQuery>,
330    Json(request): Json<ActivateChaosRuleRequest>,
331) -> Result<Json<Value>, StatusCode> {
332    state
333        .engine
334        .activate_chaos_rule(&params.workspace, request.rule)
335        .await
336        .map_err(|e| {
337            error!("Failed to activate chaos rule: {}", e);
338            StatusCode::INTERNAL_SERVER_ERROR
339        })?;
340
341    info!("Activated chaos rule for workspace: {}", params.workspace);
342    Ok(Json(serde_json::json!({
343        "success": true,
344        "workspace": params.workspace,
345    })))
346}
347
348/// Deactivate a chaos rule
349///
350/// POST /api/v1/consistency/chaos/deactivate?workspace={workspace_id}
351pub async fn deactivate_chaos_rule(
352    State(state): State<ConsistencyState>,
353    Query(params): Query<WorkspaceQuery>,
354    Json(request): Json<DeactivateChaosRuleRequest>,
355) -> Result<Json<Value>, StatusCode> {
356    state
357        .engine
358        .deactivate_chaos_rule(&params.workspace, &request.rule_name)
359        .await
360        .map_err(|e| {
361            error!("Failed to deactivate chaos rule: {}", e);
362            StatusCode::INTERNAL_SERVER_ERROR
363        })?;
364
365    info!("Deactivated chaos rule for workspace: {}", params.workspace);
366    Ok(Json(serde_json::json!({
367        "success": true,
368        "workspace": params.workspace,
369        "rule_name": request.rule_name,
370    })))
371}
372
373/// Set persona lifecycle state
374///
375/// POST /api/v1/consistency/persona/lifecycle?workspace={workspace_id}
376pub async fn set_persona_lifecycle(
377    State(state): State<ConsistencyState>,
378    Query(params): Query<WorkspaceQuery>,
379    Json(request): Json<SetPersonaLifecycleRequest>,
380) -> Result<Json<Value>, StatusCode> {
381    // Parse lifecycle state
382    let lifecycle_state = match request.initial_state.to_lowercase().as_str() {
383        "new" | "new_signup" => LifecycleState::NewSignup,
384        "active" => LifecycleState::Active,
385        "power_user" | "poweruser" => LifecycleState::PowerUser,
386        "churn_risk" | "churnrisk" => LifecycleState::ChurnRisk,
387        "churned" => LifecycleState::Churned,
388        "upgrade_pending" | "upgradepending" => LifecycleState::UpgradePending,
389        "payment_failed" | "paymentfailed" => LifecycleState::PaymentFailed,
390        _ => {
391            error!("Invalid lifecycle state: {}", request.initial_state);
392            return Err(StatusCode::BAD_REQUEST);
393        }
394    };
395
396    // Get unified state to access active persona
397    let unified_state = state.engine.get_state(&params.workspace).await.ok_or_else(|| {
398        error!("State not found for workspace: {}", params.workspace);
399        StatusCode::NOT_FOUND
400    })?;
401
402    // Update persona lifecycle if active persona matches
403    if let Some(ref persona) = unified_state.active_persona {
404        if persona.id == request.persona_id {
405            let mut persona_mut = persona.clone();
406            let lifecycle = PersonaLifecycle::new(request.persona_id.clone(), lifecycle_state);
407            persona_mut.set_lifecycle(lifecycle);
408
409            // Apply lifecycle effects to persona traits
410            if let Some(ref lifecycle) = persona_mut.lifecycle {
411                let effects = lifecycle.apply_lifecycle_effects();
412                for (key, value) in effects {
413                    persona_mut.set_trait(key, value);
414                }
415            }
416
417            // Update the persona in the engine
418            state
419                .engine
420                .set_active_persona(&params.workspace, persona_mut)
421                .await
422                .map_err(|e| {
423                    error!("Failed to set persona lifecycle: {}", e);
424                    StatusCode::INTERNAL_SERVER_ERROR
425                })?;
426
427            info!(
428                "Set lifecycle state {} for persona {} in workspace: {}",
429                request.initial_state, request.persona_id, params.workspace
430            );
431
432            return Ok(Json(serde_json::json!({
433                "success": true,
434                "workspace": params.workspace,
435                "persona_id": request.persona_id,
436                "lifecycle_state": request.initial_state,
437            })));
438        }
439    }
440
441    error!(
442        "Persona {} not found or not active in workspace: {}",
443        request.persona_id, params.workspace
444    );
445    Err(StatusCode::NOT_FOUND)
446}
447
448/// Get user by ID with persona graph enrichment
449///
450/// GET /api/v1/consistency/users/{id}?workspace={workspace_id}
451/// This endpoint uses the persona graph to enrich the user response with related entities.
452pub async fn get_user_with_graph(
453    State(state): State<ConsistencyState>,
454    Path(user_id): Path<String>,
455    Query(params): Query<WorkspaceQuery>,
456) -> Result<Json<Value>, StatusCode> {
457    // Get user entity
458    let user_entity = state
459        .engine
460        .get_entity(&params.workspace, "user", &user_id)
461        .await
462        .ok_or_else(|| {
463            error!("User not found: {} in workspace: {}", user_id, params.workspace);
464            StatusCode::NOT_FOUND
465        })?;
466
467    // Enrich response with persona graph data
468    let mut response = user_entity.data.clone();
469    enrich_user_response(&state.engine, &params.workspace, &user_id, &mut response).await;
470
471    Ok(Json(response))
472}
473
474/// Get orders for a user using persona graph
475///
476/// GET /api/v1/consistency/users/{id}/orders?workspace={workspace_id}
477/// This endpoint uses the persona graph to find all orders related to the user.
478pub async fn get_user_orders_with_graph(
479    State(state): State<ConsistencyState>,
480    Path(user_id): Path<String>,
481    Query(params): Query<WorkspaceQuery>,
482) -> Result<Json<Value>, StatusCode> {
483    // Verify user exists
484    state
485        .engine
486        .get_entity(&params.workspace, "user", &user_id)
487        .await
488        .ok_or_else(|| {
489            error!("User not found: {} in workspace: {}", user_id, params.workspace);
490            StatusCode::NOT_FOUND
491        })?;
492
493    // Get orders via persona graph
494    let orders = get_user_orders_via_graph(&state.engine, &params.workspace, &user_id).await;
495
496    // Convert to JSON response
497    let orders_json: Vec<Value> = orders.iter().map(|e| e.data.clone()).collect();
498
499    Ok(Json(serde_json::json!({
500        "user_id": user_id,
501        "orders": orders_json,
502        "count": orders_json.len(),
503    })))
504}
505
506/// Get order by ID with persona graph enrichment
507///
508/// GET /api/v1/consistency/orders/{id}?workspace={workspace_id}
509/// This endpoint uses the persona graph to enrich the order response with related entities.
510pub async fn get_order_with_graph(
511    State(state): State<ConsistencyState>,
512    Path(order_id): Path<String>,
513    Query(params): Query<WorkspaceQuery>,
514) -> Result<Json<Value>, StatusCode> {
515    // Get order entity
516    let order_entity = state
517        .engine
518        .get_entity(&params.workspace, "order", &order_id)
519        .await
520        .ok_or_else(|| {
521            error!("Order not found: {} in workspace: {}", order_id, params.workspace);
522            StatusCode::NOT_FOUND
523        })?;
524
525    // Enrich response with persona graph data
526    let mut response = order_entity.data.clone();
527    enrich_order_response(&state.engine, &params.workspace, &order_id, &mut response).await;
528
529    Ok(Json(response))
530}
531
532/// Update persona lifecycle states based on current virtual time
533///
534/// POST /api/v1/consistency/persona/update-lifecycles?workspace={workspace_id}
535/// This endpoint checks all active personas and updates their lifecycle states
536/// based on elapsed time since state entry, using virtual time if time travel is enabled.
537pub async fn update_persona_lifecycles(
538    State(state): State<ConsistencyState>,
539    Query(params): Query<WorkspaceQuery>,
540) -> Result<Json<Value>, StatusCode> {
541    use mockforge_core::time_travel::now as get_virtual_time;
542
543    // Get unified state
544    let mut unified_state = state.engine.get_state(&params.workspace).await.ok_or_else(|| {
545        error!("State not found for workspace: {}", params.workspace);
546        StatusCode::NOT_FOUND
547    })?;
548
549    // Get current time (virtual if time travel is enabled, real otherwise)
550    let current_time = get_virtual_time();
551
552    // Update lifecycle state for active persona if present
553    let mut updated = false;
554    if let Some(ref mut persona) = unified_state.active_persona {
555        let old_state = persona
556            .lifecycle
557            .as_ref()
558            .map(|l| l.current_state)
559            .unwrap_or(mockforge_data::LifecycleState::Active);
560
561        // Update lifecycle state based on elapsed time
562        persona.update_lifecycle_state(current_time);
563
564        let new_state = persona
565            .lifecycle
566            .as_ref()
567            .map(|l| l.current_state)
568            .unwrap_or(mockforge_data::LifecycleState::Active);
569
570        if old_state != new_state {
571            updated = true;
572            info!(
573                "Persona {} lifecycle state updated: {:?} -> {:?}",
574                persona.id, old_state, new_state
575            );
576
577            // Update the persona in the engine
578            state
579                .engine
580                .set_active_persona(&params.workspace, persona.clone())
581                .await
582                .map_err(|e| {
583                    error!("Failed to update persona lifecycle: {}", e);
584                    StatusCode::INTERNAL_SERVER_ERROR
585                })?;
586        }
587    }
588
589    Ok(Json(serde_json::json!({
590        "success": true,
591        "workspace": params.workspace,
592        "updated": updated,
593        "current_time": current_time.to_rfc3339(),
594    })))
595}
596
597/// List all available lifecycle presets
598///
599/// GET /api/v1/consistency/lifecycle-presets
600pub async fn list_lifecycle_presets() -> Json<Value> {
601    let presets: Vec<serde_json::Value> = LifecyclePreset::all()
602        .iter()
603        .map(|preset| {
604            serde_json::json!({
605                "name": preset.name(),
606                "id": format!("{:?}", preset).to_lowercase(),
607                "description": preset.description(),
608            })
609        })
610        .collect();
611
612    Json(serde_json::json!({
613        "presets": presets,
614    }))
615}
616
617/// Get details of a specific lifecycle preset
618///
619/// GET /api/v1/consistency/lifecycle-presets/{preset_name}
620pub async fn get_lifecycle_preset_details(
621    Path(preset_name): Path<String>,
622) -> Result<Json<Value>, StatusCode> {
623    // Parse preset name
624    let preset = match preset_name.to_lowercase().as_str() {
625        "subscription" => LifecyclePreset::Subscription,
626        "loan" => LifecyclePreset::Loan,
627        "order_fulfillment" | "orderfulfillment" => LifecyclePreset::OrderFulfillment,
628        "user_engagement" | "userengagement" => LifecyclePreset::UserEngagement,
629        _ => {
630            error!("Unknown lifecycle preset: {}", preset_name);
631            return Err(StatusCode::NOT_FOUND);
632        }
633    };
634
635    // Create a sample lifecycle to get the structure
636    let sample_lifecycle = PersonaLifecycle::from_preset(preset, "sample".to_string());
637
638    // Build response with preset details
639    let response = serde_json::json!({
640        "preset": {
641            "name": preset.name(),
642            "id": format!("{:?}", preset).to_lowercase(),
643            "description": preset.description(),
644        },
645        "initial_state": format!("{:?}", sample_lifecycle.current_state),
646        "states": sample_lifecycle
647            .transition_rules
648            .iter()
649            .map(|rule| {
650                serde_json::json!({
651                    "from": format!("{:?}", sample_lifecycle.current_state),
652                    "to": format!("{:?}", rule.to),
653                    "after_days": rule.after_days,
654                    "condition": rule.condition,
655                })
656            })
657            .collect::<Vec<_>>(),
658        "affected_endpoints": match preset {
659            LifecyclePreset::Subscription => vec!["billing", "support", "subscription"],
660            LifecyclePreset::Loan => vec!["loan", "loans", "credit", "application"],
661            LifecyclePreset::OrderFulfillment => vec!["order", "orders", "fulfillment", "shipment", "delivery"],
662            LifecyclePreset::UserEngagement => vec!["profile", "user", "users", "activity", "engagement", "notifications"],
663        },
664    });
665
666    Ok(Json(response))
667}
668
669/// Apply a lifecycle preset to a persona
670///
671/// POST /api/v1/consistency/lifecycle-presets/apply?workspace={workspace_id}
672pub async fn apply_lifecycle_preset(
673    State(state): State<ConsistencyState>,
674    Query(params): Query<WorkspaceQuery>,
675    Json(request): Json<ApplyLifecyclePresetRequest>,
676) -> Result<Json<Value>, StatusCode> {
677    // Parse preset name
678    let preset = match request.preset.to_lowercase().as_str() {
679        "subscription" => LifecyclePreset::Subscription,
680        "loan" => LifecyclePreset::Loan,
681        "order_fulfillment" | "orderfulfillment" => LifecyclePreset::OrderFulfillment,
682        "user_engagement" | "userengagement" => LifecyclePreset::UserEngagement,
683        _ => {
684            error!("Unknown lifecycle preset: {}", request.preset);
685            return Err(StatusCode::BAD_REQUEST);
686        }
687    };
688
689    // Get unified state
690    let mut unified_state = state.engine.get_state(&params.workspace).await.ok_or_else(|| {
691        error!("State not found for workspace: {}", params.workspace);
692        StatusCode::NOT_FOUND
693    })?;
694
695    // Check if persona exists and is active
696    if let Some(ref mut persona) = unified_state.active_persona {
697        if persona.id != request.persona_id {
698            error!(
699                "Persona {} not found or not active in workspace: {}",
700                request.persona_id, params.workspace
701            );
702            return Err(StatusCode::NOT_FOUND);
703        }
704
705        // Create lifecycle from preset
706        let lifecycle = PersonaLifecycle::from_preset(preset, request.persona_id.clone());
707
708        // Apply lifecycle to persona
709        persona.set_lifecycle(lifecycle.clone());
710
711        // Apply lifecycle effects to persona traits
712        let effects = lifecycle.apply_lifecycle_effects();
713        for (key, value) in effects {
714            persona.set_trait(key, value);
715        }
716
717        // Update the persona in the engine
718        state
719            .engine
720            .set_active_persona(&params.workspace, persona.clone())
721            .await
722            .map_err(|e| {
723                error!("Failed to apply lifecycle preset: {}", e);
724                StatusCode::INTERNAL_SERVER_ERROR
725            })?;
726
727        info!(
728            "Applied lifecycle preset {:?} to persona {} in workspace: {}",
729            preset, request.persona_id, params.workspace
730        );
731
732        return Ok(Json(serde_json::json!({
733            "success": true,
734            "workspace": params.workspace,
735            "persona_id": request.persona_id,
736            "preset": preset.name(),
737            "lifecycle_state": format!("{:?}", lifecycle.current_state),
738        })));
739    }
740
741    error!("No active persona found in workspace: {}", params.workspace);
742    Err(StatusCode::NOT_FOUND)
743}
744
745/// Create consistency router
746pub fn consistency_router(state: ConsistencyState) -> axum::Router {
747    use axum::routing::{get, post};
748
749    axum::Router::new()
750        // State management
751        .route("/api/v1/consistency/state", get(get_state))
752        // Persona management
753        .route("/api/v1/consistency/persona", post(set_persona))
754        .route("/api/v1/consistency/persona/lifecycle", post(set_persona_lifecycle))
755        .route("/api/v1/consistency/persona/update-lifecycles", post(update_persona_lifecycles))
756        // Lifecycle preset management
757        .route("/api/v1/consistency/lifecycle-presets", get(list_lifecycle_presets))
758        .route("/api/v1/consistency/lifecycle-presets/{preset_name}", get(get_lifecycle_preset_details))
759        .route("/api/v1/consistency/lifecycle-presets/apply", post(apply_lifecycle_preset))
760        // Scenario management
761        .route("/api/v1/consistency/scenario", post(set_scenario))
762        // Reality level management
763        .route("/api/v1/consistency/reality-level", post(set_reality_level))
764        // Reality ratio management
765        .route("/api/v1/consistency/reality-ratio", post(set_reality_ratio))
766        // Entity management
767        .route("/api/v1/consistency/entities", get(list_entities).post(register_entity))
768        .route(
769            "/api/v1/consistency/entities/{entity_type}/{entity_id}",
770            get(get_entity),
771        )
772        // Persona graph-enabled endpoints
773        .route("/api/v1/consistency/users/{id}", get(get_user_with_graph))
774        .route("/api/v1/consistency/users/{id}/orders", get(get_user_orders_with_graph))
775        .route("/api/v1/consistency/orders/{id}", get(get_order_with_graph))
776        // Chaos rule management
777        .route("/api/v1/consistency/chaos/activate", post(activate_chaos_rule))
778        .route("/api/v1/consistency/chaos/deactivate", post(deactivate_chaos_rule))
779        .with_state(state)
780}