v0.3.2-pre.004-fix.001
This commit is contained in:
13
.env.example
13
.env.example
@@ -1,13 +1,18 @@
|
||||
# file: .env.example
|
||||
# version: 11
|
||||
# version: 13
|
||||
|
||||
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
|
||||
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
||||
KSP_LOGS_DIRECTORY=logs
|
||||
|
||||
# PostgreSQL connection URI used by Config std.store profiles.
|
||||
# Keep credentials only in the process environment or local .env; the committed fallback remains secret-classified and is redacted in safe projections.
|
||||
# KSP_SECRET_STORE_POSTGRES_URI=postgresql://user:password@localhost/ksp
|
||||
# PostgreSQL URI for the Devnet Store target.
|
||||
KSP_SECRET_STORE_DEVNET_POSTGRES_URI=postgresql://...
|
||||
|
||||
# PostgreSQL URI for the Mainnet Store target.
|
||||
KSP_SECRET_STORE_MAINNET_POSTGRES_URI=postgresql://...
|
||||
|
||||
# PostgreSQL URI for the Testnet Store target.
|
||||
KSP_SECRET_STORE_TESTNET_POSTGRES_URI=postgresql://...
|
||||
|
||||
# KSP Wallet root directory. Used by config/std.wallet.json before an optional profile subdirectory is appended.
|
||||
# The committed Wallet document falls back to "wallets" when neither the process environment nor .env defines this variable.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 335
|
||||
# version: 336
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.2-pre.4"
|
||||
version = "0.3.2-pre.4.fix.1"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,12 +1,61 @@
|
||||
{
|
||||
"format_version": 1,
|
||||
"default_profile": "postgres_default",
|
||||
"default_profile": "devnet",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "postgres_default",
|
||||
"profile_id": "devnet",
|
||||
"network": "devnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_POSTGRES_URI:-postgresql://localhost/ksp}",
|
||||
"connection_uri": "${KSP_SECRET_STORE_DEVNET_POSTGRES_URI:-postgresql://localhost/ksp_devnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "mainnet",
|
||||
"network": "mainnet-beta",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "testnet",
|
||||
"network": "testnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_TESTNET_POSTGRES_URI:-postgresql://localhost/ksp_testnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
||||
},
|
||||
"networkId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "^[A-Za-z0-9_.:-]+$"
|
||||
},
|
||||
"duration100To60000": {
|
||||
"type": "integer",
|
||||
"minimum": 100,
|
||||
@@ -39,6 +45,7 @@
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"profile_id",
|
||||
"network",
|
||||
"backend",
|
||||
"postgres"
|
||||
],
|
||||
@@ -46,6 +53,9 @@
|
||||
"profile_id": {
|
||||
"$ref": "#/$defs/profileId"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/$defs/networkId"
|
||||
},
|
||||
"backend": {
|
||||
"const": "postgres"
|
||||
},
|
||||
|
||||
@@ -1,12 +1,61 @@
|
||||
{
|
||||
"format_version": 1,
|
||||
"default_profile": "postgres_default",
|
||||
"default_profile": "devnet",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "postgres_default",
|
||||
"profile_id": "devnet",
|
||||
"network": "devnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_POSTGRES_URI:-postgresql://localhost/ksp}",
|
||||
"connection_uri": "${KSP_SECRET_STORE_DEVNET_POSTGRES_URI:-postgresql://localhost/ksp_devnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "mainnet",
|
||||
"network": "mainnet-beta",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "testnet",
|
||||
"network": "testnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_TESTNET_POSTGRES_URI:-postgresql://localhost/ksp_testnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/profiles.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
#[test]
|
||||
fn profile_inventory_exposes_registered_profile_documents() {
|
||||
@@ -15,6 +15,9 @@ fn profile_inventory_exposes_registered_profile_documents() {
|
||||
assert!(inventory.iter().any(|document| -> bool {
|
||||
return document.file_id == ksp_config_lib::FILE_ID_STD_TRANSPORT;
|
||||
}));
|
||||
assert!(inventory.iter().any(|document| -> bool {
|
||||
return document.file_id == ksp_config_lib::FILE_ID_STD_STORE;
|
||||
}));
|
||||
assert!(inventory.iter().any(|document| -> bool {
|
||||
return document.file_id == ksp_config_lib::FILE_ID_STD_WALLET;
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/store.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Effective standard Store configuration mapped to `ksp_store_lib::StoreSettings`.
|
||||
pub struct ResolvedStoreConfig {
|
||||
@@ -25,11 +25,19 @@ impl ResolvedStoreConfig {
|
||||
}
|
||||
|
||||
/// Returns the selected standard Store profile identifier.
|
||||
///
|
||||
/// For `std.store`, the profile identifier is also the stable named Store target identifier.
|
||||
#[must_use]
|
||||
pub fn profile_id(&self) -> &str {
|
||||
return self.profile_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the selected named Store target identifier.
|
||||
#[must_use]
|
||||
pub fn target_id(&self) -> &str {
|
||||
return self.profile_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the source that selected the standard Store profile.
|
||||
#[must_use]
|
||||
pub const fn selection_source(&self) -> crate::ConfigProfileSelectionSource {
|
||||
@@ -111,6 +119,7 @@ impl crate::ConfigDocumentEngine {
|
||||
struct EffectiveStoreSource {
|
||||
backend: String,
|
||||
format_version: u32,
|
||||
network: String,
|
||||
postgres: EffectivePostgresSource,
|
||||
profile_id: String,
|
||||
}
|
||||
@@ -164,9 +173,7 @@ fn resolve_store_profile(profile: &crate::ResolvedConfigProfile, environment: &c
|
||||
let source = match source {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(
|
||||
effective_error(profile, "effective Store Config cannot be decoded into the runtime adapter contract").with_source(error),
|
||||
);
|
||||
return std::result::Result::Err(effective_error(profile, "effective Store Config cannot be decoded into the runtime adapter contract").with_source(error));
|
||||
},
|
||||
};
|
||||
if source.format_version != 1 {
|
||||
@@ -195,8 +202,14 @@ fn resolve_store_profile(profile: &crate::ResolvedConfigProfile, environment: &c
|
||||
std::time::Duration::from_millis(source.postgres.bootstrap.migration_timeout_ms),
|
||||
std::time::Duration::from_millis(source.postgres.bootstrap.migration_lock_timeout_ms),
|
||||
);
|
||||
let network = ksp_store_lib::RawNetworkId::new(source.network);
|
||||
let network = match network {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(effective_error(profile, "effective Store network identifier is invalid")),
|
||||
};
|
||||
let postgres = ksp_store_lib::PostgresStoreSettings::new(source.postgres.connection_uri, pool, tls_mode, bootstrap);
|
||||
let settings = ksp_store_lib::StoreSettings::new(
|
||||
network,
|
||||
ksp_store_lib::StoreBackendSettings::Postgres(postgres),
|
||||
std::time::Duration::from_millis(source.postgres.shutdown_timeout_ms),
|
||||
);
|
||||
@@ -206,6 +219,7 @@ fn resolve_store_profile(profile: &crate::ResolvedConfigProfile, environment: &c
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
profile_id = profile.profile_id(),
|
||||
network = settings.network().as_str(),
|
||||
backend = settings.backend_kind().code(),
|
||||
"mapped standard Store Config to Store settings"
|
||||
);
|
||||
@@ -219,7 +233,10 @@ fn resolve_store_profile(profile: &crate::ResolvedConfigProfile, environment: &c
|
||||
});
|
||||
}
|
||||
|
||||
fn validate_connection_uri_provenance(effective: &crate::ResolvedConfigJson, profile: &crate::ResolvedConfigProfile) -> ksp_core_lib::Result<()> {
|
||||
fn validate_connection_uri_provenance(
|
||||
effective: &crate::ResolvedConfigJson,
|
||||
profile: &crate::ResolvedConfigProfile,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let provenance = match effective.provenance_at("/postgres/connection_uri") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Store PostgreSQL connection URI provenance is unavailable")),
|
||||
|
||||
78
crates/ksp-config-lib/unit_tests/fixtures/std.store.json
Normal file
78
crates/ksp-config-lib/unit_tests/fixtures/std.store.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"format_version": 1,
|
||||
"default_profile": "devnet",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "devnet",
|
||||
"network": "devnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_DEVNET_POSTGRES_URI:-postgresql://localhost/ksp_devnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "mainnet",
|
||||
"network": "mainnet-beta",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "testnet",
|
||||
"network": "testnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_TESTNET_POSTGRES_URI:-postgresql://localhost/ksp_testnet}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/store.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn committed_store_profile_maps_exact_runtime_settings_and_secret_fallback() {
|
||||
@@ -13,9 +13,11 @@ fn committed_store_profile_maps_exact_runtime_settings_and_secret_fallback() {
|
||||
assert!(resolved.is_ok(), "committed Store profile should map without opening PostgreSQL: {resolved:?}");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert_eq!(resolved.file_id().as_str(), crate::FILE_ID_STD_STORE);
|
||||
assert_eq!(resolved.profile_id(), "postgres_default");
|
||||
assert_eq!(resolved.profile_id(), "devnet");
|
||||
assert_eq!(resolved.target_id(), "devnet");
|
||||
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile);
|
||||
assert_eq!(resolved.settings().backend_kind(), ksp_store_lib::StoreBackendKind::Postgres);
|
||||
assert_eq!(resolved.settings().network().as_str(), "devnet");
|
||||
assert_eq!(resolved.settings().shutdown_timeout(), std::time::Duration::from_millis(5_000));
|
||||
let postgres = match resolved.settings().backend() {
|
||||
ksp_store_lib::StoreBackendSettings::Postgres(postgres) => std::option::Option::Some(postgres),
|
||||
@@ -35,10 +37,10 @@ fn committed_store_profile_maps_exact_runtime_settings_and_secret_fallback() {
|
||||
}
|
||||
assert!(resolved.effective().sensitivity().is_secret());
|
||||
let safe = resolved.effective().safe_value().to_string();
|
||||
assert!(!safe.contains("postgresql://localhost/ksp"));
|
||||
assert!(!safe.contains("postgresql://localhost/ksp_devnet"));
|
||||
assert!(safe.contains(crate::REDACTED_CONFIG_VALUE));
|
||||
let debug = format!("{resolved:?}");
|
||||
assert!(!debug.contains("postgresql://localhost/ksp"));
|
||||
assert!(!debug.contains("postgresql://localhost/ksp_devnet"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +51,9 @@ fn process_store_uri_wins_and_remains_redacted_in_safe_views() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let canary = "postgresql://secret-user:secret-pass@db.example/ksp";
|
||||
let canary = "postgresql://secret-user:secret-pass@db.example/ksp_devnet";
|
||||
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||
process.insert("KSP_SECRET_STORE_POSTGRES_URI".to_owned(), canary.to_owned());
|
||||
process.insert("KSP_SECRET_STORE_DEVNET_POSTGRES_URI".to_owned(), canary.to_owned());
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
let resolved = engine.load_resolved_store_config(std::option::Option::None, &environment);
|
||||
assert!(resolved.is_ok(), "secret process Store URI should map: {resolved:?}");
|
||||
@@ -82,10 +84,10 @@ fn literal_or_nonsecret_store_uri_is_rejected_by_effective_adapter() {
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let profiles = source.get_mut("profiles").and_then(serde_json::Value::as_array_mut);
|
||||
if let std::option::Option::Some(profiles) = profiles {
|
||||
if let std::option::Option::Some(profile) = profiles.first_mut() {
|
||||
profile["postgres"]["connection_uri"] = serde_json::Value::String(value.to_owned());
|
||||
}
|
||||
if let std::option::Option::Some(profiles) = profiles
|
||||
&& let std::option::Option::Some(profile) = profiles.first_mut()
|
||||
{
|
||||
profile["postgres"]["connection_uri"] = serde_json::Value::String(value.to_owned());
|
||||
}
|
||||
let engine = fixture_engine_with_document(fixture.path(), &source);
|
||||
assert!(engine.is_ok());
|
||||
@@ -104,6 +106,40 @@ fn literal_or_nonsecret_store_uri_is_rejected_by_effective_adapter() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_store_targets_select_one_network_and_database_without_runtime_multiplexing() {
|
||||
let engine = committed_engine();
|
||||
let engine = match engine {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||
process.insert("KSP_SECRET_STORE_DEVNET_POSTGRES_URI".to_owned(), "postgresql://devnet.invalid/ksp_devnet".to_owned());
|
||||
process.insert("KSP_SECRET_STORE_MAINNET_POSTGRES_URI".to_owned(), "postgresql://mainnet.invalid/ksp_mainnet".to_owned());
|
||||
process.insert("KSP_SECRET_STORE_TESTNET_POSTGRES_URI".to_owned(), "postgresql://testnet.invalid/ksp_testnet".to_owned());
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
for (target_id, network, expected_uri) in [
|
||||
("devnet", "devnet", "postgresql://devnet.invalid/ksp_devnet"),
|
||||
("mainnet", "mainnet-beta", "postgresql://mainnet.invalid/ksp_mainnet"),
|
||||
("testnet", "testnet", "postgresql://testnet.invalid/ksp_testnet"),
|
||||
] {
|
||||
let resolved = engine.load_resolved_store_config(std::option::Option::Some(target_id), &environment);
|
||||
assert!(resolved.is_ok(), "named Store target should resolve independently: {target_id}: {resolved:?}");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert_eq!(resolved.target_id(), target_id);
|
||||
assert_eq!(resolved.profile_id(), target_id);
|
||||
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
||||
assert_eq!(resolved.settings().network().as_str(), network);
|
||||
assert_eq!(
|
||||
resolved.effective().value().pointer("/postgres/connection_uri").and_then(serde_json::Value::as_str),
|
||||
std::option::Option::Some(expected_uri),
|
||||
);
|
||||
assert!(!resolved.effective().safe_value().to_string().contains(expected_uri));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||
let workspace = workspace_root();
|
||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
||||
@@ -122,24 +158,16 @@ fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||
fn fixture_engine_with_document(root: &std::path::Path, document: &serde_json::Value) -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||
let config_root = root.join("config");
|
||||
if let std::result::Result::Err(error) = std::fs::create_dir_all(config_root.as_path()) {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "test Config root cannot be created").with_source(error),
|
||||
);
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "test Config root cannot be created").with_source(error));
|
||||
}
|
||||
let bytes = serde_json::to_vec_pretty(document);
|
||||
let bytes = match bytes {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_SYNTAX_INVALID, "test Store Config cannot be encoded").with_source(error),
|
||||
);
|
||||
},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_SYNTAX_INVALID, "test Store Config cannot be encoded").with_source(error)),
|
||||
};
|
||||
let path = config_root.join(crate::DEFAULT_STD_STORE_FILENAME);
|
||||
if let std::result::Result::Err(error) = std::fs::write(path.as_path(), bytes) {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "test Store Config cannot be written").with_source(error),
|
||||
);
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "test Store Config cannot be written").with_source(error));
|
||||
}
|
||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths(config_root, workspace_root().join("config/schemas"));
|
||||
let bootstrap = match bootstrap {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/src/settings.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
const DEFAULT_CONNECT_TIMEOUT_MS: u64 = 10_000;
|
||||
const DEFAULT_MAX_CONNECTIONS: u32 = 8;
|
||||
@@ -117,7 +117,12 @@ impl PostgresPoolSettings {
|
||||
.with_context("maximum", MAX_CONNECTIONS.to_string()),
|
||||
);
|
||||
}
|
||||
let connect_validation = validate_duration("postgres.pool.connect_timeout", self.connect_timeout, MIN_CONNECT_TIMEOUT_MS, MAX_CONNECT_TIMEOUT_MS);
|
||||
let connect_validation = validate_duration(
|
||||
"postgres.pool.connect_timeout",
|
||||
self.connect_timeout,
|
||||
MIN_CONNECT_TIMEOUT_MS,
|
||||
MAX_CONNECT_TIMEOUT_MS,
|
||||
);
|
||||
if let std::result::Result::Err(error) = connect_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -125,12 +130,21 @@ impl PostgresPoolSettings {
|
||||
if let std::result::Result::Err(error) = wait_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let create_validation = validate_duration("postgres.pool.create_timeout", self.create_timeout, MIN_POOL_CREATE_TIMEOUT_MS, MAX_POOL_CREATE_TIMEOUT_MS);
|
||||
let create_validation = validate_duration(
|
||||
"postgres.pool.create_timeout",
|
||||
self.create_timeout,
|
||||
MIN_POOL_CREATE_TIMEOUT_MS,
|
||||
MAX_POOL_CREATE_TIMEOUT_MS,
|
||||
);
|
||||
if let std::result::Result::Err(error) = create_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let recycle_validation =
|
||||
validate_duration("postgres.pool.recycle_timeout", self.recycle_timeout, MIN_POOL_RECYCLE_TIMEOUT_MS, MAX_POOL_RECYCLE_TIMEOUT_MS);
|
||||
let recycle_validation = validate_duration(
|
||||
"postgres.pool.recycle_timeout",
|
||||
self.recycle_timeout,
|
||||
MIN_POOL_RECYCLE_TIMEOUT_MS,
|
||||
MAX_POOL_RECYCLE_TIMEOUT_MS,
|
||||
);
|
||||
if let std::result::Result::Err(error) = recycle_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -185,8 +199,12 @@ impl PostgresBootstrapSettings {
|
||||
|
||||
/// Validates bootstrap and migration deadlines without contacting PostgreSQL.
|
||||
pub fn validate(&self) -> ksp_store_api::Result<()> {
|
||||
let migration_validation =
|
||||
validate_duration("postgres.bootstrap.migration_timeout", self.migration_timeout, MIN_MIGRATION_TIMEOUT_MS, MAX_MIGRATION_TIMEOUT_MS);
|
||||
let migration_validation = validate_duration(
|
||||
"postgres.bootstrap.migration_timeout",
|
||||
self.migration_timeout,
|
||||
MIN_MIGRATION_TIMEOUT_MS,
|
||||
MAX_MIGRATION_TIMEOUT_MS,
|
||||
);
|
||||
if let std::result::Result::Err(error) = migration_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -312,14 +330,15 @@ impl StoreBackendSettings {
|
||||
#[derive(Debug)]
|
||||
pub struct StoreSettings {
|
||||
backend: StoreBackendSettings,
|
||||
network: ksp_store_api::RawNetworkId,
|
||||
shutdown_timeout: std::time::Duration,
|
||||
}
|
||||
|
||||
impl StoreSettings {
|
||||
/// Creates complete Store runtime settings from one explicit backend and shutdown bound.
|
||||
/// Creates complete Store runtime settings for one explicit logical network, backend and shutdown bound.
|
||||
#[must_use]
|
||||
pub const fn new(backend: StoreBackendSettings, shutdown_timeout: std::time::Duration) -> Self {
|
||||
return Self { backend, shutdown_timeout };
|
||||
pub fn new(network: ksp_store_api::RawNetworkId, backend: StoreBackendSettings, shutdown_timeout: std::time::Duration) -> Self {
|
||||
return Self { backend, network, shutdown_timeout };
|
||||
}
|
||||
|
||||
/// Returns the selected backend settings.
|
||||
@@ -334,6 +353,12 @@ impl StoreSettings {
|
||||
return self.backend.kind();
|
||||
}
|
||||
|
||||
/// Returns the single logical network bound to this Store instance.
|
||||
#[must_use]
|
||||
pub const fn network(&self) -> &ksp_store_api::RawNetworkId {
|
||||
return &self.network;
|
||||
}
|
||||
|
||||
/// Returns the maximum duration allowed for explicit Store shutdown.
|
||||
#[must_use]
|
||||
pub const fn shutdown_timeout(&self) -> std::time::Duration {
|
||||
@@ -355,10 +380,10 @@ impl StoreSettings {
|
||||
}
|
||||
|
||||
impl StoreSettings {
|
||||
/// Creates settings using the common default shutdown bound while keeping backend construction explicit.
|
||||
/// Creates settings using the common default shutdown bound while keeping network and backend construction explicit.
|
||||
#[must_use]
|
||||
pub fn with_default_shutdown(backend: StoreBackendSettings) -> Self {
|
||||
return Self::new(backend, std::time::Duration::from_millis(DEFAULT_SHUTDOWN_TIMEOUT_MS));
|
||||
pub fn with_default_shutdown(network: ksp_store_api::RawNetworkId, backend: StoreBackendSettings) -> Self {
|
||||
return Self::new(network, backend, std::time::Duration::from_millis(DEFAULT_SHUTDOWN_TIMEOUT_MS));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/tests/feature_mismatch.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -17,6 +17,13 @@ fn poll_ready<T>(future: impl std::future::Future<Output = T>) -> T {
|
||||
};
|
||||
}
|
||||
|
||||
fn valid_network() -> ksp_store_lib::RawNetworkId {
|
||||
return match ksp_store_lib::RawNetworkId::new("devnet") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("valid test network rejected: {error:?}"),
|
||||
};
|
||||
}
|
||||
|
||||
fn settings() -> ksp_store_lib::StoreSettings {
|
||||
let postgres = ksp_store_lib::PostgresStoreSettings::new(
|
||||
"postgresql://operator-supplied-sensitive-value",
|
||||
@@ -24,7 +31,7 @@ fn settings() -> ksp_store_lib::StoreSettings {
|
||||
ksp_store_lib::PostgresTlsMode::Disabled,
|
||||
ksp_store_lib::PostgresBootstrapSettings::default(),
|
||||
);
|
||||
return ksp_store_lib::StoreSettings::with_default_shutdown(ksp_store_lib::StoreBackendSettings::Postgres(postgres));
|
||||
return ksp_store_lib::StoreSettings::with_default_shutdown(valid_network(), ksp_store_lib::StoreBackendSettings::Postgres(postgres));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "postgres"))]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/tests/public_api.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -15,8 +15,13 @@ fn pre_003_settings_and_lifecycle_contract_are_available_from_crate_root() {
|
||||
ksp_store_lib::PostgresTlsMode::VerifyFull,
|
||||
ksp_store_lib::PostgresBootstrapSettings::default(),
|
||||
);
|
||||
let settings = ksp_store_lib::StoreSettings::with_default_shutdown(ksp_store_lib::StoreBackendSettings::Postgres(postgres));
|
||||
let network = match ksp_store_lib::RawNetworkId::new("devnet") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("valid public API network rejected: {error:?}"),
|
||||
};
|
||||
let settings = ksp_store_lib::StoreSettings::with_default_shutdown(network, ksp_store_lib::StoreBackendSettings::Postgres(postgres));
|
||||
assert_eq!(settings.backend_kind(), ksp_store_lib::StoreBackendKind::Postgres);
|
||||
assert_eq!(settings.network().as_str(), "devnet");
|
||||
assert!(settings.validate().is_ok());
|
||||
let _open = ksp_store_lib::Store::open;
|
||||
let _close = ksp_store_lib::Store::close;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
// file: crates/ksp-store-lib/unit_tests/settings.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
fn valid_network() -> crate::RawNetworkId {
|
||||
return match crate::RawNetworkId::new("devnet") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("valid test network rejected: {error:?}"),
|
||||
};
|
||||
}
|
||||
|
||||
fn valid_postgres_settings() -> crate::PostgresStoreSettings {
|
||||
return crate::PostgresStoreSettings::new(
|
||||
@@ -22,9 +29,10 @@ fn defaults_match_the_pre_001_runtime_bounds() {
|
||||
assert!(bootstrap.auto_migrate());
|
||||
assert_eq!(bootstrap.migration_timeout(), std::time::Duration::from_millis(30_000));
|
||||
assert_eq!(bootstrap.migration_lock_timeout(), std::time::Duration::from_millis(10_000));
|
||||
let store = crate::StoreSettings::with_default_shutdown(crate::StoreBackendSettings::Postgres(valid_postgres_settings()));
|
||||
let store = crate::StoreSettings::with_default_shutdown(valid_network(), crate::StoreBackendSettings::Postgres(valid_postgres_settings()));
|
||||
assert_eq!(store.shutdown_timeout(), std::time::Duration::from_millis(5_000));
|
||||
assert_eq!(store.backend_kind(), crate::StoreBackendKind::Postgres);
|
||||
assert_eq!(store.network().as_str(), "devnet");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,9 +117,11 @@ fn exact_runtime_boundaries_validate_and_adjacent_values_are_rejected() {
|
||||
|
||||
#[test]
|
||||
fn store_shutdown_bound_is_independent_from_backend_and_rejects_outside_values() {
|
||||
let valid = crate::StoreSettings::new(crate::StoreBackendSettings::Postgres(valid_postgres_settings()), std::time::Duration::from_millis(100));
|
||||
let valid =
|
||||
crate::StoreSettings::new(valid_network(), crate::StoreBackendSettings::Postgres(valid_postgres_settings()), std::time::Duration::from_millis(100));
|
||||
assert!(valid.validate().is_ok());
|
||||
let invalid = crate::StoreSettings::new(crate::StoreBackendSettings::Postgres(valid_postgres_settings()), std::time::Duration::from_millis(30_001));
|
||||
let invalid =
|
||||
crate::StoreSettings::new(valid_network(), crate::StoreBackendSettings::Postgres(valid_postgres_settings()), std::time::Duration::from_millis(30_001));
|
||||
let error = invalid.validate().err();
|
||||
assert_eq!(error.map(|value| return value.code()), std::option::Option::Some(crate::ERROR_CODE_SETTINGS_INVALID));
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/unit_tests/store.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
fn poll_ready<T>(future: impl std::future::Future<Output = T>) -> T {
|
||||
let mut future = std::boxed::Box::pin(future);
|
||||
@@ -11,6 +11,13 @@ fn poll_ready<T>(future: impl std::future::Future<Output = T>) -> T {
|
||||
};
|
||||
}
|
||||
|
||||
fn valid_network() -> crate::RawNetworkId {
|
||||
return match crate::RawNetworkId::new("devnet") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("valid test network rejected: {error:?}"),
|
||||
};
|
||||
}
|
||||
|
||||
fn valid_store_settings() -> crate::StoreSettings {
|
||||
let postgres = crate::PostgresStoreSettings::new(
|
||||
"postgresql://secret-user:secret-password@db.internal/ksp",
|
||||
@@ -18,7 +25,7 @@ fn valid_store_settings() -> crate::StoreSettings {
|
||||
crate::PostgresTlsMode::VerifyFull,
|
||||
crate::PostgresBootstrapSettings::default(),
|
||||
);
|
||||
return crate::StoreSettings::with_default_shutdown(crate::StoreBackendSettings::Postgres(postgres));
|
||||
return crate::StoreSettings::with_default_shutdown(valid_network(), crate::StoreBackendSettings::Postgres(postgres));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -29,7 +36,7 @@ fn invalid_settings_are_rejected_before_backend_dispatch() {
|
||||
crate::PostgresTlsMode::Disabled,
|
||||
crate::PostgresBootstrapSettings::default(),
|
||||
);
|
||||
let settings = crate::StoreSettings::with_default_shutdown(crate::StoreBackendSettings::Postgres(postgres));
|
||||
let settings = crate::StoreSettings::with_default_shutdown(valid_network(), crate::StoreBackendSettings::Postgres(postgres));
|
||||
let result = poll_ready(crate::Store::open(settings));
|
||||
let error = result.err();
|
||||
assert_eq!(error.map(|value| return value.code()), std::option::Option::Some(crate::ERROR_CODE_SETTINGS_INVALID));
|
||||
|
||||
77
deltas/0.3.2/pre.004-fix.001.md
Normal file
77
deltas/0.3.2/pre.004-fix.001.md
Normal file
@@ -0,0 +1,77 @@
|
||||
<!-- file: deltas/0.3.2/pre.004-fix.001.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.3.2-pre.004-fix.001` — Store targets réseau-spécifiques + gate fixes
|
||||
|
||||
## 1. Base
|
||||
|
||||
Base exacte : `0.3.2-pre.004`. Le gate opérateur du 29 août 2026 confirme `cargo check`, les tests Config/Store principaux et les trois builds Tauri, mais révèle :
|
||||
|
||||
```text
|
||||
1 warning Clippy collapsible_if dans ksp-config-lib/unit_tests/store.rs
|
||||
1 échec Config Desk : fixture cfg.std.store absente
|
||||
```
|
||||
|
||||
La discussion de design menée pendant ce gate confirme aussi qu'une instance `Store` ne doit jamais multiplexer plusieurs réseaux.
|
||||
|
||||
## 2. Décision target/réseau
|
||||
|
||||
Le mécanisme de profiles Config existant devient directement le mécanisme de targets Store nommés :
|
||||
|
||||
```text
|
||||
profile_id = target Store nommé
|
||||
default_profile = target autonome
|
||||
profile forcé = target explicitement imposé par job/worker/app
|
||||
1 target = 1 network + 1 backend + 1 URI/base
|
||||
1 StoreSettings = 1 RawNetworkId + 1 backend
|
||||
```
|
||||
|
||||
`ksp-store-lib` ne contient aucun registry ni routeur multi-target. Pour changer de réseau, le consumer résout un autre profile `std.store` puis ouvre une autre instance Store.
|
||||
|
||||
## 3. Targets committed
|
||||
|
||||
```text
|
||||
devnet -> network devnet -> KSP_SECRET_STORE_DEVNET_POSTGRES_URI -> ksp_devnet
|
||||
mainnet -> network mainnet-beta -> KSP_SECRET_STORE_MAINNET_POSTGRES_URI -> ksp_mainnet
|
||||
testnet -> network testnet -> KSP_SECRET_STORE_TESTNET_POSTGRES_URI -> ksp_testnet
|
||||
```
|
||||
|
||||
`default_profile = devnet`, cohérent avec le défaut Transport et plus sûr pour les workflows de développement. Le schema reste extensible à d'autres targets/réseaux sûrs.
|
||||
|
||||
## 4. Deuxième protection réseau
|
||||
|
||||
`StoreSettings` exige désormais un `RawNetworkId`. Les futures opérations RAW devront rejeter avant I/O toute identité/query dont le réseau ne correspond pas au réseau de l'instance Store. Cette vérification ne route jamais automatiquement vers un autre target.
|
||||
|
||||
## 5. Correctifs de gate
|
||||
|
||||
- collapse du `if let` imbriqué signalé par Clippy ;
|
||||
- ajout de `crates/ksp-config-lib/unit_tests/fixtures/std.store.json` ;
|
||||
- Config Desk vérifie désormais explicitement que `cfg.std.store` appartient à l'inventaire profilé ;
|
||||
- tests Config ajoutés pour la sélection explicite et indépendante de `devnet`, `mainnet` et `testnet`.
|
||||
|
||||
## 6. Version
|
||||
|
||||
```text
|
||||
workspace.package.version = 0.3.2-pre.4.fix.1
|
||||
```
|
||||
|
||||
## 7. Gate opérateur
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.2
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-config-lib
|
||||
cargo test -p ksp-store-lib
|
||||
cargo test -p ksp-store-lib --no-default-features
|
||||
cargo check -p ksp-store-lib --no-default-features
|
||||
cargo test -p ksp-app-config-desk
|
||||
```
|
||||
|
||||
Les trois builds Tauri `pre.004` sont déjà verts et les resources packagées ne changent pas dans ce fix.
|
||||
|
||||
## 8. Suite
|
||||
|
||||
Après gate propre, `pre.005` peut ouvrir la connexion PostgreSQL physique. Elle consommera un seul target/réseau déjà résolu ; aucun multiplexage réseau n'est à introduire.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Plan `0.3.2` — Store/PostgreSQL runtime foundation
|
||||
|
||||
@@ -349,6 +349,7 @@ Lorsque la feature est active, `ksp-store-lib` convertit en privé ces settings
|
||||
Les groupes de settings sont :
|
||||
|
||||
```text
|
||||
network RawNetworkId explicite, un seul réseau logique par instance Store
|
||||
connection_uri String possédée, Debug redacted, aucun getter de diagnostic
|
||||
pool bornes de section 6
|
||||
tls Disabled ou VerifyFull
|
||||
@@ -381,7 +382,7 @@ StoreSettings
|
||||
Store
|
||||
```
|
||||
|
||||
`PostgresPoolSettings::default()` et `PostgresBootstrapSettings::default()` portent exactement les bornes décidées par le gate `pre.001`. `StoreSettings::with_default_shutdown(...)` fournit uniquement la borne commune de shutdown à 5 s. Il n'existe toujours aucun `Default` pour `PostgresStoreSettings` ou `StoreSettings`, afin de ne jamais inventer une URI ou un backend de production.
|
||||
`PostgresPoolSettings::default()` et `PostgresBootstrapSettings::default()` portent exactement les bornes décidées par le gate `pre.001`. À partir de `pre.004-fix.001`, `StoreSettings` exige aussi un `RawNetworkId` explicite et `StoreSettings::with_default_shutdown(network, ...)` fournit uniquement la borne commune de shutdown à 5 s. Il n'existe toujours aucun `Default` pour `PostgresStoreSettings` ou `StoreSettings`, afin de ne jamais inventer une URI ou un backend de production.
|
||||
|
||||
Les validations `pre.003` restent backend-neutral et sans parsing PostgreSQL : URI non vide, pool 1..64, timeouts pool/connect 100..60 000 ms, migration 1 000..300 000 ms, lock 100..120 000 ms et shutdown 100..30 000 ms. Le parsing/normalisation de l'URI et les options réellement honorées restent propriétaires de `pre.005`.
|
||||
|
||||
@@ -611,28 +612,41 @@ Le document V1 est profilé comme les autres documents standards :
|
||||
```json
|
||||
{
|
||||
"format_version": 1,
|
||||
"default_profile": "postgres_default",
|
||||
"default_profile": "devnet",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "postgres_default",
|
||||
"profile_id": "devnet",
|
||||
"network": "devnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_POSTGRES_URI:-postgresql://localhost/ksp}",
|
||||
"pool": {
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 10000,
|
||||
"wait_timeout_ms": 5000,
|
||||
"create_timeout_ms": 10000,
|
||||
"recycle_timeout_ms": 5000
|
||||
},
|
||||
"tls": {
|
||||
"mode": "verify_full"
|
||||
},
|
||||
"bootstrap": {
|
||||
"auto_migrate": true,
|
||||
"migration_timeout_ms": 30000,
|
||||
"migration_lock_timeout_ms": 10000
|
||||
},
|
||||
"connection_uri": "${KSP_SECRET_STORE_DEVNET_POSTGRES_URI:-postgresql://localhost/ksp_devnet}",
|
||||
"pool": { "max_connections": 8, "connect_timeout_ms": 10000, "wait_timeout_ms": 5000, "create_timeout_ms": 10000, "recycle_timeout_ms": 5000 },
|
||||
"tls": { "mode": "verify_full" },
|
||||
"bootstrap": { "auto_migrate": true, "migration_timeout_ms": 30000, "migration_lock_timeout_ms": 10000 },
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "mainnet",
|
||||
"network": "mainnet-beta",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",
|
||||
"pool": { "max_connections": 8, "connect_timeout_ms": 10000, "wait_timeout_ms": 5000, "create_timeout_ms": 10000, "recycle_timeout_ms": 5000 },
|
||||
"tls": { "mode": "verify_full" },
|
||||
"bootstrap": { "auto_migrate": true, "migration_timeout_ms": 30000, "migration_lock_timeout_ms": 10000 },
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile_id": "testnet",
|
||||
"network": "testnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_TESTNET_POSTGRES_URI:-postgresql://localhost/ksp_testnet}",
|
||||
"pool": { "max_connections": 8, "connect_timeout_ms": 10000, "wait_timeout_ms": 5000, "create_timeout_ms": 10000, "recycle_timeout_ms": 5000 },
|
||||
"tls": { "mode": "verify_full" },
|
||||
"bootstrap": { "auto_migrate": true, "migration_timeout_ms": 30000, "migration_lock_timeout_ms": 10000 },
|
||||
"shutdown_timeout_ms": 5000
|
||||
}
|
||||
}
|
||||
@@ -640,13 +654,17 @@ Le document V1 est profilé comme les autres documents standards :
|
||||
}
|
||||
```
|
||||
|
||||
Le fallback localhost est classé `Secret` car il appartient à un placeholder `KSP_SECRET_*`; sa safe projection reste redacted. Il ne provoque aucune connexion automatique.
|
||||
Chaque profile `std.store` est un **target Store nommé** : son `profile_id` est l'identifiant de target, son champ `network` fixe exactement un réseau logique et son backend désigne exactement une base/configuration physique. `default_profile` choisit le target autonome ; un job, worker ou app peut imposer un autre `profile_id` via le mécanisme Config existant. `ksp-store-lib` ne route jamais entre plusieurs targets.
|
||||
|
||||
Le target par défaut committed est `devnet`, cohérent avec le défaut Transport et plus sûr pour les workflows de développement. Les targets `mainnet` et `testnet` utilisent des URI distinctes. Le fallback localhost de chaque target est classé `Secret` car il appartient à un placeholder `KSP_SECRET_*`; sa safe projection reste redacted. Il ne provoque aucune connexion automatique.
|
||||
|
||||
`.env.example` ajoutera dans la même tranche :
|
||||
|
||||
```text
|
||||
# PostgreSQL connection URI used by Config std.store profiles.
|
||||
# KSP_SECRET_STORE_POSTGRES_URI=postgresql://user:password@localhost/ksp
|
||||
# PostgreSQL connection URIs used by named Config std.store targets.
|
||||
# KSP_SECRET_STORE_DEVNET_POSTGRES_URI=postgresql://user:password@localhost/ksp_devnet
|
||||
# KSP_SECRET_STORE_MAINNET_POSTGRES_URI=postgresql://user:password@localhost/ksp_mainnet
|
||||
# KSP_SECRET_STORE_TESTNET_POSTGRES_URI=postgresql://user:password@localhost/ksp_testnet
|
||||
```
|
||||
|
||||
La valeur d'exemple commentée n'est jamais un vrai credential.
|
||||
@@ -657,6 +675,10 @@ Config reconnaît `backend = "postgres"` indépendamment de la feature du consum
|
||||
|
||||
Ainsi Config ne doit pas connaître les `cfg(feature = "postgres")` de chaque consumer.
|
||||
|
||||
### 12.4 Invariant target/réseau
|
||||
|
||||
Une instance `Store` ouverte reçoit exactement un `StoreSettings.network`. Ce réseau provient du target Config sélectionné, mais reste un contrat Store backend-neutral. Les futures opérations RAW doivent comparer ce réseau à celui de l'identité/query avant I/O : une transaction/account/query `devnet` soumise à un Store `mainnet-beta` doit être rejetée, jamais routée vers un autre Store. La sélection d'un autre réseau se fait en résolvant un autre profile/target puis en ouvrant une autre instance `Store`.
|
||||
|
||||
## 13. Logging et redaction
|
||||
|
||||
Les deux crates runtime auront un `constants.rs` privé avec un target explicite :
|
||||
@@ -937,7 +959,7 @@ Le dernier comportement est volontairement transitoire : `pre.003` ne retourne j
|
||||
|
||||
### `pre.004` — Config `std.store`
|
||||
|
||||
Document/schema/example/registry/adaptor, `.env.example`, provenance/sensitivity/redaction, packaging resources strictement nécessaires. Aucun env dans Store/backend. `pre.004` matérialise les noms canoniques `std.store.json`, `std.store.schema.json` et `std.store.example.json`, conformément au prompt de démarrage et aux conventions Config existantes.
|
||||
Document/schema/example/registry/adaptor, `.env.example`, provenance/sensitivity/redaction, targets nommés réseau-spécifiques, packaging resources strictement nécessaires. Aucun env dans Store/backend. `pre.004` matérialise les noms canoniques `std.store.json`, `std.store.schema.json` et `std.store.example.json`, conformément au prompt de démarrage et aux conventions Config existantes.
|
||||
|
||||
### `pre.005` — PostgreSQL connection + deadpool + Rustls
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Validation `0.3.2` — Store/PostgreSQL runtime foundation
|
||||
|
||||
@@ -213,28 +213,46 @@ Critères :
|
||||
std.store enregistré
|
||||
schema V1 valide
|
||||
example valide
|
||||
profiles typés
|
||||
profiles/targets nommés typés
|
||||
un réseau explicite par target
|
||||
backend postgres explicite
|
||||
```
|
||||
|
||||
Matérialisé par `pre.004` avec `cfg.std.store` / `schema.std.store`, V1 profilée, backend PostgreSQL typé et example canonique.
|
||||
Matérialisé par `pre.004` puis corrigé par `pre.004-fix.001` avec `cfg.std.store` / `schema.std.store`, trois targets committed `devnet`/`mainnet`/`testnet`, `default_profile = devnet`, un `network` explicite par target et des URI PostgreSQL séparées.
|
||||
|
||||
Statut : `TODO gate opérateur pre.004`.
|
||||
Statut : `TODO gate opérateur pre.004-fix.001`.
|
||||
|
||||
### V32-CONFIG-002 — Secrets/provenance
|
||||
|
||||
Critères :
|
||||
|
||||
```text
|
||||
KSP_SECRET_STORE_POSTGRES_URI classé Secret
|
||||
KSP_SECRET_STORE_{DEVNET,MAINNET,TESTNET}_POSTGRES_URI classés Secret
|
||||
safe projection redacted
|
||||
provenance sans valeur
|
||||
dotenv inventory à jour
|
||||
```
|
||||
|
||||
Matérialisé par `pre.004` : `KSP_SECRET_STORE_POSTGRES_URI` est inventorié, le fallback reste `Secret`, la safe projection est redacted et la provenance n'embarque aucune valeur.
|
||||
Matérialisé par `pre.004-fix.001` : les trois URI réseau-spécifiques sont inventoriées, chaque fallback reste `Secret`, la safe projection est redacted et la provenance n'embarque aucune valeur.
|
||||
|
||||
Statut : `TODO gate opérateur pre.004`.
|
||||
Statut : `TODO gate opérateur pre.004-fix.001`.
|
||||
|
||||
### V32-CONFIG-002B — Target/réseau sans multiplexage
|
||||
|
||||
Critères :
|
||||
|
||||
```text
|
||||
profile_id std.store = identifiant de target Store
|
||||
default_profile choisit exactement un target
|
||||
StoreSettings porte exactement un RawNetworkId
|
||||
résolution explicite mainnet/devnet/testnet retourne URI + network du target choisi
|
||||
ksp-store-lib ne possède aucun routeur multi-target/multi-réseau
|
||||
future RAW mismatch Store.network != entity/query.network rejeté avant I/O
|
||||
```
|
||||
|
||||
Matérialisé contractuellement par `pre.004-fix.001`; l'enforcement sur opérations RAW sera exercé dans `0.3.3`/`0.3.4`.
|
||||
|
||||
Statut : `TODO gate opérateur pre.004-fix.001 / TODO 0.3.3-0.3.4`.
|
||||
|
||||
### V32-CONFIG-003 — No-env Store/backend
|
||||
|
||||
@@ -249,17 +267,17 @@ PG*
|
||||
.pgpass
|
||||
```
|
||||
|
||||
`pre.004` renforce aussi le canari d'ownership avec les nouveaux filenames Store ; Store/backend restent sans dépendance Config et sans lecture KSP/KSPB.
|
||||
`pre.004` renforce aussi le canari d'ownership avec les nouveaux filenames Store ; `pre.004-fix.001` conserve cette frontière tout en ajoutant le réseau au contrat `StoreSettings`. Store/backend restent sans dépendance Config et sans lecture KSP/KSPB.
|
||||
|
||||
Statut : `TODO gate opérateur pre.004 / TODO pre.009`.
|
||||
Statut : `TODO gate opérateur pre.004-fix.001 / TODO pre.009`.
|
||||
|
||||
### V32-CONFIG-004 — Adapter Config -> Store
|
||||
|
||||
Critère : `ksp-config-lib` seul transforme un profil résolu en `StoreSettings` et ne transmet aucun secret dans diagnostics.
|
||||
Critère : `ksp-config-lib` seul transforme un profil/target résolu en `StoreSettings` réseau-spécifique et ne transmet aucun secret dans diagnostics.
|
||||
|
||||
Matérialisé par `pre.004` : seul `ksp-config-lib` dépend de `ksp-store-lib` avec `default-features = false` et construit `StoreSettings` sans forcer la feature backend.
|
||||
Matérialisé par `pre.004` puis `pre.004-fix.001` : seul `ksp-config-lib` dépend de `ksp-store-lib` avec `default-features = false`; il mappe le `profile_id` sélectionné vers un target, construit son `RawNetworkId` et ses settings backend sans forcer la feature backend.
|
||||
|
||||
Statut : `TODO gate opérateur pre.004`.
|
||||
Statut : `TODO gate opérateur pre.004-fix.001`.
|
||||
|
||||
## 6. Pool et lifecycle PostgreSQL
|
||||
|
||||
|
||||
Reference in New Issue
Block a user