use std::path::Path;
use std::sync::Arc;
use systemprompt_config::ProfileBootstrap;
use systemprompt_database::{Database, DbPool};
use systemprompt_manifest::Config;
use systemprompt_manifest::services::{GatewayState, ProviderRegistry};
use systemprompt_security::authz::{YAML_SOURCE, reconcile_services_authz};
pub(super) enum ReconcileOutcome {
Reconciled,
Deferred(String),
}
pub(super) async fn reconcile_authz(
gateway: Option<&GatewayState>,
providers: &ProviderRegistry,
source_path: &str,
) -> ReconcileOutcome {
match try_reconcile(gateway, providers, source_path).await {
Ok(()) => ReconcileOutcome::Reconciled,
Err(err) => {
tracing::warn!(
error = %err,
"services file saved, but the authz catalog could not be reconciled now; it will \
be reconciled on the next app start"
);
ReconcileOutcome::Deferred(err.to_string())
},
}
}
pub(super) fn append_reconcile_notice(message: String, outcome: &ReconcileOutcome) -> String {
match outcome {
ReconcileOutcome::Reconciled => message,
ReconcileOutcome::Deferred(reason) => format!(
"{message}\n\n⚠ authz reconcile deferred: {reason}\nThe file was saved; the authz \
catalog will be reconciled on the next app start."
),
}
}
async fn try_reconcile(
gateway: Option<&GatewayState>,
providers: &ProviderRegistry,
source_path: &str,
) -> anyhow::Result<()> {
let cfg = Config::get()?;
let database: DbPool = Arc::new(
Database::connect(
&cfg.database_url,
cfg.database_write_url.as_deref(),
&systemprompt_database::PoolConfig::default(),
)
.await?,
);
let mut services = systemprompt_loader::ConfigLoader::load()?;
services.providers = providers.clone();
if let Some(gateway) = gateway {
services.gateway = Some(gateway.clone());
}
let services_dir = ProfileBootstrap::get()?.paths.services.clone();
let source = format!("services:{source_path}");
reconcile_services_authz(
&database,
&services,
Path::new(&services_dir),
YAML_SOURCE,
None,
)
.await?;
tracing::debug!(source = %source, "authz reconciled after a services edit");
Ok(())
}