systemprompt-runtime 0.20.0

Application runtime for systemprompt.io AI governance infrastructure. AppContext, lifecycle builder, extension registry, and module wiring for the MCP governance pipeline.
Documentation
//! Subsystem resolution helpers used by [`AppContextBuilder::build`].
//!
//! [`AppContextBuilder::build`]: super::AppContextBuilder::build

use std::sync::Arc;

use systemprompt_analytics::{AnalyticsService, FingerprintRepository, GeoIpReader};
use systemprompt_database::{Database, DbPool};
use systemprompt_marketplace::{AllowAllFilter, MarketplaceFilter, discover_filters};
use systemprompt_models::auth::UserRole;
use systemprompt_models::services::{SystemAdmin, SystemAdminConfig};
use systemprompt_models::{AppPaths, Config, ContentConfigRaw, ContentRouting};
use systemprompt_users::UserService;

use crate::context::AppContext;
use crate::error::{RuntimeError, RuntimeResult};

pub(super) struct ContentAnalytics {
    pub(super) geoip_reader: Option<GeoIpReader>,
    pub(super) content_config: Option<Arc<ContentConfigRaw>>,
    pub(super) route_classifier: Arc<systemprompt_models::RouteClassifier>,
    pub(super) analytics_service: Arc<AnalyticsService>,
    pub(super) fingerprint_repo: Option<Arc<FingerprintRepository>>,
}

pub(super) fn assemble_content_analytics(
    config: &Config,
    app_paths: &AppPaths,
    database: &Arc<Database>,
    show_startup_warnings: bool,
) -> RuntimeResult<ContentAnalytics> {
    let geoip_reader = AppContext::load_geoip_database(config, show_startup_warnings);
    let content_config = AppContext::load_content_config(config, app_paths);
    let content_routing = content_routing_from(content_config.as_ref());
    let route_classifier = Arc::new(systemprompt_models::RouteClassifier::new(
        content_routing.clone(),
    ));
    let analytics_service = Arc::new(AnalyticsService::new(
        database,
        geoip_reader.clone(),
        content_routing,
    )?);

    let fingerprint_repo = match FingerprintRepository::new(database) {
        Ok(repo) => Some(Arc::new(repo)),
        Err(e) => {
            tracing::warn!(error = %e, "Failed to initialize fingerprint repository");
            None
        },
    };

    Ok(ContentAnalytics {
        geoip_reader,
        content_config,
        route_classifier,
        analytics_service,
        fingerprint_repo,
    })
}

pub(super) async fn resolve_and_install_system_admin(
    config: &Config,
    users: &Arc<UserService>,
) -> RuntimeResult<Arc<SystemAdmin>> {
    let cfg = SystemAdminConfig {
        username: config.system_admin_username.clone(),
    };
    let resolved = resolve_system_admin(&cfg, users.as_ref()).await?;
    systemprompt_logging::install_log_attribution(resolved.clone());
    Ok(Arc::new(resolved))
}

async fn resolve_system_admin(
    cfg: &SystemAdminConfig,
    users: &UserService,
) -> RuntimeResult<SystemAdmin> {
    let user = users.find_by_name(&cfg.username).await?.ok_or_else(|| {
        RuntimeError::SystemAdminNotFound {
            username: cfg.username.clone(),
        }
    })?;
    if !user.is_active() {
        return Err(RuntimeError::SystemAdminInactive {
            username: cfg.username.clone(),
        });
    }
    let admin_role = UserRole::Admin.as_str();
    if !user.roles.iter().any(|r| r == admin_role) {
        return Err(RuntimeError::SystemAdminMissingRole {
            username: cfg.username.clone(),
        });
    }
    Ok(SystemAdmin::new(user.id, user.name))
}

pub(super) fn build_marketplace_filter(database: &DbPool) -> Arc<dyn MarketplaceFilter> {
    for reg in discover_filters() {
        match (reg.factory)(database) {
            Ok(filter) => {
                tracing::debug!(
                    priority = reg.priority,
                    "marketplace filter registered via inventory; using highest-priority impl",
                );
                return filter;
            },
            Err(err) => {
                tracing::error!(
                    priority = reg.priority,
                    error = %err,
                    "marketplace filter factory failed; trying next candidate",
                );
            },
        }
    }
    let fallback: Arc<dyn MarketplaceFilter> = Arc::new(AllowAllFilter);
    fallback
}

pub(super) fn content_routing_from(
    content_config: Option<&Arc<ContentConfigRaw>>,
) -> Option<Arc<dyn ContentRouting>> {
    let concrete = Arc::clone(content_config?);
    let routing: Arc<dyn ContentRouting> = concrete;
    Some(routing)
}