0.5.1-pre.002

This commit is contained in:
2026-08-09 19:34:08 +02:00
parent 816eee59a9
commit 6a680767ae
767 changed files with 12257 additions and 12195 deletions

View File

@@ -0,0 +1,184 @@
// file: ks-pipeline-demo-scenarios/src/environment.rs
// version: 3
//! Environment initialization for opt-in demonstration scenarios.
/// Loads the workspace `.env` selected by `ks-config`.
pub fn initialize_demo_scenario_environment(
workspace_root: &std::path::Path,
) -> ks_core::Result<ks_config::EnvironmentLoadReport> {
return ks_config::load_workspace_environment(workspace_root);
}
/// Resolves one compatible Devnet profile for demo scenarios.
///
/// An explicit name has priority, followed by `KB_DEVNET_PROFILE`, then the
/// first compatible Devnet profile declared by the configuration.
pub fn resolve_demo_devnet_profile(
config: &ks_config::AppConfig,
requested_profile_name: std::option::Option<&str>,
) -> ks_core::Result<ks_config::ProfileConfig> {
let explicit = requested_profile_name
.map(str::trim)
.filter(|value| return !value.is_empty())
.map(std::string::ToString::to_string);
let selected_name = match explicit {
std::option::Option::Some(value) => std::option::Option::Some(value),
std::option::Option::None => std::env::var("KB_DEVNET_PROFILE")
.ok()
.map(|value| return value.trim().to_string())
.filter(|value| return !value.is_empty()),
};
for profile in &config.profiles {
let compatible = profile.wallet.cluster == "devnet"
&& profile.wallet.temporary_wallet_enabled
&& profile.wallet.temporary_wallet_persist;
if !compatible {
continue;
}
if let std::option::Option::Some(name) = selected_name.as_ref()
&& profile.name != *name
{
continue;
}
return std::result::Result::Ok(profile.clone());
}
return match selected_name {
std::option::Option::Some(name) => std::result::Result::Err(ks_core::Error::config(
format!("compatible Devnet profile '{name}' is unavailable"),
)),
std::option::Option::None => std::result::Result::Err(ks_core::Error::config(
"no compatible Devnet profile is configured",
)),
};
}
/// Readiness report for one Devnet profile PostgreSQL store.
#[derive(Clone, Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DevnetProfileStoreReadiness {
/// Selected Devnet profile.
pub profile_name: std::string::String,
/// Whether schema creation was allowed by configuration.
pub auto_initialize_schema: bool,
/// Number of known tables already present before preparation.
pub existing_tables_before: usize,
/// Number of known tables created during preparation.
pub created_tables: usize,
/// Number of known tables present after preparation.
pub existing_tables_after: usize,
/// Total number of known tables expected by the current store.
pub expected_tables: usize,
}
/// Verifies or initializes the PostgreSQL schema selected by one Devnet profile.
pub async fn prepare_demo_devnet_profile_store(
profile: &ks_config::ProfileConfig,
) -> ks_core::Result<DevnetProfileStoreReadiness> {
if profile.wallet.cluster != "devnet" {
return std::result::Result::Err(ks_core::Error::config(format!(
"profile '{}' is not configured for Devnet",
profile.name
)));
}
if !profile.database.enabled {
return std::result::Result::Err(ks_core::Error::config(format!(
"Devnet profile '{}' has database storage disabled",
profile.name
)));
}
if profile.database.backend != "postgres" {
return std::result::Result::Err(ks_core::Error::config(format!(
"Devnet profile '{}' must use the PostgreSQL backend",
profile.name
)));
}
let options = match ks_store::PostgresStoreOptions::new(
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
false,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store = match ks_store::PostgresStore::connect(options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let before = match store.known_table_diagnostics().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let existing_tables_before = before.iter().filter(|table| return table.exists).count();
if profile.database.postgres.auto_initialize_schema
&& let std::result::Result::Err(error) = store.initialize_store_schema().await
{
return std::result::Result::Err(error);
}
let after = match store.known_table_diagnostics().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let existing_tables_after = after.iter().filter(|table| return table.exists).count();
if existing_tables_after != after.len() {
let missing = after
.iter()
.filter(|table| return !table.exists)
.map(|table| return table.table_name.clone())
.collect::<std::vec::Vec<_>>();
return std::result::Result::Err(ks_core::Error::config(format!(
"Devnet profile '{}' PostgreSQL schema is incomplete and auto initialization is {}: missing {}",
profile.name,
if profile.database.postgres.auto_initialize_schema {
"enabled"
} else {
"disabled"
},
missing.join(", ")
)));
}
return std::result::Result::Ok(DevnetProfileStoreReadiness {
profile_name: profile.name.clone(),
auto_initialize_schema: profile.database.postgres.auto_initialize_schema,
existing_tables_before,
created_tables: existing_tables_after.saturating_sub(existing_tables_before),
existing_tables_after,
expected_tables: after.len(),
});
}
#[cfg(test)]
mod tests {
fn example_config() -> ks_config::AppConfig {
return match ks_config::parse_config_json(include_str!("../../config/example.config.json"))
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
};
}
#[test]
fn resolver_finds_a_compatible_devnet_profile_without_a_fixed_name() {
let config = example_config();
let profile = match super::resolve_demo_devnet_profile(&config, std::option::Option::None) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("Devnet profile resolution failed: {error}"),
};
assert_eq!(profile.wallet.cluster, "devnet");
assert!(profile.wallet.temporary_wallet_enabled);
assert!(profile.wallet.temporary_wallet_persist);
}
#[test]
fn resolver_rejects_an_explicit_non_devnet_profile() {
let config = example_config();
assert!(
super::resolve_demo_devnet_profile(
&config,
std::option::Option::Some("mainnet_research"),
)
.is_err()
);
}
}