Skip to main content

mockforge_http/
lib.rs

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