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