Skip to main content

mockforge_http/
lib.rs

1// TODO: remove once mockforge-intelligence, mockforge-proxy, etc. crates are extracted
2// Deprecation allowances are now scoped to individual handler files
3// (handlers/*.rs, middleware/drift_tracking.rs, rag_ai_generator.rs,
4// management/ai_gen.rs) and to specific build_router functions below,
5// rather than being crate-wide.
6
7//! # MockForge HTTP
8//!
9//! HTTP/REST API mocking library for MockForge.
10//!
11//! This crate provides HTTP-specific functionality for creating mock REST APIs,
12//! including OpenAPI integration, request validation, AI-powered response generation,
13//! and management endpoints.
14//!
15//! ## Overview
16//!
17//! MockForge HTTP enables you to:
18//!
19//! - **Serve OpenAPI specs**: Automatically generate mock endpoints from OpenAPI/Swagger
20//! - **Validate requests**: Enforce schema validation with configurable modes
21//! - **AI-powered responses**: Generate intelligent responses using LLMs
22//! - **Management API**: Real-time monitoring, configuration, and control
23//! - **Request logging**: Comprehensive HTTP request/response logging
24//! - **Metrics collection**: Track performance and usage statistics
25//! - **Server-Sent Events**: Stream logs and metrics to clients
26//!
27//! ## Quick Start
28//!
29//! ### Basic HTTP Server from OpenAPI
30//!
31//! ```rust,no_run
32//! use axum::Router;
33//! use mockforge_openapi::openapi_routes::ValidationMode;
34//! use mockforge_openapi::openapi_routes::ValidationOptions;
35//! use mockforge_http::build_router;
36//!
37//! #[tokio::main]
38//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
39//!     // Build router from OpenAPI specification
40//!     let router = build_router(
41//!         Some("./api-spec.json".to_string()),
42//!         Some(ValidationOptions {
43//!             request_mode: ValidationMode::Enforce,
44//!             ..ValidationOptions::default()
45//!         }),
46//!         None,
47//!     ).await;
48//!
49//!     // Start the server
50//!     let addr: std::net::SocketAddr = "0.0.0.0:3000".parse()?;
51//!     let listener = tokio::net::TcpListener::bind(addr).await?;
52//!     axum::serve(listener, router).await?;
53//!
54//!     Ok(())
55//! }
56//! ```
57//!
58//! ### With Management API
59//!
60//! Enable real-time monitoring and configuration:
61//!
62//! ```rust,no_run
63//! use mockforge_http::{management_router, ManagementState};
64//!
65//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
66//! let state = ManagementState::new(None, None, 3000);
67//!
68//! // Build management router
69//! let mgmt_router = management_router(state);
70//!
71//! // Mount under your main router
72//! let app = axum::Router::new()
73//!     .nest("/__mockforge", mgmt_router);
74//! # Ok(())
75//! # }
76//! ```
77//!
78//! ### AI-Powered Responses
79//!
80//! Generate intelligent responses based on request context:
81//!
82//! ```rust,no_run
83//! use mockforge_data::intelligent_mock::{IntelligentMockConfig, ResponseMode};
84//! use mockforge_http::{process_response_with_ai, AiResponseConfig};
85//! use serde_json::json;
86//!
87//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
88//! let ai_config = AiResponseConfig {
89//!     intelligent: Some(
90//!         IntelligentMockConfig::new(ResponseMode::Intelligent)
91//!             .with_prompt("Generate realistic user data".to_string()),
92//!     ),
93//!     drift: None,
94//! };
95//!
96//! let response = process_response_with_ai(
97//!     Some(json!({"name": "Alice"})),
98//!     ai_config
99//!         .intelligent
100//!         .clone()
101//!         .map(serde_json::to_value)
102//!         .transpose()?,
103//!     ai_config
104//!         .drift
105//!         .clone()
106//!         .map(serde_json::to_value)
107//!         .transpose()?,
108//! )
109//! .await?;
110//! # Ok(())
111//! # }
112//! ```
113//!
114//! ## Key Features
115//!
116//! ### OpenAPI Integration
117//! - Automatic endpoint generation from specs
118//! - Request/response validation
119//! - Schema-based mock data generation
120//!
121//! ### Management & Monitoring
122//! - [`management`]: REST API for server control and monitoring
123//! - [`management_ws`]: WebSocket API for real-time updates
124//! - [`sse`]: Server-Sent Events for log streaming
125//! - [`request_logging`]: Comprehensive request/response logging
126//! - [`metrics_middleware`]: Performance metrics collection
127//!
128//! ### Advanced Features
129//! - [`ai_handler`]: AI-powered response generation
130//! - [`auth`]: Authentication and authorization
131//! - [`chain_handlers`]: Multi-step request workflows
132//! - [`latency_profiles`]: Configurable latency simulation
133//! - [`replay_listing`]: Fixture management
134//!
135//! ## Middleware
136//!
137//! MockForge HTTP includes several middleware layers:
138//!
139//! - **Request Tracing**: [`http_tracing_middleware`] - Distributed tracing integration
140//! - **Metrics Collection**: [`metrics_middleware`] - Prometheus-compatible metrics
141//! - **Operation Metadata**: [`op_middleware`] - OpenAPI operation tracking
142//!
143//! ## Management API Endpoints
144//!
145//! When using the management router, these endpoints are available:
146//!
147//! - `GET /health` - Health check
148//! - `GET /stats` - Server statistics
149//! - `GET /logs` - Request logs (SSE stream)
150//! - `GET /metrics` - Performance metrics
151//! - `GET /fixtures` - List available fixtures
152//! - `POST /config/*` - Update configuration
153//!
154//! ## Examples
155//!
156//! See the [examples directory](https://github.com/SaaSy-Solutions/mockforge/tree/main/examples)
157//! for complete working examples.
158//!
159//! ## Related Crates
160//!
161//! - [`mockforge-core`](https://docs.rs/mockforge-core): Core mocking functionality
162//! - [`mockforge-data`](https://docs.rs/mockforge-data): Synthetic data generation
163//! - [`mockforge-plugin-core`](https://docs.rs/mockforge-plugin-core): Plugin development
164//!
165//! ## Documentation
166//!
167//! - [MockForge Book](https://docs.mockforge.dev/)
168//! - [HTTP Mocking Guide](https://docs.mockforge.dev/user-guide/http-mocking.html)
169//! - [API Reference](https://docs.rs/mockforge-http)
170
171pub mod ai_handler;
172pub mod auth;
173pub mod chain_handlers;
174/// Cross-protocol consistency engine integration for HTTP
175pub mod consistency;
176/// Contract diff retrieval API
177pub mod contract_diff_api;
178/// Contract diff middleware for automatic request capture
179pub mod contract_diff_middleware;
180pub mod coverage;
181pub mod database;
182/// File generation service for creating mock PDF, CSV, JSON files
183pub mod file_generator;
184/// File serving for generated mock files
185pub mod file_server;
186/// Fixtures management API for hosted-mock deployments
187pub mod fixtures_api;
188/// Kubernetes-native health check endpoints (liveness, readiness, startup probes)
189pub mod health;
190pub mod http_tracing_middleware;
191/// Latency profile configuration for HTTP request simulation
192pub mod latency_profiles;
193/// Management API for server control and monitoring
194pub mod management;
195/// WebSocket-based management API for real-time updates
196pub mod management_ws;
197pub mod metrics_middleware;
198pub mod middleware;
199/// Standalone MockAI HTTP API
200pub mod mockai_api;
201/// Runtime network-profile switching API
202pub mod network_profile_runtime;
203pub mod op_middleware;
204/// Unified protocol server lifecycle implementation
205pub mod protocol_server;
206/// Browser/Mobile Proxy Server
207pub mod proxy_server;
208/// Quick mock generation utilities
209pub mod quick_mock;
210/// RAG-powered AI response generation
211pub mod rag_ai_generator;
212/// Reality-slider-driven mock/proxy switching middleware (#222)
213pub mod reality_proxy;
214/// Replay listing and fixture management
215pub mod replay_listing;
216pub mod request_logging;
217/// Runtime route-scoped chaos rules API
218pub mod route_chaos_runtime;
219/// Runtime named-scenario activation API
220#[cfg(feature = "scenario-engine")]
221pub mod scenarios_runtime;
222/// Specification import API for OpenAPI and AsyncAPI
223pub mod spec_import;
224/// Server-Sent Events for streaming logs and metrics
225pub mod sse;
226/// State machine API for scenario state machines
227pub mod state_machine_api;
228/// Time-travel runtime API mirrored from the admin server onto the main HTTP port
229pub mod time_travel_api;
230/// TLS/HTTPS support
231pub mod tls;
232/// Token response utilities
233pub mod token_response;
234/// UI Builder API for low-code mock endpoint creation
235pub mod ui_builder;
236/// Verification API for request verification
237pub mod verification;
238
239// Access review handlers
240pub mod handlers;
241
242// Re-export AI handler utilities
243pub use ai_handler::{process_response_with_ai, AiResponseConfig, AiResponseHandler};
244// Re-export health check utilities
245pub use health::{HealthManager, ServiceStatus};
246
247// Re-export management API utilities
248pub use management::{
249    management_router, management_router_with_ui_builder, ManagementState, MockConfig,
250    ServerConfig, ServerStats,
251};
252
253// Re-export UI Builder utilities
254pub use ui_builder::{create_ui_builder_router, EndpointConfig, UIBuilderState};
255
256// Re-export management WebSocket utilities
257pub use management_ws::{ws_management_router, MockEvent, WsManagementState};
258
259// Re-export verification API utilities
260pub use verification::verification_router;
261
262// Re-export metrics middleware
263pub use metrics_middleware::collect_http_metrics;
264
265// Re-export tracing middleware
266pub use http_tracing_middleware::http_tracing_middleware;
267
268// Re-export coverage utilities
269pub use coverage::{calculate_coverage, CoverageReport, MethodCoverage, RouteCoverage};
270
271/// Helper function to load persona from config file
272/// Tries to load from common config locations: config.yaml, mockforge.yaml, tools/mockforge/config.yaml
273async fn load_persona_from_config() -> Option<Arc<Persona>> {
274    use mockforge_core::config::load_config;
275
276    // Try common config file locations
277    let config_paths = [
278        "config.yaml",
279        "mockforge.yaml",
280        "tools/mockforge/config.yaml",
281        "../tools/mockforge/config.yaml",
282    ];
283
284    for path in &config_paths {
285        if let Ok(config) = load_config(path).await {
286            // Access intelligent_behavior through mockai config
287            // Note: Config structure is mockai.intelligent_behavior.personas
288            if let Some(persona) = config.mockai.intelligent_behavior.personas.get_active_persona()
289            {
290                tracing::info!(
291                    "Loaded active persona '{}' from config file: {}",
292                    persona.name,
293                    path
294                );
295                return Some(Arc::new(persona.clone()));
296            } else {
297                tracing::debug!(
298                    "No active persona found in config file: {} (personas count: {})",
299                    path,
300                    config.mockai.intelligent_behavior.personas.personas.len()
301                );
302            }
303        } else {
304            tracing::debug!("Could not load config from: {}", path);
305        }
306    }
307
308    tracing::debug!("No persona found in config files, persona-based generation will be disabled");
309    None
310}
311
312use axum::body::Body;
313use axum::extract::State;
314use axum::http::Request;
315use axum::middleware::from_fn_with_state;
316use axum::response::Json;
317use axum::Router;
318use mockforge_chaos::core_failure_injection::{FailureConfig, FailureInjector};
319use mockforge_core::intelligent_behavior::config::Persona;
320use mockforge_foundation::latency::LatencyInjector;
321use mockforge_openapi::openapi_routes::OpenApiRouteRegistry;
322use mockforge_openapi::openapi_routes::ValidationOptions;
323use mockforge_openapi::OpenApiSpec;
324use std::sync::Arc;
325use tower_http::cors::{Any, CorsLayer};
326
327#[cfg(feature = "data-faker")]
328use mockforge_data::provider::register_core_faker_provider;
329use mockforge_foundation::latency::LatencyProfile;
330use std::collections::HashMap;
331use std::ffi::OsStr;
332use std::path::Path;
333use tokio::fs;
334use tokio::sync::RwLock;
335use tracing::*;
336
337/// Route info for storing in state
338#[derive(Clone)]
339pub struct RouteInfo {
340    /// HTTP method (GET, POST, PUT, etc.)
341    pub method: String,
342    /// API path pattern (e.g., "/api/users/{id}")
343    pub path: String,
344    /// OpenAPI operation ID if available
345    pub operation_id: Option<String>,
346    /// Operation summary from OpenAPI spec
347    pub summary: Option<String>,
348    /// Operation description from OpenAPI spec
349    pub description: Option<String>,
350    /// List of parameter names for this route
351    pub parameters: Vec<String>,
352}
353
354/// Shared state for tracking OpenAPI routes
355#[derive(Clone)]
356pub struct HttpServerState {
357    /// List of registered routes from OpenAPI spec
358    pub routes: Vec<RouteInfo>,
359    /// Optional global rate limiter for request throttling
360    pub rate_limiter: Option<Arc<middleware::rate_limit::GlobalRateLimiter>>,
361    /// Production headers to add to all responses (for deceptive deploy)
362    pub production_headers: Option<Arc<HashMap<String, String>>>,
363}
364
365impl Default for HttpServerState {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371impl HttpServerState {
372    /// Create a new empty HTTP server state
373    pub fn new() -> Self {
374        Self {
375            routes: Vec::new(),
376            rate_limiter: None,
377            production_headers: None,
378        }
379    }
380
381    /// Create HTTP server state with pre-configured routes
382    pub fn with_routes(routes: Vec<RouteInfo>) -> Self {
383        Self {
384            routes,
385            rate_limiter: None,
386            production_headers: None,
387        }
388    }
389
390    /// Add a rate limiter to the HTTP server state
391    pub fn with_rate_limiter(
392        mut self,
393        rate_limiter: Arc<middleware::rate_limit::GlobalRateLimiter>,
394    ) -> Self {
395        self.rate_limiter = Some(rate_limiter);
396        self
397    }
398
399    /// Add production headers to the HTTP server state
400    pub fn with_production_headers(mut self, headers: Arc<HashMap<String, String>>) -> Self {
401        self.production_headers = Some(headers);
402        self
403    }
404}
405
406/// Handler to return OpenAPI routes information
407async fn get_routes_handler(State(state): State<HttpServerState>) -> Json<serde_json::Value> {
408    let route_info: Vec<serde_json::Value> = state
409        .routes
410        .iter()
411        .map(|route| {
412            serde_json::json!({
413                "method": route.method,
414                "path": route.path,
415                "operation_id": route.operation_id,
416                "summary": route.summary,
417                "description": route.description,
418                "parameters": route.parameters
419            })
420        })
421        .collect();
422
423    Json(serde_json::json!({
424        "routes": route_info,
425        "total": state.routes.len()
426    }))
427}
428
429/// Handler to serve the Scalar API docs page
430async fn get_docs_handler() -> axum::response::Html<&'static str> {
431    axum::response::Html(include_str!("../static/docs.html"))
432}
433
434/// Build the base HTTP router, optionally from an OpenAPI spec.
435pub async fn build_router(
436    spec_path: Option<String>,
437    options: Option<ValidationOptions>,
438    failure_config: Option<FailureConfig>,
439) -> Router {
440    build_router_with_multi_tenant(
441        spec_path,
442        options,
443        failure_config,
444        None,
445        None,
446        None,
447        None,
448        None,
449        None,
450        None,
451    )
452    .await
453}
454
455/// Apply CORS middleware to the router based on configuration
456fn apply_cors_middleware(
457    app: Router,
458    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
459) -> Router {
460    use http::Method;
461    use tower_http::cors::AllowOrigin;
462
463    if let Some(config) = cors_config {
464        if !config.enabled {
465            return app;
466        }
467
468        let mut cors_layer = CorsLayer::new();
469        let is_wildcard_origin;
470
471        // Configure allowed origins
472        if config.allowed_origins.contains(&"*".to_string()) {
473            cors_layer = cors_layer.allow_origin(Any);
474            is_wildcard_origin = true;
475        } else if !config.allowed_origins.is_empty() {
476            // Try to parse each origin, fallback to permissive if parsing fails
477            let origins: Vec<_> = config
478                .allowed_origins
479                .iter()
480                .filter_map(|origin| {
481                    origin.parse::<http::HeaderValue>().ok().map(AllowOrigin::exact)
482                })
483                .collect();
484
485            if origins.is_empty() {
486                // If no valid origins, use permissive for development
487                warn!("No valid CORS origins configured, using permissive CORS");
488                cors_layer = cors_layer.allow_origin(Any);
489                is_wildcard_origin = true;
490            } else {
491                // Use the first origin as exact match (tower-http limitation)
492                // For multiple origins, we'd need a custom implementation
493                if origins.len() == 1 {
494                    cors_layer = cors_layer.allow_origin(origins[0].clone());
495                    is_wildcard_origin = false;
496                } else {
497                    // Multiple origins - use permissive for now
498                    warn!(
499                        "Multiple CORS origins configured, using permissive CORS. \
500                        Consider using '*' for all origins."
501                    );
502                    cors_layer = cors_layer.allow_origin(Any);
503                    is_wildcard_origin = true;
504                }
505            }
506        } else {
507            // No origins specified, use permissive for development
508            cors_layer = cors_layer.allow_origin(Any);
509            is_wildcard_origin = true;
510        }
511
512        // Configure allowed methods
513        if !config.allowed_methods.is_empty() {
514            let methods: Vec<Method> =
515                config.allowed_methods.iter().filter_map(|m| m.parse().ok()).collect();
516            if !methods.is_empty() {
517                cors_layer = cors_layer.allow_methods(methods);
518            }
519        } else {
520            // Default to common HTTP methods
521            cors_layer = cors_layer.allow_methods([
522                Method::GET,
523                Method::POST,
524                Method::PUT,
525                Method::DELETE,
526                Method::PATCH,
527                Method::OPTIONS,
528            ]);
529        }
530
531        // Configure allowed headers
532        if !config.allowed_headers.is_empty() {
533            let headers: Vec<_> = config
534                .allowed_headers
535                .iter()
536                .filter_map(|h| h.parse::<http::HeaderName>().ok())
537                .collect();
538            if !headers.is_empty() {
539                cors_layer = cors_layer.allow_headers(headers);
540            }
541        } else {
542            // Default headers
543            cors_layer =
544                cors_layer.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION]);
545        }
546
547        // Configure credentials - cannot allow credentials with wildcard origin
548        // Determine if credentials should be allowed
549        // Cannot allow credentials with wildcard origin per CORS spec
550        let should_allow_credentials = if is_wildcard_origin {
551            // Wildcard origin - credentials must be false
552            false
553        } else {
554            // Specific origins - use config value (defaults to false)
555            config.allow_credentials
556        };
557
558        cors_layer = cors_layer.allow_credentials(should_allow_credentials);
559
560        info!(
561            "CORS middleware enabled with configured settings (credentials: {})",
562            should_allow_credentials
563        );
564        app.layer(cors_layer)
565    } else {
566        // No CORS config provided - use permissive CORS for development
567        // Note: permissive() allows credentials, but since it uses wildcard origin,
568        // we need to disable credentials to avoid CORS spec violation
569        debug!("No CORS config provided, using permissive CORS for development");
570        // Create a permissive CORS layer but disable credentials to avoid CORS spec violation
571        // (cannot combine credentials with wildcard origin)
572        app.layer(CorsLayer::permissive().allow_credentials(false))
573    }
574}
575
576/// Build the base HTTP router with multi-tenant workspace support
577#[allow(clippy::too_many_arguments)]
578#[allow(deprecated)] // uses MockAI, MultiTenantWorkspaceRegistry, WorkspaceRouter, Workspace (all stay in core)
579pub async fn build_router_with_multi_tenant(
580    spec_path: Option<String>,
581    options: Option<ValidationOptions>,
582    failure_config: Option<FailureConfig>,
583    multi_tenant_config: Option<mockforge_foundation::multi_tenant_types::MultiTenantConfig>,
584    _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
585    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
586    ai_generator: Option<Arc<dyn mockforge_openapi::response::AiGenerator + Send + Sync>>,
587    smtp_registry: Option<Arc<dyn std::any::Any + Send + Sync>>,
588    mockai: Option<Arc<RwLock<mockforge_core::intelligent_behavior::MockAI>>>,
589    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
590) -> Router {
591    use std::time::Instant;
592
593    let startup_start = Instant::now();
594
595    // Set up the basic router
596    let mut app = Router::new();
597
598    // Initialize rate limiter with default configuration
599    // Can be customized via environment variables or config
600    let mut rate_limit_config = middleware::RateLimitConfig {
601        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
602            .ok()
603            .and_then(|v| v.parse().ok())
604            .unwrap_or(1000),
605        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
606            .ok()
607            .and_then(|v| v.parse().ok())
608            .unwrap_or(2000),
609        per_ip: true,
610        per_endpoint: false,
611    };
612
613    // Apply deceptive deploy configuration if enabled
614    let mut final_cors_config = cors_config;
615    let mut production_headers: Option<std::sync::Arc<HashMap<String, String>>> = None;
616    // Auth config from deceptive deploy OAuth (if configured)
617    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
618
619    if let Some(deploy_config) = &deceptive_deploy_config {
620        if deploy_config.enabled {
621            info!("Deceptive deploy mode enabled - applying production-like configuration");
622
623            // Override CORS config if provided
624            if let Some(prod_cors) = &deploy_config.cors {
625                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
626                    enabled: true,
627                    allowed_origins: prod_cors.allowed_origins.clone(),
628                    allowed_methods: prod_cors.allowed_methods.clone(),
629                    allowed_headers: prod_cors.allowed_headers.clone(),
630                    allow_credentials: prod_cors.allow_credentials,
631                });
632                info!("Applied production-like CORS configuration");
633            }
634
635            // Override rate limit config if provided
636            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
637                rate_limit_config = middleware::RateLimitConfig {
638                    requests_per_minute: prod_rate_limit.requests_per_minute,
639                    burst: prod_rate_limit.burst,
640                    per_ip: prod_rate_limit.per_ip,
641                    per_endpoint: false,
642                };
643                info!(
644                    "Applied production-like rate limiting: {} req/min, burst: {}",
645                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
646                );
647            }
648
649            // Set production headers
650            if !deploy_config.headers.is_empty() {
651                let headers_map: HashMap<String, String> = deploy_config.headers.clone();
652                production_headers = Some(std::sync::Arc::new(headers_map));
653                info!("Configured {} production headers", deploy_config.headers.len());
654            }
655
656            // Integrate OAuth config from deceptive deploy
657            if let Some(prod_oauth) = &deploy_config.oauth {
658                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
659                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
660                    oauth2: Some(oauth2_config),
661                    ..Default::default()
662                });
663                info!("Applied production-like OAuth configuration for deceptive deploy");
664            }
665        }
666    }
667
668    let rate_limit_disabled = middleware::is_rate_limit_disabled();
669    let rate_limiter =
670        std::sync::Arc::new(middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
671
672    let mut state = HttpServerState::new();
673    if rate_limit_disabled {
674        info!(
675            "HTTP rate limiting disabled (MOCKFORGE_RATE_LIMIT_ENABLED=false or --no-rate-limit)"
676        );
677    } else {
678        state = state.with_rate_limiter(rate_limiter.clone());
679    }
680
681    // Add production headers to state if configured
682    if let Some(headers) = production_headers.clone() {
683        state = state.with_production_headers(headers);
684    }
685
686    // Clone spec_path for later use
687    let spec_path_for_mgmt = spec_path.clone();
688
689    // If an OpenAPI spec is provided, integrate it
690    if let Some(spec_path) = spec_path {
691        tracing::debug!("Processing OpenAPI spec path: {}", spec_path);
692
693        // Measure OpenAPI spec loading
694        let spec_load_start = Instant::now();
695        match OpenApiSpec::from_file(&spec_path).await {
696            Ok(openapi) => {
697                let spec_load_duration = spec_load_start.elapsed();
698                info!(
699                    "Successfully loaded OpenAPI spec from {} (took {:?})",
700                    spec_path, spec_load_duration
701                );
702
703                // Measure route registry creation
704                tracing::debug!("Creating OpenAPI route registry...");
705                let registry_start = Instant::now();
706
707                // Try to load persona from config if available
708                let persona = load_persona_from_config().await;
709
710                let registry = if let Some(opts) = options {
711                    tracing::debug!("Using custom validation options");
712                    if let Some(ref persona) = persona {
713                        tracing::info!("Using persona '{}' for route generation", persona.name);
714                    }
715                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
716                } else {
717                    tracing::debug!("Using environment-based options");
718                    if let Some(ref persona) = persona {
719                        tracing::info!("Using persona '{}' for route generation", persona.name);
720                    }
721                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
722                };
723                let registry_duration = registry_start.elapsed();
724                info!(
725                    "Created OpenAPI route registry with {} routes (took {:?})",
726                    registry.routes().len(),
727                    registry_duration
728                );
729
730                // Measure route extraction
731                let extract_start = Instant::now();
732                let route_info: Vec<RouteInfo> = registry
733                    .routes()
734                    .iter()
735                    .map(|route| RouteInfo {
736                        method: route.method.clone(),
737                        path: route.path.clone(),
738                        operation_id: route.operation.operation_id.clone(),
739                        summary: route.operation.summary.clone(),
740                        description: route.operation.description.clone(),
741                        parameters: route.parameters.clone(),
742                    })
743                    .collect();
744                state.routes = route_info;
745                let extract_duration = extract_start.elapsed();
746                debug!("Extracted route information (took {:?})", extract_duration);
747
748                // Measure overrides loading
749                let overrides = if std::env::var("MOCKFORGE_HTTP_OVERRIDES_GLOB").is_ok() {
750                    tracing::debug!("Loading overrides from environment variable");
751                    let overrides_start = Instant::now();
752                    match mockforge_core::Overrides::load_from_globs(&[]).await {
753                        Ok(overrides) => {
754                            let overrides_duration = overrides_start.elapsed();
755                            info!(
756                                "Loaded {} override rules (took {:?})",
757                                overrides.rules().len(),
758                                overrides_duration
759                            );
760                            Some(overrides)
761                        }
762                        Err(e) => {
763                            tracing::warn!("Failed to load overrides: {}", e);
764                            None
765                        }
766                    }
767                } else {
768                    None
769                };
770
771                // Measure router building
772                let router_build_start = Instant::now();
773                let overrides_enabled = overrides.is_some();
774                let response_rewriter: Option<
775                    std::sync::Arc<dyn mockforge_openapi::response_rewriter::ResponseRewriter>,
776                > = Some(std::sync::Arc::new(
777                    mockforge_core::openapi_rewriter::CoreResponseRewriter::new(overrides),
778                ));
779                let openapi_router = if let Some(mockai_instance) = &mockai {
780                    tracing::debug!("Building router with MockAI support");
781                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
782                } else if let Some(ai_generator) = &ai_generator {
783                    tracing::debug!("Building router with AI generator support");
784                    registry.build_router_with_ai(Some(ai_generator.clone()))
785                } else if let Some(failure_config) = &failure_config {
786                    tracing::debug!("Building router with failure injection and overrides");
787                    let failure_injector = FailureInjector::new(Some(failure_config.clone()), true);
788                    registry.build_router_with_injectors_and_overrides(
789                        LatencyInjector::default(),
790                        Some(failure_injector),
791                        response_rewriter,
792                        overrides_enabled,
793                    )
794                } else {
795                    tracing::debug!("Building router with overrides");
796                    registry.build_router_with_injectors_and_overrides(
797                        LatencyInjector::default(),
798                        None,
799                        response_rewriter,
800                        overrides_enabled,
801                    )
802                };
803                let router_build_duration = router_build_start.elapsed();
804                debug!("Built OpenAPI router (took {:?})", router_build_duration);
805
806                tracing::debug!("Merging OpenAPI router with main router");
807                app = app.merge(openapi_router);
808                tracing::debug!("Router built successfully");
809            }
810            Err(e) => {
811                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
812            }
813        }
814    }
815
816    // Add basic health check endpoint
817    app = app.route(
818        "/health",
819        axum::routing::get(|| async {
820            use mockforge_core::server_utils::health::HealthStatus;
821            {
822                // HealthStatus should always serialize, but handle errors gracefully
823                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
824                    Ok(value) => Json(value),
825                    Err(e) => {
826                        // Log error but return a simple healthy response
827                        tracing::error!("Failed to serialize health status: {}", e);
828                        Json(serde_json::json!({
829                            "status": "healthy",
830                            "service": "mockforge-http",
831                            "uptime_seconds": 0
832                        }))
833                    }
834                }
835            }
836        }),
837    )
838    // Add SSE endpoints
839    .merge(sse::sse_router())
840    // Add file serving endpoints for generated mock files
841    .merge(file_server::file_serving_router());
842
843    // Clone state for routes_router since we'll use it for middleware too
844    let state_for_routes = state.clone();
845
846    // Create a router with state for the routes and coverage endpoints
847    let routes_router = Router::new()
848        .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
849        .route("/__mockforge/coverage", axum::routing::get(coverage::get_coverage_handler))
850        .with_state(state_for_routes);
851
852    // Merge the routes router with the main app
853    app = app.merge(routes_router);
854
855    // Add API docs page (Scalar-powered interactive explorer)
856    app = app.route("/__mockforge/docs", axum::routing::get(get_docs_handler));
857
858    // Add static coverage UI
859    // Determine the path to the coverage.html file
860    let coverage_html_path = std::env::var("MOCKFORGE_COVERAGE_UI_PATH")
861        .unwrap_or_else(|_| "crates/mockforge-http/static/coverage.html".to_string());
862
863    // Check if the file exists before serving it
864    if Path::new(&coverage_html_path).exists() {
865        app = app.nest_service(
866            "/__mockforge/coverage.html",
867            tower_http::services::ServeFile::new(&coverage_html_path),
868        );
869        debug!("Serving coverage UI from: {}", coverage_html_path);
870    } else {
871        debug!(
872            "Coverage UI file not found at: {}. Skipping static file serving.",
873            coverage_html_path
874        );
875    }
876
877    // Add management API endpoints
878    // Load spec for ManagementState so /__mockforge/api/spec can serve it
879    let mgmt_spec = if let Some(ref sp) = spec_path_for_mgmt {
880        match OpenApiSpec::from_file(sp).await {
881            Ok(s) => Some(Arc::new(s)),
882            Err(e) => {
883                debug!("Failed to load OpenAPI spec for management API: {}", e);
884                None
885            }
886        }
887    } else {
888        None
889    };
890    let mgmt_port = std::env::var("PORT")
891        .or_else(|_| std::env::var("MOCKFORGE_HTTP_PORT"))
892        .ok()
893        .and_then(|p| p.parse().ok())
894        .unwrap_or(3000);
895    let management_state = ManagementState::new(mgmt_spec, spec_path_for_mgmt, mgmt_port);
896
897    // Create WebSocket state and connect it to management state
898    use std::sync::Arc;
899    let ws_state = WsManagementState::new();
900    let ws_broadcast = Arc::new(ws_state.tx.clone());
901    let management_state = management_state.with_ws_broadcast(ws_broadcast);
902
903    // Note: ProxyConfig not available in this build function path
904    // Migration endpoints will work once ProxyConfig is passed to build_router_with_chains_and_multi_tenant
905
906    #[cfg(feature = "smtp")]
907    let management_state = {
908        if let Some(smtp_reg) = smtp_registry {
909            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
910                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
911                Err(e) => {
912                    error!(
913                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
914                        e.type_id()
915                    );
916                    management_state
917                }
918            }
919        } else {
920            management_state
921        }
922    };
923    #[cfg(not(feature = "smtp"))]
924    let management_state = management_state;
925    #[cfg(not(feature = "smtp"))]
926    let _ = smtp_registry;
927    let management_state_for_fallback = management_state.clone();
928    app = app.nest("/__mockforge/api", management_router(management_state));
929    // Serve any request that the rest of the router doesn't handle as a dynamic
930    // mock lookup. Lets the Node/Rust SDK register stubs via the management
931    // API and have requests actually reach them. Falls through to 404 when
932    // no mock matches.
933    app = app.fallback_service(
934        axum::routing::any(management::dynamic_mock_fallback)
935            .with_state(management_state_for_fallback),
936    );
937
938    // Add verification API endpoint
939    app = app.merge(verification_router());
940
941    // Add OIDC well-known endpoints
942    use crate::auth::oidc::oidc_router;
943    app = app.merge(oidc_router());
944
945    // Add access review API if enabled
946    {
947        use mockforge_core::security::get_global_access_review_service;
948        if let Some(service) = get_global_access_review_service().await {
949            use crate::handlers::access_review::{access_review_router, AccessReviewState};
950            let review_state = AccessReviewState { service };
951            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
952            debug!("Access review API mounted at /api/v1/security/access-reviews");
953        }
954    }
955
956    // Add privileged access API if enabled
957    {
958        use mockforge_core::security::get_global_privileged_access_manager;
959        if let Some(manager) = get_global_privileged_access_manager().await {
960            use crate::handlers::privileged_access::{
961                privileged_access_router, PrivilegedAccessState,
962            };
963            let privileged_state = PrivilegedAccessState { manager };
964            app = app.nest(
965                "/api/v1/security/privileged-access",
966                privileged_access_router(privileged_state),
967            );
968            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
969        }
970    }
971
972    // Add change management API if enabled
973    {
974        use mockforge_core::security::get_global_change_management_engine;
975        if let Some(engine) = get_global_change_management_engine().await {
976            use crate::handlers::change_management::{
977                change_management_router, ChangeManagementState,
978            };
979            let change_state = ChangeManagementState { engine };
980            app = app.nest("/api/v1/change-management", change_management_router(change_state));
981            debug!("Change management API mounted at /api/v1/change-management");
982        }
983    }
984
985    // Add risk assessment API if enabled
986    {
987        use mockforge_core::security::get_global_risk_assessment_engine;
988        if let Some(engine) = get_global_risk_assessment_engine().await {
989            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
990            let risk_state = RiskAssessmentState { engine };
991            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
992            debug!("Risk assessment API mounted at /api/v1/security/risks");
993        }
994    }
995
996    // Add token lifecycle API
997    {
998        use crate::auth::token_lifecycle::TokenLifecycleManager;
999        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
1000        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1001        let lifecycle_state = TokenLifecycleState {
1002            manager: lifecycle_manager,
1003        };
1004        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
1005        debug!("Token lifecycle API mounted at /api/v1/auth");
1006    }
1007
1008    // Add OAuth2 server endpoints
1009    {
1010        use crate::auth::oidc::load_oidc_state;
1011        use crate::auth::token_lifecycle::TokenLifecycleManager;
1012        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
1013        // Load OIDC state from configuration (environment variables or config file)
1014        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
1015        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1016        let oauth2_state = OAuth2ServerState {
1017            oidc_state,
1018            lifecycle_manager,
1019            auth_codes: Arc::new(RwLock::new(HashMap::new())),
1020            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
1021        };
1022        app = app.merge(oauth2_server_router(oauth2_state));
1023        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
1024    }
1025
1026    // Add consent screen endpoints
1027    {
1028        use crate::auth::oidc::load_oidc_state;
1029        use crate::auth::risk_engine::RiskEngine;
1030        use crate::auth::token_lifecycle::TokenLifecycleManager;
1031        use crate::handlers::consent::{consent_router, ConsentState};
1032        use crate::handlers::oauth2_server::OAuth2ServerState;
1033        // Load OIDC state from configuration (environment variables or config file)
1034        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
1035        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1036        let oauth2_state = OAuth2ServerState {
1037            oidc_state: oidc_state.clone(),
1038            lifecycle_manager: lifecycle_manager.clone(),
1039            auth_codes: Arc::new(RwLock::new(HashMap::new())),
1040            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
1041        };
1042        let risk_engine = Arc::new(RiskEngine::default());
1043        let consent_state = ConsentState {
1044            oauth2_state,
1045            risk_engine,
1046        };
1047        app = app.merge(consent_router(consent_state));
1048        debug!("Consent screen endpoints mounted at /consent");
1049    }
1050
1051    // Add risk simulation API
1052    {
1053        use crate::auth::risk_engine::RiskEngine;
1054        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
1055        let risk_engine = Arc::new(RiskEngine::default());
1056        let risk_state = RiskSimulationState { risk_engine };
1057        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
1058        debug!("Risk simulation API mounted at /api/v1/auth/risk");
1059    }
1060
1061    // Add management WebSocket endpoint
1062    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
1063
1064    // Add request logging middleware to capture all requests
1065    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
1066
1067    // Add security middleware for security event tracking (after logging, before contract diff)
1068    app = app.layer(axum::middleware::from_fn(middleware::security_middleware));
1069
1070    // Add contract diff middleware for automatic request capture
1071    // This captures requests for contract diff analysis (after logging)
1072    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
1073
1074    // Add rate limiting middleware (before logging to rate limit early)
1075    app = app.layer(from_fn_with_state(state.clone(), middleware::rate_limit_middleware));
1076
1077    // Add production headers middleware if configured
1078    if state.production_headers.is_some() {
1079        app =
1080            app.layer(from_fn_with_state(state.clone(), middleware::production_headers_middleware));
1081    }
1082
1083    // Add authentication middleware if OAuth is configured via deceptive deploy
1084    if let Some(auth_config) = deceptive_deploy_auth_config {
1085        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1086        use std::collections::HashMap;
1087        use std::sync::Arc;
1088        use tokio::sync::RwLock;
1089
1090        // Create OAuth2 client if configured
1091        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
1092            match create_oauth2_client(oauth2_config) {
1093                Ok(client) => Some(client),
1094                Err(e) => {
1095                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
1096                    None
1097                }
1098            }
1099        } else {
1100            None
1101        };
1102
1103        // Create auth state
1104        let auth_state = AuthState {
1105            config: auth_config,
1106            spec: None, // OpenAPI spec not available in this context
1107            oauth2_client,
1108            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1109        };
1110
1111        // Apply auth middleware
1112        app = app.layer(from_fn_with_state(auth_state, auth_middleware));
1113        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
1114    }
1115
1116    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
1117    app = apply_cors_middleware(app, final_cors_config);
1118
1119    // Add workspace routing middleware if multi-tenant is enabled
1120    if let Some(mt_config) = multi_tenant_config {
1121        if mt_config.enabled {
1122            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
1123            use std::sync::Arc;
1124
1125            info!(
1126                "Multi-tenant mode enabled with {} routing strategy",
1127                match mt_config.routing_strategy {
1128                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Path => "path-based",
1129                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Port => "port-based",
1130                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Both => "hybrid",
1131                }
1132            );
1133
1134            // Create the multi-tenant workspace registry
1135            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
1136
1137            // Register the default workspace before wrapping in Arc
1138            let default_workspace =
1139                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
1140            if let Err(e) =
1141                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
1142            {
1143                warn!("Failed to register default workspace: {}", e);
1144            } else {
1145                info!("Registered default workspace: '{}'", mt_config.default_workspace);
1146            }
1147
1148            // Auto-discover and register workspaces if configured
1149            if mt_config.auto_discover {
1150                if let Some(config_dir) = &mt_config.config_directory {
1151                    let config_path = Path::new(config_dir);
1152                    if config_path.exists() && config_path.is_dir() {
1153                        match fs::read_dir(config_path).await {
1154                            Ok(mut entries) => {
1155                                while let Ok(Some(entry)) = entries.next_entry().await {
1156                                    let path = entry.path();
1157                                    if path.extension() == Some(OsStr::new("yaml")) {
1158                                        match fs::read_to_string(&path).await {
1159                                            Ok(content) => {
1160                                                match serde_yaml::from_str::<
1161                                                    mockforge_core::Workspace,
1162                                                >(
1163                                                    &content
1164                                                ) {
1165                                                    Ok(workspace) => {
1166                                                        if let Err(e) = registry.register_workspace(
1167                                                            workspace.id.clone(),
1168                                                            workspace,
1169                                                        ) {
1170                                                            warn!("Failed to register auto-discovered workspace from {:?}: {}", path, e);
1171                                                        } else {
1172                                                            info!("Auto-registered workspace from {:?}", path);
1173                                                        }
1174                                                    }
1175                                                    Err(e) => {
1176                                                        warn!("Failed to parse workspace from {:?}: {}", path, e);
1177                                                    }
1178                                                }
1179                                            }
1180                                            Err(e) => {
1181                                                warn!(
1182                                                    "Failed to read workspace file {:?}: {}",
1183                                                    path, e
1184                                                );
1185                                            }
1186                                        }
1187                                    }
1188                                }
1189                            }
1190                            Err(e) => {
1191                                warn!("Failed to read config directory {:?}: {}", config_path, e);
1192                            }
1193                        }
1194                    } else {
1195                        warn!(
1196                            "Config directory {:?} does not exist or is not a directory",
1197                            config_path
1198                        );
1199                    }
1200                }
1201            }
1202
1203            // Wrap registry in Arc for shared access
1204            let registry = Arc::new(registry);
1205
1206            // Create workspace router and wrap the app with workspace middleware
1207            let _workspace_router = WorkspaceRouter::new(registry);
1208
1209            // Note: The actual middleware integration would need to be implemented
1210            // in the WorkspaceRouter to work with Axum's middleware system
1211            info!("Workspace routing middleware initialized for HTTP server");
1212        }
1213    }
1214
1215    let total_startup_duration = startup_start.elapsed();
1216    info!("HTTP router startup completed (total time: {:?})", total_startup_duration);
1217
1218    app
1219}
1220
1221/// Build the base HTTP router with authentication and latency support
1222pub async fn build_router_with_auth_and_latency(
1223    spec_path: Option<String>,
1224    _options: Option<()>,
1225    auth_config: Option<mockforge_core::config::AuthConfig>,
1226    latency_injector: Option<LatencyInjector>,
1227) -> Router {
1228    // Build auth router first, then layer latency on top
1229    let mut app = build_router_with_auth(spec_path.clone(), None, auth_config).await;
1230
1231    // Apply latency injection middleware if configured
1232    if let Some(injector) = latency_injector {
1233        let injector = Arc::new(injector);
1234        app = app.layer(axum::middleware::from_fn(move |req, next: axum::middleware::Next| {
1235            let injector = injector.clone();
1236            async move {
1237                let _ = injector.inject_latency(&[]).await;
1238                next.run(req).await
1239            }
1240        }));
1241    }
1242
1243    app
1244}
1245
1246/// Build the base HTTP router with latency injection support
1247pub async fn build_router_with_latency(
1248    spec_path: Option<String>,
1249    options: Option<ValidationOptions>,
1250    latency_injector: Option<LatencyInjector>,
1251) -> Router {
1252    if let Some(spec) = &spec_path {
1253        match OpenApiSpec::from_file(spec).await {
1254            Ok(openapi) => {
1255                let registry = if let Some(opts) = options {
1256                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1257                } else {
1258                    OpenApiRouteRegistry::new_with_env(openapi)
1259                };
1260
1261                if let Some(injector) = latency_injector {
1262                    return registry.build_router_with_latency(injector);
1263                } else {
1264                    return registry.build_router();
1265                }
1266            }
1267            Err(e) => {
1268                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec, e);
1269            }
1270        }
1271    }
1272
1273    build_router(None, None, None).await
1274}
1275
1276/// Build the base HTTP router with authentication support
1277pub async fn build_router_with_auth(
1278    spec_path: Option<String>,
1279    options: Option<ValidationOptions>,
1280    auth_config: Option<mockforge_core::config::AuthConfig>,
1281) -> Router {
1282    use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1283    use std::sync::Arc;
1284
1285    // If richer faker is available, register provider once (idempotent)
1286    #[cfg(feature = "data-faker")]
1287    {
1288        register_core_faker_provider();
1289    }
1290
1291    // Set up authentication state
1292    let spec = if let Some(spec_path) = &spec_path {
1293        match OpenApiSpec::from_file(&spec_path).await {
1294            Ok(spec) => Some(Arc::new(spec)),
1295            Err(e) => {
1296                warn!("Failed to load OpenAPI spec for auth: {}", e);
1297                None
1298            }
1299        }
1300    } else {
1301        None
1302    };
1303
1304    // Create OAuth2 client if configured
1305    let oauth2_client = if let Some(auth_config) = &auth_config {
1306        if let Some(oauth2_config) = &auth_config.oauth2 {
1307            match create_oauth2_client(oauth2_config) {
1308                Ok(client) => Some(client),
1309                Err(e) => {
1310                    warn!("Failed to create OAuth2 client: {}", e);
1311                    None
1312                }
1313            }
1314        } else {
1315            None
1316        }
1317    } else {
1318        None
1319    };
1320
1321    let auth_state = AuthState {
1322        config: auth_config.unwrap_or_default(),
1323        spec,
1324        oauth2_client,
1325        introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1326    };
1327
1328    // Set up the basic router with auth state
1329    let mut app = Router::new().with_state(auth_state.clone());
1330
1331    // If an OpenAPI spec is provided, integrate it
1332    if let Some(spec_path) = spec_path {
1333        match OpenApiSpec::from_file(&spec_path).await {
1334            Ok(openapi) => {
1335                info!("Loaded OpenAPI spec from {}", spec_path);
1336                let registry = if let Some(opts) = options {
1337                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1338                } else {
1339                    OpenApiRouteRegistry::new_with_env(openapi)
1340                };
1341
1342                app = registry.build_router();
1343            }
1344            Err(e) => {
1345                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
1346            }
1347        }
1348    }
1349
1350    // Add basic health check endpoint
1351    app = app.route(
1352        "/health",
1353        axum::routing::get(|| async {
1354            use mockforge_core::server_utils::health::HealthStatus;
1355            {
1356                // HealthStatus should always serialize, but handle errors gracefully
1357                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1358                    Ok(value) => Json(value),
1359                    Err(e) => {
1360                        // Log error but return a simple healthy response
1361                        tracing::error!("Failed to serialize health status: {}", e);
1362                        Json(serde_json::json!({
1363                            "status": "healthy",
1364                            "service": "mockforge-http",
1365                            "uptime_seconds": 0
1366                        }))
1367                    }
1368                }
1369            }
1370        }),
1371    )
1372    // Add SSE endpoints
1373    .merge(sse::sse_router())
1374    // Add file serving endpoints for generated mock files
1375    .merge(file_server::file_serving_router())
1376    // Add authentication middleware (before logging)
1377    .layer(from_fn_with_state(auth_state.clone(), auth_middleware))
1378    // Add request logging middleware
1379    .layer(axum::middleware::from_fn(request_logging::log_http_requests));
1380
1381    app
1382}
1383
1384/// Serve a provided router on the given port.
1385pub async fn serve_router(
1386    port: u16,
1387    app: Router,
1388) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1389    serve_router_with_tls(port, app, None).await
1390}
1391
1392/// Serve a provided router on the given port with optional TLS support.
1393pub async fn serve_router_with_tls(
1394    port: u16,
1395    app: Router,
1396    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1397) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1398    serve_router_with_tls_notify(port, app, tls_config, None).await
1399}
1400
1401/// Serve a provided router on the given port with optional TLS support, notifying
1402/// the caller via `bound_port_tx` with the actual OS-assigned port once the
1403/// listener has bound. This matters when the requested `port` is `0` (ephemeral),
1404/// because callers such as the `@mockforge-dev/sdk` need to learn the real port
1405/// to connect to it. On the TLS path, `bound_port_tx` is currently ignored and
1406/// the requested `port` is reported verbatim — callers that need dynamic TLS
1407/// ports should bind on a known port.
1408pub async fn serve_router_with_tls_notify(
1409    port: u16,
1410    app: Router,
1411    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1412    bound_port_tx: Option<tokio::sync::oneshot::Sender<u16>>,
1413) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1414    serve_router_with_tls_notify_chaos(port, app, tls_config, bound_port_tx, None).await
1415}
1416
1417/// Serve a router with optional TLS *and* an optional shared chaos config.
1418///
1419/// When `chaos_config` is `Some`, the TCP listener is wrapped with
1420/// [`mockforge_chaos::ChaosTcpListener`], which can inject TCP-level
1421/// connection faults (RST or unclean FIN) before axum/hyper see the socket.
1422/// This is the only way to surface real transport-level failures rather than
1423/// HTTP 503 responses on healthy connections. TLS path does not yet support
1424/// chaos listener wrapping (axum-server uses its own accept loop).
1425pub async fn serve_router_with_tls_notify_chaos(
1426    port: u16,
1427    app: Router,
1428    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1429    bound_port_tx: Option<tokio::sync::oneshot::Sender<u16>>,
1430    chaos_config: Option<std::sync::Arc<tokio::sync::RwLock<mockforge_chaos::ChaosConfig>>>,
1431) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1432    use std::net::SocketAddr;
1433
1434    let addr = mockforge_core::wildcard_socket_addr(port);
1435
1436    if let Some(ref tls) = tls_config {
1437        if tls.enabled {
1438            info!("HTTPS listening on {}", addr);
1439            if let Some(tx) = bound_port_tx {
1440                let _ = tx.send(port);
1441            }
1442            return serve_with_tls(addr, app, tls).await;
1443        }
1444    }
1445
1446    let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
1447        format!(
1448            "Failed to bind HTTP server to port {}: {}\n\
1449             Hint: The port may already be in use. Try using a different port with --http-port or check if another process is using this port with: lsof -i :{} or netstat -tulpn | grep {}",
1450            port, e, port, port
1451        )
1452    })?;
1453
1454    let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
1455    info!("HTTP listening on {}", listener.local_addr().unwrap_or(addr));
1456    if let Some(tx) = bound_port_tx {
1457        let _ = tx.send(actual_port);
1458    }
1459
1460    // Wrap the Router with OData URI rewrite.
1461    // Router::layer() only applies to matched routes, so we must wrap at the service level
1462    // to rewrite URIs BEFORE route matching occurs.
1463    let odata_app = tower::ServiceBuilder::new()
1464        .layer(mockforge_core::odata_rewrite::ODataRewriteLayer)
1465        .service(app);
1466    if let Some(cfg) = chaos_config {
1467        info!("HTTP listener wrapped with chaos TCP listener (RST/FIN injection enabled)");
1468        let chaos_listener = mockforge_chaos::ChaosTcpListener::new(listener, cfg);
1469        // Layer that copies ConnectInfo<ChaosClientAddr> → ConnectInfo<SocketAddr>
1470        // so existing handlers using `ConnectInfo<SocketAddr>` keep working.
1471        let app_with_addr_compat = tower::ServiceBuilder::new()
1472            .layer(axum::middleware::from_fn(copy_chaos_addr_to_socketaddr))
1473            .service(odata_app);
1474        let make_svc = axum::ServiceExt::<Request<Body>>::into_make_service_with_connect_info::<
1475            mockforge_chaos::ChaosClientAddr,
1476        >(app_with_addr_compat);
1477        axum::serve(chaos_listener, make_svc).await?;
1478    } else {
1479        let make_svc = axum::ServiceExt::<Request<Body>>::into_make_service_with_connect_info::<
1480            SocketAddr,
1481        >(odata_app);
1482        axum::serve(listener, make_svc).await?;
1483    }
1484    Ok(())
1485}
1486
1487/// Mirror `ConnectInfo<ChaosClientAddr>` (set by the chaos listener) onto
1488/// `ConnectInfo<SocketAddr>` so handlers extracting `ConnectInfo<SocketAddr>`
1489/// keep working when chaos TCP wrapping is enabled.
1490async fn copy_chaos_addr_to_socketaddr(
1491    mut req: axum::http::Request<axum::body::Body>,
1492    next: axum::middleware::Next,
1493) -> axum::response::Response {
1494    use axum::extract::ConnectInfo;
1495    if let Some(ConnectInfo(chaos_addr)) =
1496        req.extensions().get::<ConnectInfo<mockforge_chaos::ChaosClientAddr>>().copied()
1497    {
1498        let sock: std::net::SocketAddr = *chaos_addr;
1499        req.extensions_mut().insert(ConnectInfo(sock));
1500    }
1501    next.run(req).await
1502}
1503
1504/// Serve router with TLS/HTTPS support using axum-server
1505///
1506/// This function implements native TLS serving using axum-server and tokio-rustls.
1507/// It supports standard TLS and mutual TLS (mTLS) based on the configuration.
1508async fn serve_with_tls(
1509    addr: std::net::SocketAddr,
1510    app: Router,
1511    tls_config: &mockforge_core::config::HttpTlsConfig,
1512) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1513    use axum_server::tls_rustls::RustlsConfig;
1514    use std::net::SocketAddr;
1515
1516    // Initialize the rustls crypto provider (must be called before TLS operations)
1517    tls::init_crypto_provider();
1518
1519    info!("Loading TLS configuration for HTTPS server");
1520
1521    // Load TLS server configuration (supports mTLS)
1522    let server_config = tls::load_tls_server_config(tls_config)?;
1523
1524    // Create RustlsConfig from the ServerConfig
1525    // Note: axum-server's RustlsConfig can be created from a ServerConfig
1526    let rustls_config = RustlsConfig::from_config(server_config);
1527
1528    info!("Starting HTTPS server on {}", addr);
1529
1530    // Wrap the Router with OData URI rewrite (same as the non-TLS path).
1531    // Router::layer() only applies to matched routes, so we must wrap at the service level
1532    // to rewrite URIs BEFORE route matching occurs.
1533    let odata_app = tower::ServiceBuilder::new()
1534        .layer(mockforge_core::odata_rewrite::ODataRewriteLayer)
1535        .service(app);
1536    let make_svc = axum::ServiceExt::<Request<Body>>::into_make_service_with_connect_info::<
1537        SocketAddr,
1538    >(odata_app);
1539
1540    // Serve with TLS using axum-server
1541    axum_server::bind_rustls(addr, rustls_config)
1542        .serve(make_svc)
1543        .await
1544        .map_err(|e| format!("HTTPS server error: {}", e).into())
1545}
1546
1547/// Backwards-compatible start that builds + serves the base router.
1548pub async fn start(
1549    port: u16,
1550    spec_path: Option<String>,
1551    options: Option<ValidationOptions>,
1552) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1553    start_with_latency(port, spec_path, options, None).await
1554}
1555
1556/// Start HTTP server with authentication and latency support
1557pub async fn start_with_auth_and_latency(
1558    port: u16,
1559    spec_path: Option<String>,
1560    options: Option<ValidationOptions>,
1561    auth_config: Option<mockforge_core::config::AuthConfig>,
1562    latency_profile: Option<LatencyProfile>,
1563) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1564    start_with_auth_and_injectors(port, spec_path, options, auth_config, latency_profile, None)
1565        .await
1566}
1567
1568/// Start HTTP server with authentication and injectors support
1569pub async fn start_with_auth_and_injectors(
1570    port: u16,
1571    spec_path: Option<String>,
1572    options: Option<ValidationOptions>,
1573    auth_config: Option<mockforge_core::config::AuthConfig>,
1574    _latency_profile: Option<LatencyProfile>,
1575    _failure_injector: Option<FailureInjector>,
1576) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1577    // For now, ignore latency and failure injectors and just use auth
1578    let app = build_router_with_auth(spec_path, options, auth_config).await;
1579    serve_router(port, app).await
1580}
1581
1582/// Start HTTP server with latency injection support
1583pub async fn start_with_latency(
1584    port: u16,
1585    spec_path: Option<String>,
1586    options: Option<ValidationOptions>,
1587    latency_profile: Option<LatencyProfile>,
1588) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1589    let latency_injector =
1590        latency_profile.map(|profile| LatencyInjector::new(profile, Default::default()));
1591
1592    let app = build_router_with_latency(spec_path, options, latency_injector).await;
1593    serve_router(port, app).await
1594}
1595
1596/// Build the base HTTP router with chaining support
1597pub async fn build_router_with_chains(
1598    spec_path: Option<String>,
1599    options: Option<ValidationOptions>,
1600    circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1601) -> Router {
1602    build_router_with_chains_and_multi_tenant(
1603        spec_path,
1604        options,
1605        circling_config,
1606        None,
1607        None,
1608        None,
1609        None,
1610        None,
1611        None,
1612        None,
1613        false,
1614        None, // health_manager
1615        None, // mockai
1616        None, // deceptive_deploy_config
1617        None, // proxy_config
1618    )
1619    .await
1620}
1621
1622// Template expansion is now handled by mockforge_core::template_expansion
1623// which is explicitly isolated from the templating module to avoid Send issues
1624
1625/// Helper function to apply route chaos injection (fault injection and latency)
1626/// This is extracted to avoid capturing RouteChaosInjector in the closure, which causes Send issues
1627/// Uses mockforge-route-chaos crate which is isolated from mockforge-core to avoid Send issues
1628/// Uses the RouteChaosInjectorTrait from mockforge-core to avoid circular dependency
1629async fn apply_route_chaos(
1630    injector: Option<&dyn mockforge_core::priority_handler::RouteChaosInjectorTrait>,
1631    method: &http::Method,
1632    uri: &http::Uri,
1633) -> Option<axum::response::Response> {
1634    use axum::http::StatusCode;
1635    use axum::response::IntoResponse;
1636
1637    if let Some(injector) = injector {
1638        // Check for fault injection first
1639        if let Some(fault_response) = injector.get_fault_response(method, uri) {
1640            // Return fault response
1641            let mut response = Json(serde_json::json!({
1642                "error": fault_response.error_message,
1643                "fault_type": fault_response.fault_type,
1644            }))
1645            .into_response();
1646            *response.status_mut() = StatusCode::from_u16(fault_response.status_code)
1647                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
1648            return Some(response);
1649        }
1650
1651        // Inject latency if configured (this is async and may delay the request)
1652        if let Err(e) = injector.inject_latency(method, uri).await {
1653            tracing::warn!("Failed to inject latency: {}", e);
1654        }
1655    }
1656
1657    None // No fault response, processing should continue
1658}
1659
1660/// Build the base HTTP router with chaining and multi-tenant support
1661#[allow(clippy::too_many_arguments)]
1662#[allow(deprecated)] // uses core engines (DriftBudgetEngine, ThreatAnalyzer, Forecaster, ProtocolContractRegistry, MockAI, MultiTenantWorkspaceRegistry, etc.) that stay in core
1663pub async fn build_router_with_chains_and_multi_tenant(
1664    spec_path: Option<String>,
1665    options: Option<ValidationOptions>,
1666    _circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1667    multi_tenant_config: Option<mockforge_foundation::multi_tenant_types::MultiTenantConfig>,
1668    route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
1669    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
1670    _ai_generator: Option<Arc<dyn mockforge_openapi::response::AiGenerator + Send + Sync>>,
1671    smtp_registry: Option<Arc<dyn std::any::Any + Send + Sync>>,
1672    mqtt_broker: Option<Arc<dyn std::any::Any + Send + Sync>>,
1673    traffic_shaper: Option<mockforge_core::traffic_shaping::TrafficShaper>,
1674    traffic_shaping_enabled: bool,
1675    health_manager: Option<Arc<HealthManager>>,
1676    mockai: Option<Arc<RwLock<mockforge_core::intelligent_behavior::MockAI>>>,
1677    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
1678    proxy_config: Option<mockforge_proxy::config::ProxyConfig>,
1679) -> Router {
1680    use crate::latency_profiles::LatencyProfiles;
1681    use crate::op_middleware::Shared;
1682    use mockforge_core::Overrides;
1683
1684    // Extract template expansion setting before options is moved (used in OpenAPI routes and custom routes)
1685    let template_expand =
1686        options.as_ref().map(|o| o.response_template_expand).unwrap_or_else(|| {
1687            std::env::var("MOCKFORGE_RESPONSE_TEMPLATE_EXPAND")
1688                .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1689                .unwrap_or(false)
1690        });
1691
1692    let _shared = Shared {
1693        profiles: LatencyProfiles::default(),
1694        overrides: Overrides::default(),
1695        failure_injector: None,
1696        traffic_shaper,
1697        overrides_enabled: false,
1698        traffic_shaping_enabled,
1699    };
1700
1701    // Start with basic router
1702    let mut app = Router::new();
1703    let mut include_default_health = true;
1704    let mut captured_routes: Vec<RouteInfo> = Vec::new();
1705
1706    // If an OpenAPI spec is provided, integrate it
1707    if let Some(ref spec) = spec_path {
1708        match OpenApiSpec::from_file(&spec).await {
1709            Ok(openapi) => {
1710                info!("Loaded OpenAPI spec from {}", spec);
1711
1712                // Try to load persona from config if available
1713                let persona = load_persona_from_config().await;
1714
1715                let mut registry = if let Some(opts) = options {
1716                    tracing::debug!("Using custom validation options");
1717                    if let Some(ref persona) = persona {
1718                        tracing::info!("Using persona '{}' for route generation", persona.name);
1719                    }
1720                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
1721                } else {
1722                    tracing::debug!("Using environment-based options");
1723                    if let Some(ref persona) = persona {
1724                        tracing::info!("Using persona '{}' for route generation", persona.name);
1725                    }
1726                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
1727                };
1728
1729                // Load custom fixtures if enabled
1730                let fixtures_dir = std::env::var("MOCKFORGE_FIXTURES_DIR")
1731                    .unwrap_or_else(|_| "/app/fixtures".to_string());
1732                let custom_fixtures_enabled = std::env::var("MOCKFORGE_CUSTOM_FIXTURES_ENABLED")
1733                    .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1734                    .unwrap_or(true); // Enabled by default
1735
1736                if custom_fixtures_enabled {
1737                    use mockforge_openapi::CustomFixtureLoader;
1738                    use std::path::PathBuf;
1739                    use std::sync::Arc;
1740
1741                    let fixtures_path = PathBuf::from(&fixtures_dir);
1742                    let mut custom_loader = CustomFixtureLoader::new(fixtures_path, true);
1743
1744                    if let Err(e) = custom_loader.load_fixtures().await {
1745                        tracing::warn!("Failed to load custom fixtures: {}", e);
1746                    } else {
1747                        tracing::info!("Custom fixtures loaded from {}", fixtures_dir);
1748                        registry = registry.with_custom_fixture_loader(Arc::new(custom_loader));
1749                    }
1750                }
1751
1752                if registry
1753                    .routes()
1754                    .iter()
1755                    .any(|route| route.method == "GET" && route.path == "/health")
1756                {
1757                    include_default_health = false;
1758                }
1759                // Capture route info for the /__mockforge/routes endpoint
1760                captured_routes = registry
1761                    .routes()
1762                    .iter()
1763                    .map(|r| RouteInfo {
1764                        method: r.method.clone(),
1765                        path: r.path.clone(),
1766                        operation_id: r.operation.operation_id.clone(),
1767                        summary: r.operation.summary.clone(),
1768                        description: r.operation.description.clone(),
1769                        parameters: r.parameters.clone(),
1770                    })
1771                    .collect();
1772
1773                // Store routes in the global route store so the admin server
1774                // can serve them without proxying back to the HTTP server.
1775                {
1776                    let global_routes: Vec<mockforge_core::request_logger::GlobalRouteInfo> =
1777                        captured_routes
1778                            .iter()
1779                            .map(|r| mockforge_core::request_logger::GlobalRouteInfo {
1780                                method: r.method.clone(),
1781                                path: r.path.clone(),
1782                                operation_id: r.operation_id.clone(),
1783                                summary: r.summary.clone(),
1784                                description: r.description.clone(),
1785                                parameters: r.parameters.clone(),
1786                            })
1787                            .collect();
1788                    mockforge_core::request_logger::set_global_routes(global_routes);
1789                    tracing::info!("Stored {} routes in global route store", captured_routes.len());
1790                }
1791
1792                // Use MockAI if available, otherwise use standard router
1793                let spec_router = if let Some(ref mockai_instance) = mockai {
1794                    tracing::debug!("Building router with MockAI support");
1795                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
1796                } else {
1797                    registry.build_router()
1798                };
1799                app = app.merge(spec_router);
1800            }
1801            Err(e) => {
1802                warn!("Failed to load OpenAPI spec from {:?}: {}. Starting without OpenAPI integration.", spec_path, e);
1803            }
1804        }
1805    }
1806
1807    // Register custom routes from config with advanced routing features
1808    // Create RouteChaosInjector for advanced fault injection and latency
1809    // Store as trait object to avoid circular dependency (RouteChaosInjectorTrait is in mockforge-core)
1810    let route_chaos_injector: Option<
1811        std::sync::Arc<dyn mockforge_core::priority_handler::RouteChaosInjectorTrait>,
1812    > = if let Some(ref route_configs) = route_configs {
1813        if !route_configs.is_empty() {
1814            // Convert to the type expected by RouteChaosInjector
1815            // Note: Both use the same mockforge-core, but we need to ensure type compatibility
1816            let route_configs_converted: Vec<mockforge_core::config::RouteConfig> =
1817                route_configs.to_vec();
1818            match mockforge_route_chaos::RouteChaosInjector::new(route_configs_converted) {
1819                Ok(injector) => {
1820                    info!(
1821                        "Initialized advanced routing features for {} route(s)",
1822                        route_configs.len()
1823                    );
1824                    // RouteChaosInjector implements RouteChaosInjectorTrait, so we can cast it
1825                    // The trait is implemented in mockforge-route-chaos/src/lib.rs
1826                    Some(std::sync::Arc::new(injector)
1827                        as std::sync::Arc<
1828                            dyn mockforge_core::priority_handler::RouteChaosInjectorTrait,
1829                        >)
1830                }
1831                Err(e) => {
1832                    warn!(
1833                        "Failed to initialize advanced routing features: {}. Using basic routing.",
1834                        e
1835                    );
1836                    None
1837                }
1838            }
1839        } else {
1840            None
1841        }
1842    } else {
1843        None
1844    };
1845
1846    if let Some(route_configs) = route_configs {
1847        use axum::http::StatusCode;
1848        use axum::response::IntoResponse;
1849
1850        if !route_configs.is_empty() {
1851            info!("Registering {} custom route(s) from config", route_configs.len());
1852        }
1853
1854        let injector = route_chaos_injector.clone();
1855        for route_config in route_configs {
1856            let status = route_config.response.status;
1857            let body = route_config.response.body.clone();
1858            let headers = route_config.response.headers.clone();
1859            let path = route_config.path.clone();
1860            let method = route_config.method.clone();
1861
1862            // Create handler that returns the configured response with template expansion
1863            // Supports both basic templates ({{uuid}}, {{now}}) and request-aware templates
1864            // ({{request.query.name}}, {{request.path.id}}, {{request.headers.name}})
1865            // Register route using `any()` since we need full Request access for template expansion
1866            let expected_method = method.to_uppercase();
1867            // Clone Arc for the closure - Arc is Send-safe
1868            // Note: RouteChaosInjector is marked as Send+Sync via unsafe impl, but the compiler
1869            // is conservative because rng() is available in the rand crate, even though we use thread_rng()
1870            let injector_clone = injector.clone();
1871            app = app.route(
1872                &path,
1873                #[allow(clippy::non_send_fields_in_send_ty)]
1874                axum::routing::any(move |req: Request<Body>| {
1875                    let body = body.clone();
1876                    let headers = headers.clone();
1877                    let expand = template_expand;
1878                    let expected = expected_method.clone();
1879                    let status_code = status;
1880                    // Clone Arc again for the async block
1881                    let injector_for_chaos = injector_clone.clone();
1882
1883                    async move {
1884                        // Check if request method matches expected method
1885                        if req.method().as_str() != expected.as_str() {
1886                            // Return 405 Method Not Allowed for wrong method
1887                            return axum::response::Response::builder()
1888                                .status(StatusCode::METHOD_NOT_ALLOWED)
1889                                .header("Allow", &expected)
1890                                .body(Body::empty())
1891                                .unwrap()
1892                                .into_response();
1893                        }
1894
1895                        // Apply advanced routing features (fault injection and latency) if available
1896                        // Use helper function to avoid capturing RouteChaosInjector in closure
1897                        // Pass the Arc as a reference to the helper function
1898                        if let Some(fault_response) = apply_route_chaos(
1899                            injector_for_chaos.as_deref(),
1900                            req.method(),
1901                            req.uri(),
1902                        )
1903                        .await
1904                        {
1905                            return fault_response;
1906                        }
1907
1908                        // Create JSON response from body, or empty object if None
1909                        let mut body_value = body.unwrap_or(serde_json::json!({}));
1910
1911                        // Apply template expansion if enabled
1912                        // Use mockforge-template-expansion crate which is completely isolated
1913                        // from mockforge-core to avoid Send issues (no rng() in dependency chain)
1914                        if expand {
1915                            use mockforge_template_expansion::RequestContext;
1916                            use serde_json::Value;
1917                            use std::collections::HashMap;
1918
1919                            // Extract request data for template expansion
1920                            let method = req.method().to_string();
1921                            let path = req.uri().path().to_string();
1922
1923                            // Extract query parameters
1924                            let query_params: HashMap<String, Value> = req
1925                                .uri()
1926                                .query()
1927                                .map(|q| {
1928                                    url::form_urlencoded::parse(q.as_bytes())
1929                                        .into_owned()
1930                                        .map(|(k, v)| (k, Value::String(v)))
1931                                        .collect()
1932                                })
1933                                .unwrap_or_default();
1934
1935                            // Extract headers
1936                            let headers: HashMap<String, Value> = req
1937                                .headers()
1938                                .iter()
1939                                .map(|(k, v)| {
1940                                    (
1941                                        k.to_string(),
1942                                        Value::String(v.to_str().unwrap_or_default().to_string()),
1943                                    )
1944                                })
1945                                .collect();
1946
1947                            // Create RequestContext for expand_prompt_template
1948                            // Using RequestContext from mockforge-template-expansion (not mockforge-core)
1949                            // to avoid bringing rng() into scope
1950                            let context = RequestContext {
1951                                method,
1952                                path,
1953                                query_params,
1954                                headers,
1955                                body: None, // Body extraction would require reading the request stream
1956                                path_params: HashMap::new(),
1957                                multipart_fields: HashMap::new(),
1958                                multipart_files: HashMap::new(),
1959                            };
1960
1961                            // Perform template expansion in spawn_blocking to ensure Send safety
1962                            // The template expansion crate is completely isolated from mockforge-core
1963                            // and doesn't have rng() in its dependency chain
1964                            let body_value_clone = body_value.clone();
1965                            let context_clone = context.clone();
1966                            body_value = match tokio::task::spawn_blocking(move || {
1967                                mockforge_template_expansion::expand_templates_in_json(
1968                                    body_value_clone,
1969                                    &context_clone,
1970                                )
1971                            })
1972                            .await
1973                            {
1974                                Ok(result) => result,
1975                                Err(_) => body_value, // Fallback to original on error
1976                            };
1977                        }
1978
1979                        let mut response = Json(body_value).into_response();
1980
1981                        // Set status code
1982                        *response.status_mut() =
1983                            StatusCode::from_u16(status_code).unwrap_or(StatusCode::OK);
1984
1985                        // Add custom headers
1986                        for (key, value) in headers {
1987                            if let Ok(header_name) = http::HeaderName::from_bytes(key.as_bytes()) {
1988                                if let Ok(header_value) = http::HeaderValue::from_str(&value) {
1989                                    response.headers_mut().insert(header_name, header_value);
1990                                }
1991                            }
1992                        }
1993
1994                        response
1995                    }
1996                }),
1997            );
1998
1999            debug!("Registered route: {} {}", method, path);
2000        }
2001    }
2002
2003    // Add health check endpoints
2004    if let Some(health) = health_manager {
2005        // Use comprehensive health check router with all probe endpoints
2006        app = app.merge(health::health_router(health));
2007        info!(
2008            "Health check endpoints enabled: /health, /health/live, /health/ready, /health/startup"
2009        );
2010    } else if include_default_health {
2011        // Fallback to basic health endpoint for backwards compatibility
2012        app = app.route(
2013            "/health",
2014            axum::routing::get(|| async {
2015                use mockforge_core::server_utils::health::HealthStatus;
2016                {
2017                    // HealthStatus should always serialize, but handle errors gracefully
2018                    match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
2019                        Ok(value) => Json(value),
2020                        Err(e) => {
2021                            // Log error but return a simple healthy response
2022                            tracing::error!("Failed to serialize health status: {}", e);
2023                            Json(serde_json::json!({
2024                                "status": "healthy",
2025                                "service": "mockforge-http",
2026                                "uptime_seconds": 0
2027                            }))
2028                        }
2029                    }
2030                }
2031            }),
2032        );
2033    }
2034
2035    app = app.merge(sse::sse_router());
2036    // Add file serving endpoints for generated mock files
2037    app = app.merge(file_server::file_serving_router());
2038
2039    // Add management API endpoints
2040    // Load spec for ManagementState so /__mockforge/api/spec can serve it
2041    let mgmt_spec = if let Some(ref sp) = spec_path {
2042        match OpenApiSpec::from_file(sp).await {
2043            Ok(s) => Some(Arc::new(s)),
2044            Err(e) => {
2045                debug!("Failed to load OpenAPI spec for management API: {}", e);
2046                None
2047            }
2048        }
2049    } else {
2050        None
2051    };
2052    let spec_path_clone = spec_path.clone();
2053    let mgmt_port = std::env::var("PORT")
2054        .or_else(|_| std::env::var("MOCKFORGE_HTTP_PORT"))
2055        .ok()
2056        .and_then(|p| p.parse().ok())
2057        .unwrap_or(3000);
2058    let management_state = ManagementState::new(mgmt_spec, spec_path_clone, mgmt_port);
2059
2060    // Create WebSocket state and connect it to management state
2061    use std::sync::Arc;
2062    let ws_state = WsManagementState::new();
2063    let ws_broadcast = Arc::new(ws_state.tx.clone());
2064    let management_state = management_state.with_ws_broadcast(ws_broadcast);
2065
2066    // Add proxy config to management state if available
2067    let management_state = if let Some(proxy_cfg) = proxy_config {
2068        use tokio::sync::RwLock;
2069        let proxy_config_arc = Arc::new(RwLock::new(proxy_cfg));
2070        management_state.with_proxy_config(proxy_config_arc)
2071    } else {
2072        management_state
2073    };
2074
2075    #[cfg(feature = "smtp")]
2076    let management_state = {
2077        if let Some(smtp_reg) = smtp_registry {
2078            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
2079                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
2080                Err(e) => {
2081                    error!(
2082                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
2083                        e.type_id()
2084                    );
2085                    management_state
2086                }
2087            }
2088        } else {
2089            management_state
2090        }
2091    };
2092    #[cfg(not(feature = "smtp"))]
2093    let management_state = {
2094        let _ = smtp_registry;
2095        management_state
2096    };
2097    #[cfg(feature = "mqtt")]
2098    let management_state = {
2099        if let Some(broker) = mqtt_broker {
2100            match broker.downcast::<mockforge_mqtt::MqttBroker>() {
2101                Ok(broker) => management_state.with_mqtt_broker(broker),
2102                Err(e) => {
2103                    error!(
2104                        "Invalid MQTT broker passed to HTTP management state: {:?}",
2105                        e.type_id()
2106                    );
2107                    management_state
2108                }
2109            }
2110        } else {
2111            management_state
2112        }
2113    };
2114    #[cfg(not(feature = "mqtt"))]
2115    let management_state = {
2116        let _ = mqtt_broker;
2117        management_state
2118    };
2119    let management_state_for_fallback = management_state.clone();
2120    app = app.nest("/__mockforge/api", management_router(management_state));
2121    // Dynamic-mock fallback; see identical block earlier in this file.
2122    app = app.fallback_service(
2123        axum::routing::any(management::dynamic_mock_fallback)
2124            .with_state(management_state_for_fallback),
2125    );
2126
2127    // Add verification API endpoint
2128    app = app.merge(verification_router());
2129
2130    // Mount the request-chains management API at the prefix the UI's
2131    // ChainsPage expects (`/__mockforge/chains/*`). Without this nest the
2132    // page 404s on every call. The registry/engine are constructed from
2133    // the chain_config param when supplied, otherwise from defaults.
2134    {
2135        use crate::chain_handlers::{chains_router, create_chain_state};
2136        let chain_config = _circling_config.clone().unwrap_or_default();
2137        let chain_registry = Arc::new(mockforge_core::request_chaining::RequestChainRegistry::new(
2138            chain_config.clone(),
2139        ));
2140        let chain_engine = Arc::new(mockforge_core::chain_execution::ChainExecutionEngine::new(
2141            chain_registry.clone(),
2142            chain_config,
2143        ));
2144        app = app.nest(
2145            "/__mockforge/chains",
2146            chains_router(create_chain_state(chain_registry, chain_engine)),
2147        );
2148    }
2149
2150    // Contract-diff retrieval API. The `capture_for_contract_diff`
2151    // middleware (layered earlier) populates the global capture manager
2152    // on every request; this surface lets operators read the captures
2153    // and run the analyser against the current OpenAPI spec.
2154    {
2155        use crate::contract_diff_api::{contract_diff_api_router, ContractDiffApiState};
2156        let cd_state = Arc::new(ContractDiffApiState::new(spec_path.clone()));
2157        app = app.nest("/__mockforge/api/contract-diff", contract_diff_api_router(cd_state));
2158    }
2159
2160    // Fixtures management API. Lives on the admin server too (port 9080),
2161    // but hosted-mock Fly machines only expose port 3000 publicly — so
2162    // we mount the same surface on the main HTTP app. Newly-uploaded
2163    // fixtures land in `MOCKFORGE_FIXTURES_DIR` and are picked up by
2164    // the startup-time loader on the next deploy/restart; live reload
2165    // is a separate concern.
2166    {
2167        use crate::fixtures_api::{fixtures_api_router, FixturesApiState};
2168        let fx_state = FixturesApiState::from_env();
2169        app = app.nest("/__mockforge/fixtures", fixtures_api_router(fx_state));
2170    }
2171
2172    // Standalone MockAI API. Until now MockAI was only invoked from the
2173    // OpenAPI route generator — so a hosted mock without a spec, or a
2174    // user wanting to prototype an "AI persona" mock without registering
2175    // routes, had no way to reach the engine. The endpoint surfaces 503
2176    // when MockAI isn't configured (no API key) so callers get a clear
2177    // signal rather than a silent failure.
2178    {
2179        use crate::mockai_api::{mockai_api_router, MockAiApiState};
2180        let api_state = MockAiApiState::new(mockai.clone());
2181        app = app.nest("/__mockforge/api/mockai", mockai_api_router(api_state));
2182    }
2183
2184    // Time-travel runtime API. The admin server mounts these routes on
2185    // its own port (9080), but hosted-mock Fly machines only expose
2186    // port 3000 publicly — so until now operators couldn't set / advance
2187    // virtual time on a deployed mock. The handlers consult a process-
2188    // wide TimeTravelManager that serve.rs initialises alongside the
2189    // existing admin-server registration; both paths see the same Arc.
2190    app = app.nest("/__mockforge/time-travel", time_travel_api::time_travel_router());
2191
2192    // Runtime route-chaos rules API + middleware. This sits in front of
2193    // the static per-route handlers so operators can add/remove fault and
2194    // latency rules without redeploying. Static rules from the YAML
2195    // config still apply to their routes; runtime rules are additive.
2196    {
2197        use crate::route_chaos_runtime::{
2198            route_chaos_api_router, runtime_route_chaos_middleware, RuntimeRouteChaosState,
2199        };
2200        let runtime_state = RuntimeRouteChaosState::new(Vec::new());
2201        let middleware_state = runtime_state.clone();
2202        app = app.layer(from_fn_with_state(middleware_state, runtime_route_chaos_middleware));
2203        app = app.nest("/__mockforge/api/route-chaos", route_chaos_api_router(runtime_state));
2204    }
2205
2206    // Runtime network-profile switching. Operators activate a named
2207    // catalog profile (e.g., "mobile_3g") and the middleware applies its
2208    // latency to every subsequent request — useful for "what does my
2209    // consumer look like under bad network" probes against a live
2210    // hosted mock without a redeploy.
2211    {
2212        use crate::network_profile_runtime::{
2213            network_profile_api_router, network_profile_middleware, NetworkProfileRuntimeState,
2214        };
2215        let runtime_state = NetworkProfileRuntimeState::new(
2216            mockforge_core::network_profiles::NetworkProfileCatalog::new(),
2217        );
2218        let middleware_state = runtime_state.clone();
2219        app = app.layer(from_fn_with_state(middleware_state, network_profile_middleware));
2220        app = app
2221            .nest("/__mockforge/api/network-profiles", network_profile_api_router(runtime_state));
2222    }
2223
2224    // Add OIDC well-known endpoints
2225    use crate::auth::oidc::oidc_router;
2226    app = app.merge(oidc_router());
2227
2228    // Add access review API if enabled
2229    {
2230        use mockforge_core::security::get_global_access_review_service;
2231        if let Some(service) = get_global_access_review_service().await {
2232            use crate::handlers::access_review::{access_review_router, AccessReviewState};
2233            let review_state = AccessReviewState { service };
2234            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
2235            debug!("Access review API mounted at /api/v1/security/access-reviews");
2236        }
2237    }
2238
2239    // Add privileged access API if enabled
2240    {
2241        use mockforge_core::security::get_global_privileged_access_manager;
2242        if let Some(manager) = get_global_privileged_access_manager().await {
2243            use crate::handlers::privileged_access::{
2244                privileged_access_router, PrivilegedAccessState,
2245            };
2246            let privileged_state = PrivilegedAccessState { manager };
2247            app = app.nest(
2248                "/api/v1/security/privileged-access",
2249                privileged_access_router(privileged_state),
2250            );
2251            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
2252        }
2253    }
2254
2255    // Add change management API if enabled
2256    {
2257        use mockforge_core::security::get_global_change_management_engine;
2258        if let Some(engine) = get_global_change_management_engine().await {
2259            use crate::handlers::change_management::{
2260                change_management_router, ChangeManagementState,
2261            };
2262            let change_state = ChangeManagementState { engine };
2263            app = app.nest("/api/v1/change-management", change_management_router(change_state));
2264            debug!("Change management API mounted at /api/v1/change-management");
2265        }
2266    }
2267
2268    // Add risk assessment API if enabled
2269    {
2270        use mockforge_core::security::get_global_risk_assessment_engine;
2271        if let Some(engine) = get_global_risk_assessment_engine().await {
2272            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
2273            let risk_state = RiskAssessmentState { engine };
2274            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
2275            debug!("Risk assessment API mounted at /api/v1/security/risks");
2276        }
2277    }
2278
2279    // Add token lifecycle API
2280    {
2281        use crate::auth::token_lifecycle::TokenLifecycleManager;
2282        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
2283        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2284        let lifecycle_state = TokenLifecycleState {
2285            manager: lifecycle_manager,
2286        };
2287        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
2288        debug!("Token lifecycle API mounted at /api/v1/auth");
2289    }
2290
2291    // Add OAuth2 server endpoints
2292    {
2293        use crate::auth::oidc::load_oidc_state;
2294        use crate::auth::token_lifecycle::TokenLifecycleManager;
2295        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
2296        // Load OIDC state from configuration (environment variables or config file)
2297        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
2298        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2299        let oauth2_state = OAuth2ServerState {
2300            oidc_state,
2301            lifecycle_manager,
2302            auth_codes: Arc::new(RwLock::new(HashMap::new())),
2303            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
2304        };
2305        app = app.merge(oauth2_server_router(oauth2_state));
2306        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
2307    }
2308
2309    // Add consent screen endpoints
2310    {
2311        use crate::auth::oidc::load_oidc_state;
2312        use crate::auth::risk_engine::RiskEngine;
2313        use crate::auth::token_lifecycle::TokenLifecycleManager;
2314        use crate::handlers::consent::{consent_router, ConsentState};
2315        use crate::handlers::oauth2_server::OAuth2ServerState;
2316        // Load OIDC state from configuration (environment variables or config file)
2317        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
2318        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2319        let oauth2_state = OAuth2ServerState {
2320            oidc_state: oidc_state.clone(),
2321            lifecycle_manager: lifecycle_manager.clone(),
2322            auth_codes: Arc::new(RwLock::new(HashMap::new())),
2323            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
2324        };
2325        let risk_engine = Arc::new(RiskEngine::default());
2326        let consent_state = ConsentState {
2327            oauth2_state,
2328            risk_engine,
2329        };
2330        app = app.merge(consent_router(consent_state));
2331        debug!("Consent screen endpoints mounted at /consent");
2332    }
2333
2334    // Add risk simulation API
2335    {
2336        use crate::auth::risk_engine::RiskEngine;
2337        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
2338        let risk_engine = Arc::new(RiskEngine::default());
2339        let risk_state = RiskSimulationState { risk_engine };
2340        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
2341        debug!("Risk simulation API mounted at /api/v1/auth/risk");
2342    }
2343
2344    // Initialize database connection (optional)
2345    let database = {
2346        use crate::database::Database;
2347        let database_url = std::env::var("DATABASE_URL").ok();
2348        match Database::connect_optional(database_url.as_deref()).await {
2349            Ok(db) => {
2350                if db.is_connected() {
2351                    // Run migrations if database is connected
2352                    if let Err(e) = db.migrate_if_connected().await {
2353                        warn!("Failed to run database migrations: {}", e);
2354                    } else {
2355                        info!("Database connected and migrations applied");
2356                    }
2357                }
2358                Some(db)
2359            }
2360            Err(e) => {
2361                warn!("Failed to connect to database: {}. Continuing without database support.", e);
2362                None
2363            }
2364        }
2365    };
2366
2367    // Add drift budget and incident management endpoints
2368    // Initialize shared components for drift tracking and protocol contracts
2369    let (drift_engine, incident_manager, drift_config) = {
2370        use mockforge_core::contract_drift::{DriftBudgetConfig, DriftBudgetEngine};
2371        use mockforge_core::incidents::{IncidentManager, IncidentStore};
2372        use std::sync::Arc;
2373
2374        // Initialize drift budget engine with default config
2375        let drift_config = DriftBudgetConfig::default();
2376        let drift_engine = Arc::new(DriftBudgetEngine::new(drift_config.clone()));
2377
2378        // Initialize incident store and manager
2379        let incident_store = Arc::new(IncidentStore::default());
2380        let incident_manager = Arc::new(IncidentManager::new(incident_store.clone()));
2381
2382        (drift_engine, incident_manager, drift_config)
2383    };
2384
2385    {
2386        use crate::handlers::drift_budget::{drift_budget_router, DriftBudgetState};
2387        use crate::middleware::drift_tracking::DriftTrackingState;
2388        use mockforge_contracts::consumer_contracts::{
2389            ConsumerBreakingChangeDetector, UsageRecorder,
2390        };
2391        use mockforge_core::ai_contract_diff::ContractDiffAnalyzer;
2392        use std::sync::Arc;
2393
2394        // Initialize usage recorder and consumer detector
2395        let usage_recorder = Arc::new(UsageRecorder::default());
2396        let consumer_detector =
2397            Arc::new(ConsumerBreakingChangeDetector::new(usage_recorder.clone()));
2398
2399        // Initialize contract diff analyzer if enabled
2400        let diff_analyzer = if drift_config.enabled {
2401            match ContractDiffAnalyzer::new(
2402                mockforge_core::ai_contract_diff::ContractDiffConfig::default(),
2403            ) {
2404                Ok(analyzer) => Some(Arc::new(analyzer)),
2405                Err(e) => {
2406                    warn!("Failed to create contract diff analyzer: {}", e);
2407                    None
2408                }
2409            }
2410        } else {
2411            None
2412        };
2413
2414        // Get OpenAPI spec if available
2415        // Note: Load from spec_path if available, or leave as None for manual configuration.
2416        let spec = if let Some(ref spec_path) = spec_path {
2417            match OpenApiSpec::from_file(spec_path).await {
2418                Ok(s) => Some(Arc::new(s)),
2419                Err(e) => {
2420                    debug!("Failed to load OpenAPI spec for drift tracking: {}", e);
2421                    None
2422                }
2423            }
2424        } else {
2425            None
2426        };
2427
2428        // Create drift tracking state
2429        let drift_tracking_state = DriftTrackingState {
2430            diff_analyzer,
2431            spec,
2432            drift_engine: drift_engine.clone(),
2433            incident_manager: incident_manager.clone(),
2434            usage_recorder,
2435            consumer_detector,
2436            enabled: drift_config.enabled,
2437        };
2438
2439        // Add response body buffering middleware (before drift tracking)
2440        app = app.layer(axum::middleware::from_fn(middleware::buffer_response_middleware));
2441
2442        // Add drift tracking middleware (after response buffering)
2443        // Use a wrapper that inserts state into extensions before calling the middleware
2444        let drift_tracking_state_clone = drift_tracking_state.clone();
2445        app = app.layer(axum::middleware::from_fn(
2446            move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2447                let state = drift_tracking_state_clone.clone();
2448                async move {
2449                    // Insert state into extensions if not already present
2450                    if req.extensions().get::<DriftTrackingState>().is_none() {
2451                        req.extensions_mut().insert(state);
2452                    }
2453                    // Call the middleware function
2454                    middleware::drift_tracking::drift_tracking_middleware_with_extensions(req, next)
2455                        .await
2456                }
2457            },
2458        ));
2459
2460        let drift_state = DriftBudgetState {
2461            engine: drift_engine.clone(),
2462            incident_manager: incident_manager.clone(),
2463            gitops_handler: None, // Can be initialized later if GitOps is configured
2464        };
2465
2466        app = app.merge(drift_budget_router(drift_state));
2467        debug!("Drift budget and incident management endpoints mounted at /api/v1/drift");
2468    }
2469
2470    // Add pipeline management endpoints (MockOps)
2471    #[cfg(feature = "pipelines")]
2472    {
2473        use crate::handlers::pipelines::{pipeline_router, PipelineState};
2474
2475        let pipeline_state = PipelineState::new();
2476        app = app.merge(pipeline_router(pipeline_state));
2477        debug!("Pipeline management endpoints mounted at /api/v1/pipelines");
2478    }
2479
2480    // Add governance endpoints (forecasting, semantic drift, threat modeling, contract health)
2481    {
2482        use crate::handlers::contract_health::{contract_health_router, ContractHealthState};
2483        use crate::handlers::forecasting::{forecasting_router, ForecastingState};
2484        use crate::handlers::semantic_drift::{semantic_drift_router, SemanticDriftState};
2485        use crate::handlers::threat_modeling::{threat_modeling_router, ThreatModelingState};
2486        use mockforge_contracts::contract_drift::forecasting::{Forecaster, ForecastingConfig};
2487        use mockforge_core::contract_drift::threat_modeling::ThreatAnalyzer;
2488        use mockforge_core::incidents::semantic_manager::SemanticIncidentManager;
2489        use mockforge_foundation::threat_modeling_types::ThreatModelingConfig;
2490        use std::sync::Arc;
2491
2492        // Initialize forecasting
2493        let forecasting_config = ForecastingConfig::default();
2494        let forecaster = Arc::new(Forecaster::new(forecasting_config));
2495        let forecasting_state = ForecastingState {
2496            forecaster,
2497            database: database.clone(),
2498        };
2499
2500        // Initialize semantic drift manager
2501        let semantic_manager = Arc::new(SemanticIncidentManager::new());
2502        let semantic_state = SemanticDriftState {
2503            manager: semantic_manager,
2504            database: database.clone(),
2505        };
2506
2507        // Initialize threat analyzer
2508        let threat_config = ThreatModelingConfig::default();
2509        let threat_analyzer = match ThreatAnalyzer::new(threat_config) {
2510            Ok(analyzer) => Arc::new(analyzer),
2511            Err(e) => {
2512                warn!("Failed to create threat analyzer: {}. Using default.", e);
2513                Arc::new(ThreatAnalyzer::new(ThreatModelingConfig::default()).unwrap_or_else(
2514                    |_| {
2515                        // Fallback to a minimal config if default also fails
2516                        ThreatAnalyzer::new(ThreatModelingConfig {
2517                            enabled: false,
2518                            ..Default::default()
2519                        })
2520                        .expect("Failed to create fallback threat analyzer")
2521                    },
2522                ))
2523            }
2524        };
2525        // Load webhook configs from ServerConfig
2526        let mut webhook_configs = Vec::new();
2527        let config_paths = [
2528            "config.yaml",
2529            "mockforge.yaml",
2530            "tools/mockforge/config.yaml",
2531            "../tools/mockforge/config.yaml",
2532        ];
2533
2534        for path in &config_paths {
2535            if let Ok(config) = mockforge_core::config::load_config(path).await {
2536                if !config.incidents.webhooks.is_empty() {
2537                    webhook_configs = config.incidents.webhooks.clone();
2538                    info!("Loaded {} webhook configs from config: {}", webhook_configs.len(), path);
2539                    break;
2540                }
2541            }
2542        }
2543
2544        if webhook_configs.is_empty() {
2545            debug!("No webhook configs found in config files, using empty list");
2546        }
2547
2548        let threat_state = ThreatModelingState {
2549            analyzer: threat_analyzer,
2550            webhook_configs,
2551            database: database.clone(),
2552        };
2553
2554        // Initialize contract health state
2555        let contract_health_state = ContractHealthState {
2556            incident_manager: incident_manager.clone(),
2557            semantic_manager: Arc::new(SemanticIncidentManager::new()),
2558            database: database.clone(),
2559        };
2560
2561        // Register routers
2562        app = app.merge(forecasting_router(forecasting_state));
2563        debug!("Forecasting endpoints mounted at /api/v1/forecasts");
2564
2565        app = app.merge(semantic_drift_router(semantic_state));
2566        debug!("Semantic drift endpoints mounted at /api/v1/semantic-drift");
2567
2568        app = app.merge(threat_modeling_router(threat_state));
2569        debug!("Threat modeling endpoints mounted at /api/v1/threats");
2570
2571        app = app.merge(contract_health_router(contract_health_state));
2572        debug!("Contract health endpoints mounted at /api/v1/contract-health");
2573    }
2574
2575    // Add protocol contracts endpoints with fitness registry initialization
2576    {
2577        use crate::handlers::protocol_contracts::{
2578            protocol_contracts_router, ProtocolContractState,
2579        };
2580        use mockforge_core::contract_drift::{
2581            ConsumerImpactAnalyzer, FitnessFunctionRegistry, ProtocolContractRegistry,
2582        };
2583        use std::sync::Arc;
2584        use tokio::sync::RwLock;
2585
2586        // Initialize protocol contract registry
2587        let contract_registry = Arc::new(RwLock::new(ProtocolContractRegistry::new()));
2588
2589        // Initialize fitness function registry and load from config
2590        let mut fitness_registry = FitnessFunctionRegistry::new();
2591
2592        // Try to load config and populate fitness rules
2593        let config_paths = [
2594            "config.yaml",
2595            "mockforge.yaml",
2596            "tools/mockforge/config.yaml",
2597            "../tools/mockforge/config.yaml",
2598        ];
2599
2600        let mut config_loaded = false;
2601        for path in &config_paths {
2602            if let Ok(config) = mockforge_core::config::load_config(path).await {
2603                if !config.contracts.fitness_rules.is_empty() {
2604                    if let Err(e) =
2605                        fitness_registry.load_from_config(&config.contracts.fitness_rules)
2606                    {
2607                        warn!("Failed to load fitness rules from config {}: {}", path, e);
2608                    } else {
2609                        info!(
2610                            "Loaded {} fitness rules from config: {}",
2611                            config.contracts.fitness_rules.len(),
2612                            path
2613                        );
2614                        config_loaded = true;
2615                        break;
2616                    }
2617                }
2618            }
2619        }
2620
2621        if !config_loaded {
2622            debug!("No fitness rules found in config files, using empty registry");
2623        }
2624
2625        let fitness_registry = Arc::new(RwLock::new(fitness_registry));
2626
2627        // Reuse drift engine and incident manager from drift budget section
2628
2629        // Initialize consumer impact analyzer
2630        let consumer_mapping_registry =
2631            mockforge_core::contract_drift::ConsumerMappingRegistry::new();
2632        let consumer_analyzer =
2633            Arc::new(RwLock::new(ConsumerImpactAnalyzer::new(consumer_mapping_registry)));
2634
2635        let protocol_state = ProtocolContractState {
2636            registry: contract_registry,
2637            drift_engine: Some(drift_engine.clone()),
2638            incident_manager: Some(incident_manager.clone()),
2639            fitness_registry: Some(fitness_registry),
2640            consumer_analyzer: Some(consumer_analyzer),
2641        };
2642
2643        app = app.nest("/api/v1/contracts", protocol_contracts_router(protocol_state));
2644        debug!("Protocol contracts endpoints mounted at /api/v1/contracts");
2645    }
2646
2647    // Add behavioral cloning middleware (optional - applies learned behavior to requests)
2648    #[cfg(feature = "behavioral-cloning")]
2649    {
2650        use crate::middleware::behavioral_cloning::BehavioralCloningMiddlewareState;
2651        use std::path::PathBuf;
2652
2653        // Determine database path (defaults to ./recordings.db)
2654        let db_path = std::env::var("RECORDER_DATABASE_PATH")
2655            .ok()
2656            .map(PathBuf::from)
2657            .or_else(|| std::env::current_dir().ok().map(|p| p.join("recordings.db")));
2658
2659        let bc_middleware_state = if let Some(path) = db_path {
2660            BehavioralCloningMiddlewareState::with_database_path(path)
2661        } else {
2662            BehavioralCloningMiddlewareState::new()
2663        };
2664
2665        // Only enable if BEHAVIORAL_CLONING_ENABLED is set to true
2666        let enabled = std::env::var("BEHAVIORAL_CLONING_ENABLED")
2667            .ok()
2668            .and_then(|v| v.parse::<bool>().ok())
2669            .unwrap_or(false);
2670
2671        if enabled {
2672            let bc_state_clone = bc_middleware_state.clone();
2673            app = app.layer(axum::middleware::from_fn(
2674                move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2675                    let state = bc_state_clone.clone();
2676                    async move {
2677                        // Insert state into extensions if not already present
2678                        if req.extensions().get::<BehavioralCloningMiddlewareState>().is_none() {
2679                            req.extensions_mut().insert(state);
2680                        }
2681                        // Call the middleware function
2682                        crate::middleware::behavioral_cloning::behavioral_cloning_middleware(
2683                            req, next,
2684                        )
2685                        .await
2686                    }
2687                },
2688            ));
2689            debug!("Behavioral cloning middleware enabled (applies learned behavior to requests)");
2690        }
2691    }
2692
2693    // Add consumer contracts endpoints
2694    {
2695        use crate::handlers::consumer_contracts::{
2696            consumer_contracts_router, ConsumerContractsState,
2697        };
2698        use mockforge_contracts::consumer_contracts::{
2699            ConsumerBreakingChangeDetector, ConsumerRegistry, UsageRecorder,
2700        };
2701        use std::sync::Arc;
2702
2703        // Initialize consumer registry
2704        let registry = Arc::new(ConsumerRegistry::default());
2705
2706        // Initialize usage recorder
2707        let usage_recorder = Arc::new(UsageRecorder::default());
2708
2709        // Initialize breaking change detector
2710        let detector = Arc::new(ConsumerBreakingChangeDetector::new(usage_recorder.clone()));
2711
2712        let consumer_state = ConsumerContractsState {
2713            registry,
2714            usage_recorder,
2715            detector,
2716            violations: Arc::new(RwLock::new(HashMap::new())),
2717        };
2718
2719        app = app.merge(consumer_contracts_router(consumer_state));
2720        debug!("Consumer contracts endpoints mounted at /api/v1/consumers");
2721    }
2722
2723    // Add behavioral cloning endpoints
2724    #[cfg(feature = "behavioral-cloning")]
2725    {
2726        use crate::handlers::behavioral_cloning::{
2727            behavioral_cloning_router, BehavioralCloningState,
2728        };
2729        use std::path::PathBuf;
2730
2731        // Determine database path (defaults to ./recordings.db)
2732        let db_path = std::env::var("RECORDER_DATABASE_PATH")
2733            .ok()
2734            .map(PathBuf::from)
2735            .or_else(|| std::env::current_dir().ok().map(|p| p.join("recordings.db")));
2736
2737        let bc_state = if let Some(path) = db_path {
2738            BehavioralCloningState::with_database_path(path)
2739        } else {
2740            BehavioralCloningState::new()
2741        };
2742
2743        app = app.merge(behavioral_cloning_router(bc_state));
2744        debug!("Behavioral cloning endpoints mounted at /api/v1/behavioral-cloning");
2745    }
2746
2747    // Add consistency engine and cross-protocol state management
2748    {
2749        use crate::consistency::{ConsistencyMiddlewareState, HttpAdapter};
2750        use crate::handlers::consistency::{consistency_router, ConsistencyState};
2751        use mockforge_core::consistency::ConsistencyEngine;
2752        use std::sync::Arc;
2753
2754        // Initialize consistency engine
2755        let consistency_engine = Arc::new(ConsistencyEngine::new());
2756
2757        // Create and register HTTP adapter
2758        let http_adapter = Arc::new(HttpAdapter::new(consistency_engine.clone()));
2759        consistency_engine.register_adapter(http_adapter.clone()).await;
2760
2761        // Create consistency state for handlers
2762        let consistency_state = ConsistencyState {
2763            engine: consistency_engine.clone(),
2764        };
2765
2766        // Create X-Ray state first (needed for middleware)
2767        use crate::handlers::xray::XRayState;
2768        let xray_state = Arc::new(XRayState {
2769            engine: consistency_engine.clone(),
2770            request_contexts: std::sync::Arc::new(RwLock::new(HashMap::new())),
2771        });
2772
2773        // Create consistency middleware state
2774        let consistency_middleware_state = ConsistencyMiddlewareState {
2775            engine: consistency_engine.clone(),
2776            adapter: http_adapter,
2777            xray_state: Some(xray_state.clone()),
2778        };
2779
2780        // Reality-driven mock/proxy middleware (#222). Must be added
2781        // BEFORE the consistency layer so it ends up inner — that way
2782        // each request hits consistency first (which injects UnifiedState
2783        // including reality_continuum_ratio), then this middleware reads
2784        // that state and either forwards to upstream or falls through to
2785        // the mock chain. Activated only when MOCKFORGE_PROXY_UPSTREAM is
2786        // set; absent that, the layer isn't even installed.
2787        if let Some(reality_cfg) = reality_proxy::RealityProxyConfig::from_env() {
2788            tracing::info!(
2789                upstream = %reality_cfg.upstream_base,
2790                "Reality-driven proxy middleware enabled — requests will be split between mock and upstream based on reality_continuum_ratio"
2791            );
2792            app = app.layer(axum::middleware::from_fn(
2793                move |req: axum::extract::Request, next: axum::middleware::Next| {
2794                    let cfg = reality_cfg.clone();
2795                    async move { reality_proxy::reality_proxy_middleware(cfg, req, next).await }
2796                },
2797            ));
2798        }
2799
2800        // Add consistency middleware (before other middleware to inject state early)
2801        let consistency_middleware_state_clone = consistency_middleware_state.clone();
2802        app = app.layer(axum::middleware::from_fn(
2803            move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2804                let state = consistency_middleware_state_clone.clone();
2805                async move {
2806                    // Insert state into extensions if not already present
2807                    if req.extensions().get::<ConsistencyMiddlewareState>().is_none() {
2808                        req.extensions_mut().insert(state);
2809                    }
2810                    // Call the middleware function
2811                    consistency::middleware::consistency_middleware(req, next).await
2812                }
2813            },
2814        ));
2815
2816        // Add consistency API endpoints
2817        app = app.merge(consistency_router(consistency_state));
2818        debug!("Consistency engine initialized and endpoints mounted at /api/v1/consistency");
2819
2820        // Runtime named-scenario activation API. Locally-installed
2821        // scenarios (from `~/.mockforge/scenario-metadata/*.json`) become
2822        // listable + activatable via HTTP; activation writes through to
2823        // the consistency engine's active_scenario slot for the chosen
2824        // workspace. Failure to load the scenario index is non-fatal —
2825        // the API just shows an empty list, which matches the natural
2826        // state of a fresh deployment with no scenarios installed yet.
2827        #[cfg(feature = "scenario-engine")]
2828        {
2829            use crate::scenarios_runtime::{scenarios_api_router, ScenarioRuntimeState};
2830            let mut scenario_storage = match mockforge_scenarios::ScenarioStorage::new() {
2831                Ok(s) => s,
2832                Err(e) => {
2833                    tracing::warn!(
2834                        error = %e,
2835                        "Failed to init scenario storage; runtime scenarios API will list empty"
2836                    );
2837                    // Construct an isolated tempdir-backed storage so the
2838                    // API still mounts but lists nothing.
2839                    let tmp = std::env::temp_dir().join("mockforge-empty-scenarios");
2840                    mockforge_scenarios::ScenarioStorage::with_dir(&tmp)
2841                        .expect("temp scenario storage")
2842                }
2843            };
2844            if let Err(e) = scenario_storage.load().await {
2845                tracing::warn!(
2846                    error = %e,
2847                    "Failed to load installed scenarios; API will list empty until scenarios are installed"
2848                );
2849            }
2850            let scenarios_state =
2851                ScenarioRuntimeState::new(scenario_storage, consistency_engine.clone());
2852            app = app.nest("/__mockforge/api/scenarios", scenarios_api_router(scenarios_state));
2853            debug!("Scenario runtime API mounted at /__mockforge/api/scenarios");
2854        }
2855
2856        // Add fidelity score endpoints
2857        {
2858            use crate::handlers::fidelity::{fidelity_router, FidelityState};
2859            let fidelity_state = FidelityState::new();
2860            app = app.merge(fidelity_router(fidelity_state));
2861            debug!("Fidelity score endpoints mounted at /api/v1/workspace/:workspace_id/fidelity");
2862        }
2863
2864        // Add scenario studio endpoints
2865        {
2866            use crate::handlers::scenario_studio::{scenario_studio_router, ScenarioStudioState};
2867            let scenario_studio_state = ScenarioStudioState::new();
2868            app = app.merge(scenario_studio_router(scenario_studio_state));
2869            debug!("Scenario Studio endpoints mounted at /api/v1/scenario-studio");
2870        }
2871
2872        // Add performance mode endpoints
2873        {
2874            use crate::handlers::performance::{performance_router, PerformanceState};
2875            let performance_state = PerformanceState::new();
2876            app = app.nest("/api/performance", performance_router(performance_state));
2877            debug!("Performance mode endpoints mounted at /api/performance");
2878        }
2879
2880        // Add world state endpoints
2881        {
2882            use crate::handlers::world_state::{world_state_router, WorldStateState};
2883            use mockforge_world_state::WorldStateEngine;
2884            use std::sync::Arc;
2885            use tokio::sync::RwLock;
2886
2887            let world_state_engine = Arc::new(RwLock::new(WorldStateEngine::new()));
2888            let world_state_state = WorldStateState {
2889                engine: world_state_engine,
2890            };
2891            app = app.nest("/api/world-state", world_state_router().with_state(world_state_state));
2892            debug!("World state endpoints mounted at /api/world-state");
2893        }
2894
2895        // Add snapshot management endpoints
2896        {
2897            use crate::handlers::snapshots::{snapshot_router, SnapshotState};
2898            use mockforge_core::snapshots::SnapshotManager;
2899            use std::path::PathBuf;
2900
2901            let snapshot_dir = std::env::var("MOCKFORGE_SNAPSHOT_DIR").ok().map(PathBuf::from);
2902            let snapshot_manager = Arc::new(SnapshotManager::new(snapshot_dir));
2903
2904            let snapshot_state = SnapshotState {
2905                manager: snapshot_manager,
2906                consistency_engine: Some(consistency_engine.clone()),
2907                workspace_persistence: None, // Can be initialized later if workspace persistence is available
2908                vbr_engine: None, // Can be initialized when VBR engine is available in server state
2909                recorder: None,   // Can be initialized when Recorder is available in server state
2910            };
2911
2912            app = app.merge(snapshot_router(snapshot_state));
2913            debug!("Snapshot management endpoints mounted at /api/v1/snapshots");
2914
2915            // Add X-Ray API endpoints for browser extension
2916            {
2917                use crate::handlers::xray::xray_router;
2918                app = app.merge(xray_router((*xray_state).clone()));
2919                debug!("X-Ray API endpoints mounted at /api/v1/xray");
2920            }
2921        }
2922
2923        // Add A/B testing endpoints and middleware
2924        {
2925            use crate::handlers::ab_testing::{ab_testing_router, ABTestingState};
2926            use crate::middleware::ab_testing::ab_testing_middleware;
2927
2928            let ab_testing_state = ABTestingState::new();
2929
2930            // Add A/B testing middleware (before other response middleware)
2931            let ab_testing_state_clone = ab_testing_state.clone();
2932            app = app.layer(axum::middleware::from_fn(
2933                move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2934                    let state = ab_testing_state_clone.clone();
2935                    async move {
2936                        // Insert state into extensions if not already present
2937                        if req.extensions().get::<ABTestingState>().is_none() {
2938                            req.extensions_mut().insert(state);
2939                        }
2940                        // Call the middleware function
2941                        ab_testing_middleware(req, next).await
2942                    }
2943                },
2944            ));
2945
2946            // Add A/B testing API endpoints
2947            app = app.merge(ab_testing_router(ab_testing_state));
2948            debug!("A/B testing endpoints mounted at /api/v1/ab-tests");
2949        }
2950    }
2951
2952    // Add PR generation endpoints (optional - only if configured)
2953    {
2954        use crate::handlers::pr_generation::{pr_generation_router, PRGenerationState};
2955        use mockforge_core::pr_generation::{PRGenerator, PRProvider};
2956        use std::sync::Arc;
2957
2958        // Load PR generation config from environment or use default
2959        let pr_config = mockforge_core::pr_generation::PRGenerationConfig::from_env();
2960
2961        let generator = if pr_config.enabled && pr_config.token.is_some() {
2962            let token = pr_config.token.as_ref().unwrap().clone();
2963            let generator = match pr_config.provider {
2964                PRProvider::GitHub => PRGenerator::new_github(
2965                    pr_config.owner.clone(),
2966                    pr_config.repo.clone(),
2967                    token,
2968                    pr_config.base_branch.clone(),
2969                ),
2970                PRProvider::GitLab => PRGenerator::new_gitlab(
2971                    pr_config.owner.clone(),
2972                    pr_config.repo.clone(),
2973                    token,
2974                    pr_config.base_branch.clone(),
2975                ),
2976            };
2977            Some(Arc::new(generator))
2978        } else {
2979            None
2980        };
2981
2982        let pr_state = PRGenerationState {
2983            generator: generator.clone(),
2984        };
2985
2986        app = app.merge(pr_generation_router(pr_state));
2987        if generator.is_some() {
2988            debug!(
2989                "PR generation endpoints mounted at /api/v1/pr (configured for {:?})",
2990                pr_config.provider
2991            );
2992        } else {
2993            debug!("PR generation endpoints mounted at /api/v1/pr (not configured - set GITHUB_TOKEN/GITLAB_TOKEN and PR_REPO_OWNER/PR_REPO_NAME)");
2994        }
2995    }
2996
2997    // Add management WebSocket endpoint
2998    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
2999
3000    // Add workspace routing middleware if multi-tenant is enabled
3001    if let Some(mt_config) = multi_tenant_config {
3002        if mt_config.enabled {
3003            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
3004            use std::sync::Arc;
3005
3006            info!(
3007                "Multi-tenant mode enabled with {} routing strategy",
3008                match mt_config.routing_strategy {
3009                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Path => "path-based",
3010                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Port => "port-based",
3011                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Both => "hybrid",
3012                }
3013            );
3014
3015            // Create the multi-tenant workspace registry
3016            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
3017
3018            // Register the default workspace before wrapping in Arc
3019            let default_workspace =
3020                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
3021            if let Err(e) =
3022                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
3023            {
3024                warn!("Failed to register default workspace: {}", e);
3025            } else {
3026                info!("Registered default workspace: '{}'", mt_config.default_workspace);
3027            }
3028
3029            // Wrap registry in Arc for shared access
3030            let registry = Arc::new(registry);
3031
3032            // Create workspace router
3033            let _workspace_router = WorkspaceRouter::new(registry);
3034            info!("Workspace routing middleware initialized for HTTP server");
3035        }
3036    }
3037
3038    // Apply deceptive deploy configuration if enabled
3039    let mut final_cors_config = cors_config;
3040    let mut production_headers: Option<std::sync::Arc<HashMap<String, String>>> = None;
3041    // Auth config from deceptive deploy OAuth (if configured)
3042    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
3043    let mut rate_limit_config = middleware::RateLimitConfig {
3044        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
3045            .ok()
3046            .and_then(|v| v.parse().ok())
3047            .unwrap_or(1000),
3048        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
3049            .ok()
3050            .and_then(|v| v.parse().ok())
3051            .unwrap_or(2000),
3052        per_ip: true,
3053        per_endpoint: false,
3054    };
3055
3056    if let Some(deploy_config) = &deceptive_deploy_config {
3057        if deploy_config.enabled {
3058            info!("Deceptive deploy mode enabled - applying production-like configuration");
3059
3060            // Override CORS config if provided
3061            if let Some(prod_cors) = &deploy_config.cors {
3062                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
3063                    enabled: true,
3064                    allowed_origins: prod_cors.allowed_origins.clone(),
3065                    allowed_methods: prod_cors.allowed_methods.clone(),
3066                    allowed_headers: prod_cors.allowed_headers.clone(),
3067                    allow_credentials: prod_cors.allow_credentials,
3068                });
3069                info!("Applied production-like CORS configuration");
3070            }
3071
3072            // Override rate limit config if provided
3073            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
3074                rate_limit_config = middleware::RateLimitConfig {
3075                    requests_per_minute: prod_rate_limit.requests_per_minute,
3076                    burst: prod_rate_limit.burst,
3077                    per_ip: prod_rate_limit.per_ip,
3078                    per_endpoint: false,
3079                };
3080                info!(
3081                    "Applied production-like rate limiting: {} req/min, burst: {}",
3082                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
3083                );
3084            }
3085
3086            // Set production headers
3087            if !deploy_config.headers.is_empty() {
3088                let headers_map: HashMap<String, String> = deploy_config.headers.clone();
3089                production_headers = Some(std::sync::Arc::new(headers_map));
3090                info!("Configured {} production headers", deploy_config.headers.len());
3091            }
3092
3093            // Integrate OAuth config from deceptive deploy
3094            if let Some(prod_oauth) = &deploy_config.oauth {
3095                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
3096                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
3097                    oauth2: Some(oauth2_config),
3098                    ..Default::default()
3099                });
3100                info!("Applied production-like OAuth configuration for deceptive deploy");
3101            }
3102        }
3103    }
3104
3105    // Initialize rate limiter and state
3106    let rate_limit_disabled = middleware::is_rate_limit_disabled();
3107    let rate_limiter =
3108        std::sync::Arc::new(middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
3109
3110    let mut state = HttpServerState::new();
3111    if rate_limit_disabled {
3112        info!(
3113            "HTTP rate limiting disabled (MOCKFORGE_RATE_LIMIT_ENABLED=false or --no-rate-limit)"
3114        );
3115    } else {
3116        state = state.with_rate_limiter(rate_limiter.clone());
3117    }
3118
3119    // Add production headers to state if configured
3120    if let Some(headers) = production_headers.clone() {
3121        state = state.with_production_headers(headers);
3122    }
3123
3124    // Add rate limiting middleware (no-op when state.rate_limiter is None)
3125    app = app.layer(from_fn_with_state(state.clone(), middleware::rate_limit_middleware));
3126
3127    // Add production headers middleware if configured
3128    if state.production_headers.is_some() {
3129        app =
3130            app.layer(from_fn_with_state(state.clone(), middleware::production_headers_middleware));
3131    }
3132
3133    // Add authentication middleware if OAuth is configured via deceptive deploy
3134    if let Some(auth_config) = deceptive_deploy_auth_config {
3135        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
3136        use std::collections::HashMap;
3137        use std::sync::Arc;
3138        use tokio::sync::RwLock;
3139
3140        // Create OAuth2 client if configured
3141        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
3142            match create_oauth2_client(oauth2_config) {
3143                Ok(client) => Some(client),
3144                Err(e) => {
3145                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
3146                    None
3147                }
3148            }
3149        } else {
3150            None
3151        };
3152
3153        // Create auth state
3154        let auth_state = AuthState {
3155            config: auth_config,
3156            spec: None, // OpenAPI spec not available in this context
3157            oauth2_client,
3158            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
3159        };
3160
3161        // Apply auth middleware
3162        app = app.layer(from_fn_with_state(auth_state, auth_middleware));
3163        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
3164    }
3165
3166    // Add runtime daemon 404 detection middleware (if enabled)
3167    #[cfg(feature = "runtime-daemon")]
3168    {
3169        use mockforge_runtime_daemon::{AutoGenerator, NotFoundDetector, RuntimeDaemonConfig};
3170        use std::sync::Arc;
3171
3172        // Load runtime daemon config from environment
3173        let daemon_config = RuntimeDaemonConfig::from_env();
3174
3175        if daemon_config.enabled {
3176            info!("Runtime daemon enabled - auto-creating mocks from 404s");
3177
3178            // Determine management API URL (default to localhost:3000)
3179            let management_api_url =
3180                std::env::var("MOCKFORGE_MANAGEMENT_API_URL").unwrap_or_else(|_| {
3181                    let port =
3182                        std::env::var("MOCKFORGE_HTTP_PORT").unwrap_or_else(|_| "3000".to_string());
3183                    format!("http://localhost:{}", port)
3184                });
3185
3186            // Create auto-generator
3187            let generator = Arc::new(AutoGenerator::new(daemon_config.clone(), management_api_url));
3188
3189            // Create detector and set generator
3190            let detector = NotFoundDetector::new(daemon_config.clone());
3191            detector.set_generator(generator).await;
3192
3193            // Add middleware layer
3194            let detector_clone = detector.clone();
3195            app = app.layer(axum::middleware::from_fn(
3196                move |req: axum::extract::Request, next: axum::middleware::Next| {
3197                    let detector = detector_clone.clone();
3198                    async move { detector.detect_and_auto_create(req, next).await }
3199                },
3200            ));
3201
3202            debug!("Runtime daemon 404 detection middleware added");
3203        }
3204    }
3205
3206    // Add /__mockforge/routes endpoint so the admin UI can discover registered routes
3207    {
3208        let routes_state = HttpServerState::with_routes(captured_routes);
3209        let routes_router = Router::new()
3210            .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
3211            .with_state(routes_state);
3212        app = app.merge(routes_router);
3213    }
3214
3215    // Add API docs page (Scalar-powered interactive explorer)
3216    app = app.route("/__mockforge/docs", axum::routing::get(get_docs_handler));
3217
3218    // Note: OData URI rewrite is applied at the service level in serve_router_with_tls()
3219    // because Router::layer() only applies to matched routes, not unmatched ones.
3220
3221    // Add request logging middleware to capture all requests for the admin dashboard
3222    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
3223
3224    // Add contract diff middleware for automatic request capture
3225    // This captures requests for contract diff analysis
3226    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
3227
3228    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
3229    app = apply_cors_middleware(app, final_cors_config);
3230
3231    app
3232}
3233
3234// Note: start_with_traffic_shaping function removed due to compilation issues
3235// Use build_router_with_traffic_shaping_and_multi_tenant directly instead
3236
3237#[test]
3238fn test_route_info_clone() {
3239    let route = RouteInfo {
3240        method: "POST".to_string(),
3241        path: "/users".to_string(),
3242        operation_id: Some("createUser".to_string()),
3243        summary: None,
3244        description: None,
3245        parameters: vec![],
3246    };
3247
3248    let cloned = route.clone();
3249    assert_eq!(route.method, cloned.method);
3250    assert_eq!(route.path, cloned.path);
3251    assert_eq!(route.operation_id, cloned.operation_id);
3252}
3253
3254#[test]
3255fn test_http_server_state_new() {
3256    let state = HttpServerState::new();
3257    assert_eq!(state.routes.len(), 0);
3258}
3259
3260#[test]
3261fn test_http_server_state_with_routes() {
3262    let routes = vec![
3263        RouteInfo {
3264            method: "GET".to_string(),
3265            path: "/users".to_string(),
3266            operation_id: Some("getUsers".to_string()),
3267            summary: None,
3268            description: None,
3269            parameters: vec![],
3270        },
3271        RouteInfo {
3272            method: "POST".to_string(),
3273            path: "/users".to_string(),
3274            operation_id: Some("createUser".to_string()),
3275            summary: None,
3276            description: None,
3277            parameters: vec![],
3278        },
3279    ];
3280
3281    let state = HttpServerState::with_routes(routes.clone());
3282    assert_eq!(state.routes.len(), 2);
3283    assert_eq!(state.routes[0].method, "GET");
3284    assert_eq!(state.routes[1].method, "POST");
3285}
3286
3287#[test]
3288fn test_http_server_state_clone() {
3289    let routes = vec![RouteInfo {
3290        method: "GET".to_string(),
3291        path: "/test".to_string(),
3292        operation_id: None,
3293        summary: None,
3294        description: None,
3295        parameters: vec![],
3296    }];
3297
3298    let state = HttpServerState::with_routes(routes);
3299    let cloned = state.clone();
3300
3301    assert_eq!(state.routes.len(), cloned.routes.len());
3302    assert_eq!(state.routes[0].method, cloned.routes[0].method);
3303}
3304
3305#[tokio::test]
3306async fn test_build_router_without_openapi() {
3307    let _router = build_router(None, None, None).await;
3308    // Should succeed without OpenAPI spec
3309}
3310
3311#[tokio::test]
3312async fn test_build_router_with_nonexistent_spec() {
3313    let _router = build_router(Some("/nonexistent/spec.yaml".to_string()), None, None).await;
3314    // Should succeed but log a warning
3315}
3316
3317#[tokio::test]
3318async fn test_build_router_with_auth_and_latency() {
3319    let _router = build_router_with_auth_and_latency(None, None, None, None).await;
3320    // Should succeed without parameters
3321}
3322
3323#[tokio::test]
3324async fn test_build_router_with_latency() {
3325    let _router = build_router_with_latency(None, None, None).await;
3326    // Should succeed without parameters
3327}
3328
3329#[tokio::test]
3330async fn test_build_router_with_auth() {
3331    let _router = build_router_with_auth(None, None, None).await;
3332    // Should succeed without parameters
3333}
3334
3335#[tokio::test]
3336async fn test_build_router_with_chains() {
3337    let _router = build_router_with_chains(None, None, None).await;
3338    // Should succeed without parameters
3339}
3340
3341#[test]
3342fn test_route_info_with_all_fields() {
3343    let route = RouteInfo {
3344        method: "PUT".to_string(),
3345        path: "/users/{id}".to_string(),
3346        operation_id: Some("updateUser".to_string()),
3347        summary: Some("Update user".to_string()),
3348        description: Some("Updates an existing user".to_string()),
3349        parameters: vec!["id".to_string(), "body".to_string()],
3350    };
3351
3352    assert!(route.operation_id.is_some());
3353    assert!(route.summary.is_some());
3354    assert!(route.description.is_some());
3355    assert_eq!(route.parameters.len(), 2);
3356}
3357
3358#[test]
3359fn test_route_info_with_minimal_fields() {
3360    let route = RouteInfo {
3361        method: "DELETE".to_string(),
3362        path: "/users/{id}".to_string(),
3363        operation_id: None,
3364        summary: None,
3365        description: None,
3366        parameters: vec![],
3367    };
3368
3369    assert!(route.operation_id.is_none());
3370    assert!(route.summary.is_none());
3371    assert!(route.description.is_none());
3372    assert_eq!(route.parameters.len(), 0);
3373}
3374
3375#[test]
3376fn test_http_server_state_empty_routes() {
3377    let state = HttpServerState::with_routes(vec![]);
3378    assert_eq!(state.routes.len(), 0);
3379}
3380
3381#[test]
3382fn test_http_server_state_multiple_routes() {
3383    let routes = vec![
3384        RouteInfo {
3385            method: "GET".to_string(),
3386            path: "/users".to_string(),
3387            operation_id: Some("listUsers".to_string()),
3388            summary: Some("List all users".to_string()),
3389            description: None,
3390            parameters: vec![],
3391        },
3392        RouteInfo {
3393            method: "GET".to_string(),
3394            path: "/users/{id}".to_string(),
3395            operation_id: Some("getUser".to_string()),
3396            summary: Some("Get a user".to_string()),
3397            description: None,
3398            parameters: vec!["id".to_string()],
3399        },
3400        RouteInfo {
3401            method: "POST".to_string(),
3402            path: "/users".to_string(),
3403            operation_id: Some("createUser".to_string()),
3404            summary: Some("Create a user".to_string()),
3405            description: None,
3406            parameters: vec!["body".to_string()],
3407        },
3408    ];
3409
3410    let state = HttpServerState::with_routes(routes);
3411    assert_eq!(state.routes.len(), 3);
3412
3413    // Verify different HTTP methods
3414    let methods: Vec<&str> = state.routes.iter().map(|r| r.method.as_str()).collect();
3415    assert!(methods.contains(&"GET"));
3416    assert!(methods.contains(&"POST"));
3417}
3418
3419#[test]
3420fn test_http_server_state_with_rate_limiter() {
3421    use std::sync::Arc;
3422
3423    let config = crate::middleware::RateLimitConfig::default();
3424    let rate_limiter = Arc::new(crate::middleware::GlobalRateLimiter::new(config));
3425
3426    let state = HttpServerState::new().with_rate_limiter(rate_limiter);
3427
3428    assert!(state.rate_limiter.is_some());
3429    assert_eq!(state.routes.len(), 0);
3430}
3431
3432#[tokio::test]
3433async fn test_build_router_includes_rate_limiter() {
3434    let _router = build_router(None, None, None).await;
3435    // Router should be created successfully with rate limiter initialized
3436}