systemprompt-extension 0.19.0

Compile-time extension framework for systemprompt.io AI governance infrastructure. Built on the inventory crate — registers schemas, API routes, jobs, and providers in the MCP governance pipeline.
Documentation
use super::{ExtensionRegistry, topo_sort};
use crate::error::LoaderError;

pub const RESERVED_PATHS: &[&str] = &[
    "/api/v1/oauth",
    "/api/v1/users",
    "/api/v1/agents",
    "/api/v1/mcp",
    "/api/v1/stream",
    "/api/v1/content",
    "/api/v1/files",
    "/api/v1/analytics",
    "/api/v1/scheduler",
    "/api/v1/core",
    "/api/v1/admin",
    "/.well-known",
];

impl ExtensionRegistry {
    pub fn validate_dependencies(&self) -> Result<(), LoaderError> {
        for ext in self.extensions.values() {
            for dep_id in ext.dependencies() {
                if !self.extensions.contains_key(dep_id) {
                    return Err(LoaderError::MissingDependency {
                        extension: ext.id().to_owned(),
                        dependency: dep_id.to_owned(),
                    });
                }
            }
        }

        let ids: Vec<String> = self.extensions.keys().cloned().collect();
        topo_sort(&ids, &self.extensions).map(|_| ())
    }

    pub fn validate_api_paths(&self, ctx: &dyn crate::ExtensionContext) -> Result<(), LoaderError> {
        for ext in self.extensions.values() {
            if let Some(router_config) = ext.router(ctx) {
                let base_path = router_config.base_path;

                if !base_path.starts_with("/api/") {
                    return Err(LoaderError::InvalidBasePath {
                        extension: ext.id().to_owned(),
                        path: base_path.to_owned(),
                    });
                }

                for reserved in RESERVED_PATHS {
                    if base_path.starts_with(reserved) {
                        return Err(LoaderError::ReservedPathCollision {
                            extension: ext.id().to_owned(),
                            path: base_path.to_owned(),
                        });
                    }
                }
            }
        }
        Ok(())
    }
}