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