v0.3.15-pre.006-fix.001

This commit is contained in:
2026-09-13 15:47:32 +02:00
parent 0a8ce6e5f9
commit f3ed86e54c
19 changed files with 676 additions and 341 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/README.md -->
<!-- version: 13 -->
<!-- version: 14 -->
# ksp-config-lib
@@ -104,7 +104,7 @@ Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configur
- [`../../config/std.store.json`](../../config/std.store.json) — targets Store PostgreSQL Devnet/Mainnet/Testnet et settings runtime bornés ;
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
- [`../../config/composite.ksp-app-backfill-desk.json`](../../config/composite.ksp-app-backfill-desk.json) — composition Logging/Transport/Store de Backfill Desk ;
- [`../../config/composite.ksp-app-raw-transaction-ingest-desk.json`](../../config/composite.ksp-app-raw-transaction-ingest-desk.json) — profils cohérents Logging/Transport/Store du Raw Transaction Ingest Desk ;
- [`../../config/composite.ksp-app-raw-transaction-ingest-desk.json`](../../config/composite.ksp-app-raw-transaction-ingest-desk.json) — profils réseau-centriques Logging/Transport/Store du Raw Transaction Ingest Desk, capables dagréger plusieurs sources Transport same-network ;
- [`../../config/composite.ksp-app-solprices-desk.json`](../../config/composite.ksp-app-solprices-desk.json) — composition Logging/Off-chain Transport de SOL Prices Desk ;
- [`../../config/composite.ksp-app-store-desk.json`](../../config/composite.ksp-app-store-desk.json) — composition Logging/Store de Store Desk ;
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Off-chain Transport/On-chain Transport/Wallet de Wallet Desk ;

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/USAGE.md -->
<!-- version: 16 -->
<!-- version: 17 -->
# Utilisation de ksp-config-lib
@@ -158,13 +158,13 @@ secret_metadata = metadata dont la valeur doit avoir une provenance KSP_SECRET_*
`protocol` n'est pas un nouveau `WsProtocolKind` et ne transforme pas PublicNode en protocole. Config résout les placeholders, vérifie la classe de sensibilité des metadata puis construit `YellowstoneGrpcTransportSettings`. Transport ne lit jamais l'environnement. Les valeurs de `secret_metadata` sont disponibles au runtime mais redacted dans `safe_value` et dans les représentations `Debug`.
Le provider affiche `solana-yellowstone-grpc.publicnode.com:443` ; le profil versionné `publicnode_mainnet` le représente sous la forme URL TLS requise par Transport, `https://solana-yellowstone-grpc.publicnode.com:443`, sans credential. Aucun hostname Testnet n'est versionné tant que sa valeur exacte n'est pas confirmée depuis une source opérateur/officielle.
Les profils versionnés `publicnode_mainnet` et `publicnode_testnet` représentent leurs endpoints Yellowstone sous la forme URL TLS requise par Transport et conservent le `x-token` dans `secret_metadata`. Ils restent des sources Transport network-scoped et ne définissent jamais lidentité du réseau applicatif.
L'accesseur historique `into_transport_settings()` conserve volontairement son tuple `(HTTP, Option<WS>)`. Un consumer ayant besoin des trois backends utilise `into_all_transport_settings()` ou les accesseurs séparés afin de ne pas casser silencieusement les consumers V2.
Les protocoles WebSocket restent `kind = "solana_standard"` et `kind = "helius_laserstream"`. Chaque endpoint peut en plus déclarer `capabilities`, liste de descriptors `WsSubscriptionKind` (`account`, `block`, `logs`, `program`, `root`, `signature`, `slot`, `slots_updates`, `vote`, `helius_transaction`). L'absence de cette liste reste backward-compatible mais ne constitue aucune preuve de support ; une liste présente est mappée explicitement vers Transport puis validée contre le protocole. Config ne déduit jamais une capability depuis `provider`.
Les profils Solana standard versionnés déclarent conservativement les six familles stables `account/logs/program/root/signature/slot` et ne déclarent donc pas `block`. Les profils `helius_devnet` et `helius_mainnet` déclarent les sept familles Helius déjà supportées par la façade (`account/logs/program/root/signature/slot/slots_updates`) plus `helius_transaction`, sans `block` ni `vote`. Helius LaserStream WebSocket conserve ses URLs Config-owned et sa clé `KSP_SECRET_HELIUS_API_KEY`; cette surface est indépendante de Yellowstone gRPC.
Les profils Solana standard versionnés déclarent conservativement les six familles stables `account/logs/program/root/signature/slot` et ne déclarent donc pas `block`. Le profil `testnet_public` fournit le socle HTTP + WS standard Testnet sans credential ; `publicnode_testnet` reste une source Yellowstone optionnelle séparée. Les profils `helius_devnet` et `helius_mainnet` déclarent les sept familles Helius déjà supportées par la façade (`account/logs/program/root/signature/slot/slots_updates`) plus `helius_transaction`, sans `block` ni `vote`. Helius LaserStream WebSocket conserve ses URLs Config-owned et sa clé `KSP_SECRET_HELIUS_API_KEY`; cette surface est indépendante de Yellowstone gRPC.
Les scalaires `*_ms` restent des valeurs Config et sont convertis en `std::time::Duration` par l'adapter. La dépendance reste unidirectionnelle : Config connaît les contrats Transport pour les construire ; Transport ne connaît ni Config, ni `.env`, ni les variables KSP.
@@ -347,6 +347,8 @@ Elles n'altèrent jamais l'environnement hérité du processus. Une valeur proce
Sur Unix, un nouveau `.env` est créé avec des permissions privées `0600`; les permissions existantes sont préservées lors des remplacements atomiques.
Le composite `cfg.composite.ksp-app-raw-transaction-ingest-desk` expose des profils applicatifs `devnet`, `mainnet` et `testnet`. Un même profil peut référencer `transport`, `transport.helius` et `transport.yellowstone` tant que tous les profils Transport résolus appartiennent au même réseau que Store. Le provider identifie une source de capability, jamais un réseau logique.
## 8. `.env.example`
`/.env.example` est l'inventaire versionné. `/.env` reste local et ignoré.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/unit_tests/composite.rs
// version: 5
// version: 6
const TEST_COMPOSITE_FILENAME: &str = "examples/composite.example.json";
const TEST_COMPOSITE_FILE_ID: &str = "cfg.composite.test";
@@ -129,7 +129,7 @@ fn duplicate_component_ids_are_rejected_inside_one_composite_profile() {
}
#[test]
fn v0_3_15_pre_003_raw_ingest_composite_keeps_transport_and_store_networks_coherent() {
fn v0_3_15_pre_006_fix_001_raw_ingest_composite_is_network_centric_and_keeps_all_transport_sources_coherent() {
let workspace = workspace_root();
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
assert!(bootstrap.is_ok(), "committed Config roots should be valid: {bootstrap:?}");
@@ -148,47 +148,58 @@ fn v0_3_15_pre_003_raw_ingest_composite_keeps_transport_and_store_networks_coher
process.insert("KSP_SECRET_PUBLICNODE_MAINNET_GRPC_X_TOKEN".to_owned(), "publicnode-mainnet-composite-canary".to_owned());
process.insert("KSP_SECRET_PUBLICNODE_TESTNET_GRPC_X_TOKEN".to_owned(), "publicnode-testnet-composite-canary".to_owned());
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
for (profile_id, expected_network) in [
("devnet_public", "devnet"),
("helius_devnet", "devnet"),
("orbitflare_devnet", "devnet"),
("mainnet_public", "mainnet"),
("helius_mainnet", "mainnet"),
("publicnode_mainnet", "mainnet"),
("publicnode_testnet", "testnet"),
for (profile_id, expected_network, expected_transport_profiles) in [
("devnet", "devnet", vec!["devnet_public", "helius_devnet", "orbitflare_devnet"]),
("mainnet", "mainnet", vec!["mainnet_public", "helius_mainnet", "publicnode_mainnet"]),
("testnet", "testnet", vec!["testnet_public", "publicnode_testnet"]),
] {
let composite = engine.load_resolved_composite(&file_id, std::option::Option::Some(profile_id));
assert!(composite.is_ok(), "Raw Ingest composite profile {profile_id} should resolve: {composite:?}");
assert!(composite.is_ok(), "Raw Ingest network profile {profile_id} should resolve: {composite:?}");
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => continue,
};
assert_eq!(composite.profile_id(), profile_id);
let transport_component = composite.component("transport");
let store_component = composite.component("store");
assert!(transport_component.is_some(), "profile {profile_id} should contain Transport");
assert!(store_component.is_some(), "profile {profile_id} should contain Store");
let (transport_component, store_component) = match (transport_component, store_component) {
(std::option::Option::Some(transport), std::option::Option::Some(store)) => (transport, store),
_ => continue,
let store_component = match store_component {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
assert_eq!(transport_component.resolved().profile_id(), profile_id);
assert_eq!(store_component.resolved().profile_id(), expected_network);
let transport = engine.resolve_transport_config_profile(transport_component.resolved(), &environment);
let store = engine.resolve_store_config_profile(store_component.resolved(), &environment);
assert!(transport.is_ok(), "Transport profile {profile_id} should map: {transport:?}");
assert!(store.is_ok(), "Store profile {expected_network} should map: {store:?}");
let (transport, store) = match (transport, store) {
(std::result::Result::Ok(transport), std::result::Result::Ok(store)) => (transport, store),
_ => continue,
};
assert_eq!(store.settings().network().as_str(), expected_network);
assert!(transport.http_settings().endpoints().iter().all(|endpoint| return endpoint.cluster().as_str() == expected_network));
if let std::option::Option::Some(ws) = transport.ws_settings() {
assert!(ws.endpoints().iter().all(|endpoint| return endpoint.cluster().as_str() == expected_network));
if let std::result::Result::Ok(store) = store {
assert_eq!(store.settings().network().as_str(), expected_network);
}
if let std::option::Option::Some(grpc) = transport.grpc_settings() {
assert!(grpc.endpoints().iter().all(|endpoint| return endpoint.cluster().as_str() == expected_network));
let transport_components = composite
.components()
.iter()
.filter(|(component_id, component)| {
return component_id.starts_with("transport") && component.resolved().file_id().as_str() == crate::FILE_ID_STD_TRANSPORT;
})
.map(|(_, component)| return component)
.collect::<std::vec::Vec<_>>();
assert_eq!(transport_components.len(), expected_transport_profiles.len());
let actual_profiles = transport_components.iter().map(|component| return component.resolved().profile_id()).collect::<std::vec::Vec<_>>();
for expected_transport_profile in expected_transport_profiles {
assert!(
actual_profiles.contains(&expected_transport_profile),
"network profile {profile_id} should include Transport source {expected_transport_profile}"
);
}
for component in transport_components {
let transport = engine.resolve_transport_config_profile(component.resolved(), &environment);
assert!(transport.is_ok(), "Transport source {} should map: {transport:?}", component.resolved().profile_id());
if let std::result::Result::Ok(transport) = transport {
assert!(transport.http_settings().endpoints().iter().all(|endpoint| return endpoint.cluster().as_str() == expected_network));
if let std::option::Option::Some(ws) = transport.ws_settings() {
assert!(ws.endpoints().iter().all(|endpoint| return endpoint.cluster().as_str() == expected_network));
}
if let std::option::Option::Some(grpc) = transport.grpc_settings() {
assert!(grpc.endpoints().iter().all(|endpoint| return endpoint.cluster().as_str() == expected_network));
}
}
}
}
}

View File

@@ -1,9 +1,9 @@
{
"format_version": 1,
"default_profile": "devnet_public",
"default_profile": "devnet",
"profiles": [
{
"profile_id": "devnet_public",
"profile_id": "devnet",
"documents": [
{
"component_id": "logging",
@@ -16,42 +16,12 @@
"profile_id": "secret_test"
},
{
"component_id": "store",
"file_id": "cfg.std.store",
"profile_id": "devnet"
}
]
},
{
"profile_id": "helius_devnet",
"documents": [
{
"component_id": "logging",
"file_id": "cfg.std.logging",
"profile_id": "local_dev"
},
{
"component_id": "transport",
"component_id": "transport.helius",
"file_id": "cfg.std.transport",
"profile_id": "helius_devnet"
},
{
"component_id": "store",
"file_id": "cfg.std.store",
"profile_id": "devnet"
}
]
},
{
"profile_id": "orbitflare_devnet",
"documents": [
{
"component_id": "logging",
"file_id": "cfg.std.logging",
"profile_id": "local_dev"
},
{
"component_id": "transport",
"component_id": "transport.yellowstone",
"file_id": "cfg.std.transport",
"profile_id": "secret_test"
},
@@ -63,7 +33,7 @@
]
},
{
"profile_id": "mainnet_public",
"profile_id": "mainnet",
"documents": [
{
"component_id": "logging",
@@ -75,6 +45,16 @@
"file_id": "cfg.std.transport",
"profile_id": "secret_test"
},
{
"component_id": "transport.helius",
"file_id": "cfg.std.transport",
"profile_id": "helius_devnet"
},
{
"component_id": "transport.yellowstone",
"file_id": "cfg.std.transport",
"profile_id": "secret_test"
},
{
"component_id": "store",
"file_id": "cfg.std.store",
@@ -83,7 +63,7 @@
]
},
{
"profile_id": "helius_mainnet",
"profile_id": "testnet",
"documents": [
{
"component_id": "logging",
@@ -96,42 +76,7 @@
"profile_id": "secret_test"
},
{
"component_id": "store",
"file_id": "cfg.std.store",
"profile_id": "mainnet"
}
]
},
{
"profile_id": "publicnode_mainnet",
"documents": [
{
"component_id": "logging",
"file_id": "cfg.std.logging",
"profile_id": "local_dev"
},
{
"component_id": "transport",
"file_id": "cfg.std.transport",
"profile_id": "secret_test"
},
{
"component_id": "store",
"file_id": "cfg.std.store",
"profile_id": "mainnet"
}
]
},
{
"profile_id": "publicnode_testnet",
"documents": [
{
"component_id": "logging",
"file_id": "cfg.std.logging",
"profile_id": "local_dev"
},
{
"component_id": "transport",
"component_id": "transport.yellowstone",
"file_id": "cfg.std.transport",
"profile_id": "secret_test"
},

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/unit_tests/transport.rs
// version: 14
// version: 15
#[test]
fn fixture_transport_profile_maps_complete_runtime_contract() {
@@ -211,6 +211,33 @@ fn committed_v3_orbitflare_devnet_profile_maps_provider_neutral_yellowstone_grpc
}
}
#[test]
fn v0_3_15_pre_006_fix_001_testnet_public_is_keyless_standard_transport_without_yellowstone() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let resolved = engine.load_resolved_transport_config(std::option::Option::Some("testnet_public"), &environment);
assert!(resolved.is_ok(), "committed keyless Testnet Transport profile should map without provider credentials: {resolved:?}");
if let std::result::Result::Ok(resolved) = resolved {
assert_eq!(resolved.profile_id(), "testnet_public");
assert_eq!(resolved.settings().endpoints().len(), 1);
assert_eq!(resolved.settings().endpoints()[0].cluster().as_str(), "testnet");
let ws = resolved.ws_settings();
assert!(ws.is_some());
if let std::option::Option::Some(ws) = ws {
assert_eq!(ws.endpoints().len(), 1);
assert_eq!(ws.endpoints()[0].cluster().as_str(), "testnet");
assert_eq!(ws.endpoints()[0].protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
assert!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs));
assert!(!ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
}
assert!(resolved.grpc_settings().is_none(), "keyless Testnet base profile must not conflate PublicNode Yellowstone with the network itself");
}
}
#[test]
fn committed_v3_publicnode_profiles_map_provider_neutral_yellowstone_grpc_with_network_scoped_secret_x_tokens() {
let engine = committed_engine();