systemprompt-cloud 0.73.0

Cloud API client, credentials, OAuth, and tenant management for systemprompt.io AI governance deployments. Remote sync and multi-tenant orchestration for the MCP governance pipeline.
Documentation
//! Builder for the cloud-deployment [`Profile`].
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.

use systemprompt_identifiers::{Email, TenantId};
use systemprompt_manifest::profile::{
    ProfileResult, SecretsConfig, SecretsSource, SecretsValidationMode, TrustedIssuer,
};
use systemprompt_manifest::services::SystemAdminConfig;
use systemprompt_manifest::{
    CloudConfig, CloudValidationMode, ExtensionsConfig, PathsConfig, Profile,
    ProfileDatabaseConfig, ProfileType, RateLimitsConfig, ServerConfig, SiteConfig,
};

use super::{cloud_runtime_config, generate_display_name, security_config, webhook_governance};
use crate::constants::{container, profile as consts};

#[derive(Debug)]
pub struct CloudProfileBuilder {
    name: String,
    tenant_id: Option<TenantId>,
    external_url: Option<String>,
    external_db_access: bool,
    secrets_path: Option<String>,
    trusted_issuers: Vec<TrustedIssuer>,
    geoip_database: Option<String>,
}

impl CloudProfileBuilder {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            tenant_id: None,
            external_url: None,
            external_db_access: false,
            secrets_path: None,
            trusted_issuers: Vec::new(),
            geoip_database: None,
        }
    }

    #[must_use]
    pub fn with_geoip_database(mut self, geoip_database: Option<String>) -> Self {
        self.geoip_database = geoip_database;
        self
    }

    #[must_use]
    pub fn with_trusted_issuer(mut self, issuer: TrustedIssuer) -> Self {
        self.trusted_issuers.push(issuer);
        self
    }

    #[must_use]
    pub fn with_tenant_id(mut self, tenant_id: TenantId) -> Self {
        self.tenant_id = Some(tenant_id);
        self
    }

    #[must_use]
    pub fn with_external_url(mut self, url: impl Into<String>) -> Self {
        self.external_url = Some(url.into());
        self
    }

    #[must_use]
    pub const fn with_external_db_access(mut self, enabled: bool) -> Self {
        self.external_db_access = enabled;
        self
    }

    #[must_use]
    pub fn with_secrets_path(mut self, path: impl Into<String>) -> Self {
        self.secrets_path = Some(path.into());
        self
    }

    pub fn build(self) -> ProfileResult<Profile> {
        let display_name = generate_display_name(&self.name);
        let external = self
            .external_url
            .unwrap_or_else(|| consts::DEFAULT_CLOUD_URL.to_owned());

        let mut profile = Profile {
            storage: systemprompt_manifest::profile::StorageConfig::default(),
            observability: systemprompt_manifest::profile::ObservabilityConfig::default(),
            config_planes: systemprompt_manifest::profile::ConfigPlanesConfig::default(),
            name: self.name,
            display_name,
            target: ProfileType::Cloud,
            site: SiteConfig {
                name: "systemprompt.io".to_owned(),
                github_link: None,
            },
            database: ProfileDatabaseConfig {
                db_type: consts::DEFAULT_DB_TYPE.to_owned(),
                external_db_access: self.external_db_access,
                migrate_on_boot: true,
                pool: None,
            },
            security: security_config(self.trusted_issuers, false),
            server: {
                let mut server =
                    ServerConfig::new(consts::CLOUD_HOST, consts::DEFAULT_PORT, external);
                server.role = systemprompt_manifest::profile::NodeRole::All;
                server.trusted_proxies = crate::trusted_proxies::default_cloud_trusted_proxies();
                server
            },
            paths: cloud_paths(self.geoip_database),
            rate_limits: RateLimitsConfig::default(),
            runtime: cloud_runtime_config(),
            cloud: Some(CloudConfig {
                tenant_id: self.tenant_id,
                validation: CloudValidationMode::Strict,
            }),
            secrets: Some(SecretsConfig {
                secrets_path: self.secrets_path,
                validation: SecretsValidationMode::Strict,
                source: SecretsSource::Env,
                vault: None,
                external: None,
                refresh_secs: None,
            }),
            extensions: ExtensionsConfig::default(),
            governance: Some(webhook_governance()),
            services: systemprompt_manifest::profile::ServicesProfileConfig::default(),
            judge: systemprompt_manifest::profile::JudgeProfile::default(),
            retention: systemprompt_manifest::profile::RetentionConfig::default(),
            system_admin: SystemAdminConfig {
                username: "admin".to_owned(),
                email: Some(Email::local_admin()),
            },
        };
        profile.derive_fields()?;
        Ok(profile)
    }
}

fn cloud_paths(geoip_database: Option<String>) -> PathsConfig {
    PathsConfig {
        root: None,
        system: container::APP.to_owned(),
        services: container::SERVICES.to_owned(),
        bin: container::BIN.to_owned(),
        storage: Some(container::STORAGE.to_owned()),
        geoip_database,
        data: None,
        web_path: Some(container::WEB.to_owned()),
        shipped_files: None,
    }
}