systemprompt_runtime/builder/
mod.rs1mod ai_service;
12mod assembly;
13mod composition;
14mod core_layer;
15
16use composition::{build_data_plane, build_repositories, ensure_legacy_context};
17
18use std::sync::{Arc, OnceLock};
19
20use systemprompt_database::MigrationConfig;
21use systemprompt_extension::ExtensionRegistry;
22use systemprompt_marketplace::MarketplaceFilter;
23use systemprompt_mcp::services::registry::RegistryService;
24use systemprompt_security::authz::{AuthzDecisionHook, SharedAuthzHook};
25use systemprompt_users::UserService;
26
27use crate::context::{AppContext, ConfigPlane, DataPlane, Plugins, ShutdownRequest, Subsystems};
28use crate::error::RuntimeResult;
29use crate::registry::ModuleApiRegistry;
30pub use core_layer::discover_vertex_models as discover_models;
31use core_layer::{CoreLayer, init_core, init_extensions};
32
33#[derive(Default)]
42pub struct AppContextBuilder {
43 extension_registry: Option<ExtensionRegistry>,
44 show_startup_warnings: bool,
45 marketplace_filter: Option<Arc<dyn MarketplaceFilter>>,
46 authz_hook: Option<SharedAuthzHook>,
47 install_schemas: bool,
48 migration_config: MigrationConfig,
49 shutdown: Option<ShutdownRequest>,
50}
51
52impl std::fmt::Debug for AppContextBuilder {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("AppContextBuilder")
55 .field("extension_registry", &self.extension_registry.is_some())
56 .field("show_startup_warnings", &self.show_startup_warnings)
57 .field("marketplace_filter", &self.marketplace_filter.is_some())
58 .field("authz_hook", &self.authz_hook.is_some())
59 .field("install_schemas", &self.install_schemas)
60 .field("migration_config", &self.migration_config)
61 .field("shutdown", &self.shutdown.is_some())
62 .finish()
63 }
64}
65
66impl AppContextBuilder {
67 #[must_use]
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 #[must_use]
73 pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
74 self.extension_registry = Some(registry);
75 self
76 }
77
78 #[must_use]
79 pub const fn with_startup_warnings(mut self, show: bool) -> Self {
80 self.show_startup_warnings = show;
81 self
82 }
83
84 #[must_use]
85 pub fn with_marketplace_filter(mut self, filter: Arc<dyn MarketplaceFilter>) -> Self {
86 self.marketplace_filter = Some(filter);
87 self
88 }
89
90 #[must_use]
91 pub const fn with_migrations(mut self, install: bool) -> Self {
92 self.install_schemas = install;
93 self
94 }
95
96 #[must_use]
97 pub fn with_authz_hook<H>(mut self, hook: H) -> Self
98 where
99 H: AuthzDecisionHook + 'static,
100 {
101 self.authz_hook = Some(Arc::new(hook));
102 self
103 }
104
105 #[must_use]
106 pub fn with_shared_authz_hook(mut self, hook: SharedAuthzHook) -> Self {
107 self.authz_hook = Some(hook);
108 self
109 }
110
111 #[must_use]
112 pub fn with_shutdown(mut self, shutdown: ShutdownRequest) -> Self {
113 self.shutdown = Some(shutdown);
114 self
115 }
116
117 #[must_use]
118 pub const fn with_migration_config(mut self, config: MigrationConfig) -> Self {
119 self.migration_config = config;
120 self
121 }
122
123 pub async fn build(self) -> RuntimeResult<AppContext> {
124 let shutdown = self.shutdown.unwrap_or_default();
125 let CoreLayer {
126 config,
127 app_paths,
128 database,
129 authz_hook,
130 governance,
131 file_storage,
132 } = init_core(self.authz_hook).await?;
133
134 let (extension_registry, schema_install) = init_extensions(
135 self.extension_registry,
136 self.install_schemas,
137 self.migration_config,
138 &database,
139 )
140 .await?;
141
142 crate::reporting::initialize(&database).await?;
143
144 let assembly::ContentAnalytics {
145 geoip_reader,
146 content_config,
147 route_classifier,
148 analytics_service,
149 analytics_repositories,
150 fingerprint_repo,
151 } = assembly::assemble_content_analytics(
152 &config,
153 &app_paths,
154 &database,
155 self.show_startup_warnings,
156 )?;
157
158 let (repositories, user_service, system_admin, mcp_registry) =
159 build_domain_layer(&config, &database, analytics_repositories).await?;
160
161 let marketplace_filter = self
162 .marketplace_filter
163 .unwrap_or_else(|| assembly::build_marketplace_filter(&database));
164
165 let subsystems = Subsystems {
166 ai_service: ai_service::build_ai_service(&database, &repositories, &mcp_registry)?,
167 artifact_ingest: build_artifact_ingest(&database, &governance)?,
168 system_admin,
169 authz_hook,
170 governance,
171 schema_install: Arc::new(schema_install),
172 event_bridge: Arc::new(OnceLock::new()),
173 geoip_reader,
174 file_storage,
175 shutdown,
176 publish_guard: Arc::default(),
177 snapshot_wakeup: Arc::default(),
178 };
179
180 Ok(AppContext::from_parts(
181 build_data_plane(
182 database,
183 analytics_service,
184 fingerprint_repo,
185 user_service,
186 repositories,
187 ),
188 ConfigPlane {
189 config,
190 app_paths,
191 content_config,
192 route_classifier,
193 },
194 Plugins {
195 extension_registry,
196 api_registry: Arc::new(ModuleApiRegistry::new()),
197 mcp_registry,
198 marketplace_filter,
199 marketplace_cache: Arc::default(),
200 },
201 subsystems,
202 ))
203 }
204}
205
206fn build_artifact_ingest(
207 database: &systemprompt_database::DbPool,
208 governance: &systemprompt_security::policy::GovernanceEngine,
209) -> RuntimeResult<Arc<systemprompt_mcp::ArtifactIngest>> {
210 let ingest = systemprompt_mcp::ArtifactIngest::from_db(
211 database,
212 governance
213 .secret_scanner()
214 .map(|scanner| Arc::new(scanner.clone())),
215 )
216 .map_err(|e| crate::RuntimeError::Internal(format!("artifact ingest: {e}")))?;
217 Ok(Arc::new(ingest))
218}
219
220async fn build_domain_layer(
221 config: &systemprompt_models::Config,
222 database: &systemprompt_database::DbPool,
223 analytics_repositories: Arc<systemprompt_analytics::repository::AnalyticsRepositories>,
224) -> RuntimeResult<(
225 composition::RepositoryBundles,
226 Arc<UserService>,
227 Arc<systemprompt_models::SystemAdmin>,
228 RegistryService,
229)> {
230 let mut repositories = build_repositories(
231 database,
232 analytics_repositories,
233 systemprompt_identifiers::InstanceId::new(&config.instance_id),
234 )?;
235 let user_service = Arc::new(UserService::new(Arc::clone(&repositories.users)));
236 let system_admin = assembly::resolve_and_install_system_admin(config, &user_service).await?;
237 repositories.install_organization_resolver(system_admin.id());
238 let mcp_registry = RegistryService::new(system_admin.id().clone());
239 ensure_legacy_context(&repositories, &system_admin).await?;
240 Ok((repositories, user_service, system_admin, mcp_registry))
241}