systemprompt_runtime/builder/
mod.rs1mod assembly;
12mod composition;
13mod core_layer;
14
15use composition::{build_data_plane, build_repositories, ensure_legacy_context};
16
17use std::sync::{Arc, OnceLock};
18
19use systemprompt_database::MigrationConfig;
20use systemprompt_extension::ExtensionRegistry;
21use systemprompt_marketplace::{MarketplaceCache, MarketplaceFilter};
22use systemprompt_mcp::services::registry::RegistryService;
23use systemprompt_security::authz::{AuthzDecisionHook, SharedAuthzHook};
24use systemprompt_users::UserService;
25
26use crate::context::{AppContext, ConfigPlane, DataPlane, Plugins, ShutdownRequest, Subsystems};
27use crate::error::RuntimeResult;
28use crate::registry::ModuleApiRegistry;
29pub use core_layer::discover_vertex_models as discover_models;
30use core_layer::{CoreLayer, init_core, init_extensions};
31
32#[derive(Default)]
41pub struct AppContextBuilder {
42 extension_registry: Option<ExtensionRegistry>,
43 show_startup_warnings: bool,
44 marketplace_filter: Option<Arc<dyn MarketplaceFilter>>,
45 authz_hook: Option<SharedAuthzHook>,
46 install_schemas: bool,
47 migration_config: MigrationConfig,
48 shutdown: Option<ShutdownRequest>,
49}
50
51impl std::fmt::Debug for AppContextBuilder {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("AppContextBuilder")
54 .field("extension_registry", &self.extension_registry.is_some())
55 .field("show_startup_warnings", &self.show_startup_warnings)
56 .field("marketplace_filter", &self.marketplace_filter.is_some())
57 .field("authz_hook", &self.authz_hook.is_some())
58 .field("install_schemas", &self.install_schemas)
59 .field("migration_config", &self.migration_config)
60 .field("shutdown", &self.shutdown.is_some())
61 .finish()
62 }
63}
64
65impl AppContextBuilder {
66 #[must_use]
67 pub fn new() -> Self {
68 Self::default()
69 }
70
71 #[must_use]
72 pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
73 self.extension_registry = Some(registry);
74 self
75 }
76
77 #[must_use]
78 pub const fn with_startup_warnings(mut self, show: bool) -> Self {
79 self.show_startup_warnings = show;
80 self
81 }
82
83 #[must_use]
84 pub fn with_marketplace_filter(mut self, filter: Arc<dyn MarketplaceFilter>) -> Self {
85 self.marketplace_filter = Some(filter);
86 self
87 }
88
89 #[must_use]
90 pub const fn with_migrations(mut self, install: bool) -> Self {
91 self.install_schemas = install;
92 self
93 }
94
95 #[must_use]
96 pub fn with_authz_hook<H>(mut self, hook: H) -> Self
97 where
98 H: AuthzDecisionHook + 'static,
99 {
100 self.authz_hook = Some(Arc::new(hook));
101 self
102 }
103
104 #[must_use]
105 pub fn with_shared_authz_hook(mut self, hook: SharedAuthzHook) -> Self {
106 self.authz_hook = Some(hook);
107 self
108 }
109
110 #[must_use]
111 pub fn with_shutdown(mut self, shutdown: ShutdownRequest) -> Self {
112 self.shutdown = Some(shutdown);
113 self
114 }
115
116 #[must_use]
117 pub const fn with_migration_config(mut self, config: MigrationConfig) -> Self {
118 self.migration_config = config;
119 self
120 }
121
122 pub async fn build(self) -> RuntimeResult<AppContext> {
123 let shutdown = self.shutdown.unwrap_or_default();
124 let CoreLayer {
125 config,
126 app_paths,
127 database,
128 authz_hook,
129 governance,
130 file_storage,
131 } = init_core(self.authz_hook).await?;
132
133 let api_registry = Arc::new(ModuleApiRegistry::new());
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 system_admin,
167 authz_hook,
168 governance,
169 schema_install: Arc::new(schema_install),
170 event_bridge: Arc::new(OnceLock::new()),
171 geoip_reader,
172 file_storage,
173 shutdown,
174 publish_guard: Arc::new(tokio::sync::Mutex::new(
175 systemprompt_marketplace::inventory::PublishGuard::default(),
176 )),
177 snapshot_wakeup: Arc::new(crate::reporting::SnapshotWakeup::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,
197 mcp_registry,
198 marketplace_filter,
199 marketplace_cache: Arc::new(MarketplaceCache::default()),
200 },
201 subsystems,
202 ))
203 }
204}
205
206async fn build_domain_layer(
207 config: &systemprompt_models::Config,
208 database: &systemprompt_database::DbPool,
209 analytics_repositories: Arc<systemprompt_analytics::repository::AnalyticsRepositories>,
210) -> RuntimeResult<(
211 composition::RepositoryBundles,
212 Arc<UserService>,
213 Arc<systemprompt_models::SystemAdmin>,
214 RegistryService,
215)> {
216 let mut repositories = build_repositories(
217 database,
218 analytics_repositories,
219 systemprompt_identifiers::InstanceId::new(&config.instance_id),
220 )?;
221 let user_service = Arc::new(UserService::new(Arc::clone(&repositories.users)));
222 let system_admin = assembly::resolve_and_install_system_admin(config, &user_service).await?;
223 repositories.install_organization_resolver(system_admin.id());
224 let mcp_registry = RegistryService::new(system_admin.id().clone());
225 ensure_legacy_context(&repositories, &system_admin).await?;
226 Ok((repositories, user_service, system_admin, mcp_registry))
227}