diff --git a/Cargo.toml b/Cargo.toml index be2b1fb..0554a0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 603 +# version: 604 [workspace] resolver = "3" members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"] [workspace.package] -version = "0.3.15-pre.6.fix.1" +version = "0.3.15-pre.6.fix.2" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/config/std.transport.json b/config/std.transport.json index ae93276..a64bf27 100644 --- a/config/std.transport.json +++ b/config/std.transport.json @@ -78,6 +78,7 @@ "url": "${KSP_PUBLIC_SOLANA_DEVNET_WS_URL:-wss://api.devnet.solana.com}", "capabilities": [ "account", + "block", "logs", "program", "root", @@ -178,6 +179,7 @@ "url": "${KSP_PUBLIC_SOLANA_DEVNET_WS_URL:-wss://api.devnet.solana.com}", "capabilities": [ "account", + "block", "logs", "program", "root", @@ -244,6 +246,7 @@ "url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet.solana.com}", "capabilities": [ "account", + "block", "logs", "program", "root", @@ -398,6 +401,7 @@ "url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet.solana.com}", "capabilities": [ "account", + "block", "logs", "program", "root", @@ -447,6 +451,7 @@ "url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet.solana.com}", "capabilities": [ "account", + "block", "logs", "program", "root", @@ -512,6 +517,7 @@ "url": "wss://api.testnet.solana.com", "capabilities": [ "account", + "block", "logs", "program", "root", @@ -561,6 +567,7 @@ "url": "wss://api.testnet.solana.com", "capabilities": [ "account", + "block", "logs", "program", "root", diff --git a/crates/ksp-app-raw-transaction-ingest-desk/README.md b/crates/ksp-app-raw-transaction-ingest-desk/README.md index 53753dd..41506e3 100644 --- a/crates/ksp-app-raw-transaction-ingest-desk/README.md +++ b/crates/ksp-app-raw-transaction-ingest-desk/README.md @@ -1,5 +1,5 @@ - + # `ksp-app-raw-transaction-ingest-desk` @@ -41,6 +41,8 @@ helius-transaction-hydrated http-block-polling ``` +Les profils standard publics committés déclarent `Block` en plus de `Logs`; `standard-block-direct` est donc composable depuis Config sur Devnet, Mainnet et Testnet. Cette déclaration ne transforme pas `blockSubscribe` en méthode stable et ne remplace pas la revalidation runtime. + Une route `Configured` est uniquement **composable depuis Config**. La disponibilité réelle des connexions, du Store et du Worker est revalidée lors du Start avant qu'une route puisse devenir `Running`. ## Ports de développement diff --git a/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md b/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md index 017eb23..5eb4b1a 100644 --- a/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md +++ b/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de `ksp-app-raw-transaction-ingest-desk` @@ -29,7 +29,7 @@ raison sûre lorsque la route est indisponible génération d'inventaire ``` -`Configured` signifie uniquement que toutes les requirements Config nécessaires à la route sont cohérentes. Aucun probe réseau, aucune ouverture de Store et aucun Worker ne sont déclenchés par l'inventaire. +`Configured` signifie uniquement que toutes les requirements Config nécessaires à la route sont cohérentes. Les profils `solana-public` standard committés déclarent explicitement les capabilities `Logs` et `Block`; `standard-block-direct` est donc Configured sur les trois réseaux logiques tant que ce socle Config reste valide. Aucun probe réseau, aucune ouverture de Store et aucun Worker ne sont déclenchés par l'inventaire. Un refresh reconstruit l'inventaire depuis Config et publie une nouvelle génération. La sélection visuelle d'un réseau ne choisit aucune URL, provider ou ressource physique et ne démarre aucune acquisition. Une capability optionnelle dont le secret n'est pas résolu rend uniquement la route qui en dépend indisponible ; les routes satisfaites par le Transport de base du même réseau restent composables. diff --git a/crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs b/crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs index 9e68f3c..c43c44f 100644 --- a/crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs +++ b/crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs -// version: 2 +// version: 3 #[test] fn pre_006_route_requirement_projection_is_fail_closed_and_ordered() { @@ -38,14 +38,19 @@ fn pre_006_unavailable_profile_projects_all_five_routes_without_physical_materia } #[test] -fn pre_006_fix_001_network_profiles_aggregate_same_network_transport_capabilities() { +fn pre_006_fix_002_network_profiles_aggregate_same_network_transport_capabilities_and_standard_block() { let management = committed_management(); assert!(management.is_ok()); let management = match management { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; - let environment = all_optional_source_secrets(); + let environment = ksp_config_lib::ConfigEnvironment::load(); + assert!(environment.is_ok()); + let environment = match environment { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; let inventory = super::build_route_inventory_with_environment(&management, &environment, 7); assert!(inventory.is_ok()); let inventory = match inventory { @@ -63,32 +68,29 @@ fn pre_006_fix_001_network_profiles_aggregate_same_network_transport_capabilitie if let std::option::Option::Some(devnet) = devnet { assert!(devnet.is_default); assert_eq!(devnet.network.as_deref(), std::option::Option::Some("devnet")); - assert_route(devnet, crate::RawIngestRouteId::YellowstoneHydrated, true, std::option::Option::None); + assert_optional_secret_route(devnet, crate::RawIngestRouteId::YellowstoneHydrated); assert_route(devnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None); - assert_route( - devnet, - crate::RawIngestRouteId::StandardBlockDirect, - false, - std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingWsBlockCapability), - ); - assert_route(devnet, crate::RawIngestRouteId::HeliusTransactionHydrated, true, std::option::Option::None); + assert_route(devnet, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None); + assert_optional_secret_route(devnet, crate::RawIngestRouteId::HeliusTransactionHydrated); assert_route(devnet, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None); } let mainnet = profile(&inventory, "mainnet"); assert!(mainnet.is_some()); if let std::option::Option::Some(mainnet) = mainnet { assert_eq!(mainnet.network.as_deref(), std::option::Option::Some("mainnet")); - assert_route(mainnet, crate::RawIngestRouteId::YellowstoneHydrated, true, std::option::Option::None); + assert_optional_secret_route(mainnet, crate::RawIngestRouteId::YellowstoneHydrated); assert_route(mainnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None); - assert_route(mainnet, crate::RawIngestRouteId::HeliusTransactionHydrated, true, std::option::Option::None); + assert_route(mainnet, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None); + assert_optional_secret_route(mainnet, crate::RawIngestRouteId::HeliusTransactionHydrated); assert_route(mainnet, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None); } let testnet = profile(&inventory, "testnet"); assert!(testnet.is_some()); if let std::option::Option::Some(testnet) = testnet { assert_eq!(testnet.network.as_deref(), std::option::Option::Some("testnet")); - assert_route(testnet, crate::RawIngestRouteId::YellowstoneHydrated, true, std::option::Option::None); + assert_optional_secret_route(testnet, crate::RawIngestRouteId::YellowstoneHydrated); assert_route(testnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None); + assert_route(testnet, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None); assert_route( testnet, crate::RawIngestRouteId::HeliusTransactionHydrated, @@ -119,37 +121,33 @@ fn pre_006_fix_001_network_profiles_aggregate_same_network_transport_capabilitie } #[test] -fn pre_006_fix_001_missing_optional_source_secret_does_not_disable_base_network_routes() { +fn pre_006_fix_002_base_network_routes_remain_configured_independently_of_optional_sources() { let management = committed_management(); assert!(management.is_ok()); let management = match management { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; - let environment = ksp_config_lib::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new()); + let environment = ksp_config_lib::ConfigEnvironment::load(); + assert!(environment.is_ok()); + let environment = match environment { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; let inventory = super::build_route_inventory_with_environment(&management, &environment, 11); assert!(inventory.is_ok()); let inventory = match inventory { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; - let mainnet = profile(&inventory, "mainnet"); - assert!(mainnet.is_some()); - if let std::option::Option::Some(mainnet) = mainnet { - assert_route(mainnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None); - assert_route(mainnet, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None); - assert_route( - mainnet, - crate::RawIngestRouteId::YellowstoneHydrated, - false, - std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingRequiredSecret), - ); - assert_route( - mainnet, - crate::RawIngestRouteId::HeliusTransactionHydrated, - false, - std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingRequiredSecret), - ); + for profile_id in ["devnet", "mainnet", "testnet"] { + let selected = profile(&inventory, profile_id); + assert!(selected.is_some()); + if let std::option::Option::Some(selected) = selected { + assert_route(selected, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None); + assert_route(selected, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None); + assert_route(selected, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None); + } } } @@ -182,19 +180,24 @@ fn committed_management() -> ksp_core_lib::Result ksp_config_lib::ConfigEnvironment { - let mut process = std::collections::BTreeMap::::new(); - process.insert("KSP_SECRET_HELIUS_API_KEY".to_owned(), "helius-route-inventory-canary".to_owned()); - process.insert("KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN".to_owned(), "orbitflare-route-inventory-canary".to_owned()); - process.insert("KSP_SECRET_PUBLICNODE_MAINNET_GRPC_X_TOKEN".to_owned(), "publicnode-mainnet-route-inventory-canary".to_owned()); - process.insert("KSP_SECRET_PUBLICNODE_TESTNET_GRPC_X_TOKEN".to_owned(), "publicnode-testnet-route-inventory-canary".to_owned()); - return ksp_config_lib::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new()); -} - fn profile<'a>(inventory: &'a crate::RawIngestRouteInventoryDto, profile_id: &str) -> std::option::Option<&'a crate::RawIngestProfileInventoryDto> { return inventory.profiles.iter().find(|profile| return profile.profile_id == profile_id); } +fn assert_optional_secret_route(profile: &crate::RawIngestProfileInventoryDto, route_id: crate::RawIngestRouteId) { + let route = profile.routes.iter().find(|route| return route.route_id == route_id); + assert!(route.is_some()); + if let std::option::Option::Some(route) = route { + if route.selectable { + assert_eq!(route.state, crate::RawIngestRouteState::Configured); + assert_eq!(route.reason, std::option::Option::None); + } else { + assert_eq!(route.state, crate::RawIngestRouteState::Unavailable); + assert_eq!(route.reason, std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingRequiredSecret)); + } + } +} + fn assert_route( profile: &crate::RawIngestProfileInventoryDto, route_id: crate::RawIngestRouteId, diff --git a/crates/ksp-config-lib/USAGE.md b/crates/ksp-config-lib/USAGE.md index 296ea06..0277459 100644 --- a/crates/ksp-config-lib/USAGE.md +++ b/crates/ksp-config-lib/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de ksp-config-lib @@ -164,7 +164,7 @@ L'accesseur historique `into_transport_settings()` conserve volontairement son t 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`. 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 profils Solana standard publics versionnés déclarent `account/block/logs/program/root/signature/slot`. `block` reste une capability explicite, instable et validator/provider-gated : Config ne l’infère jamais du protocol kind ni du provider, mais les endpoints publics committés l’annoncent désormais explicitement pour rendre la route Standard Block composable. 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. diff --git a/crates/ksp-config-lib/unit_tests/transport.rs b/crates/ksp-config-lib/unit_tests/transport.rs index c06e181..86dc59e 100644 --- a/crates/ksp-config-lib/unit_tests/transport.rs +++ b/crates/ksp-config-lib/unit_tests/transport.rs @@ -1,5 +1,5 @@ // file: crates/ksp-config-lib/unit_tests/transport.rs -// version: 15 +// version: 16 #[test] fn fixture_transport_profile_maps_complete_runtime_contract() { @@ -146,7 +146,7 @@ fn committed_transport_document_maps_default_and_explicit_profiles() { assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.devnet.solana.com"); 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!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block)); } } if let std::result::Result::Ok(mainnet) = mainnet { @@ -160,7 +160,7 @@ fn committed_transport_document_maps_default_and_explicit_profiles() { if let std::option::Option::Some(ws) = ws { assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.mainnet.solana.com"); 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!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block)); } } } @@ -232,7 +232,7 @@ fn v0_3_15_pre_006_fix_001_testnet_public_is_keyless_standard_transport_without_ 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!(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"); } @@ -588,6 +588,35 @@ fn helius_laserstream_mainnet_and_devnet_api_key_map_to_protocol_and_safe_redact assert!(devnet_debug.contains(crate::REDACTED_CONFIG_VALUE)); } +#[test] +fn v0_3_15_pre_006_fix_002_committed_public_standard_profiles_declare_block_capability() { + 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()); + for (profile_id, cluster) in [("devnet_public", "devnet"), ("mainnet_public", "mainnet"), ("testnet_public", "testnet")] { + let resolved = engine.load_resolved_transport_config(std::option::Option::Some(profile_id), &environment); + assert!(resolved.is_ok(), "committed public standard profile {profile_id} should resolve: {resolved:?}"); + let resolved = match resolved { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + let ws = resolved.ws_settings(); + assert!(ws.is_some(), "committed public standard profile {profile_id} should expose WebSocket settings"); + if let std::option::Option::Some(ws) = ws { + assert_eq!(ws.endpoints().len(), 1); + let endpoint = &ws.endpoints()[0]; + assert_eq!(endpoint.provider().as_str(), "solana-public"); + assert_eq!(endpoint.cluster().as_str(), cluster); + assert_eq!(endpoint.protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard); + assert!(endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs)); + assert!(endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block)); + } + } +} + #[test] fn v0_3_15_pre_003_committed_helius_profiles_map_transaction_capability_without_block_or_vote() { let engine = committed_engine(); diff --git a/deltas/0.3.15/pre.006-fix.002.md b/deltas/0.3.15/pre.006-fix.002.md new file mode 100644 index 0000000..4e6bf0b --- /dev/null +++ b/deltas/0.3.15/pre.006-fix.002.md @@ -0,0 +1,70 @@ + + + +# Delta `0.3.15-pre.006-fix.002` + +## Objet + +Corriger la seconde qualification de `pre.006` : `standard-block-direct` ne doit pas rester structurellement indisponible lorsque les endpoints Standard WS committés portent cette capability, et les canaris Desk ne doivent pas appeler une API Config crate-private. + +## Standard Block + +Les endpoints `solana-public` Standard WS committés de `devnet`, `mainnet` et `testnet` déclarent désormais explicitement : + +```text +account +block +logs +program +root +signature +slot +``` + +La route `standard-block-direct` devient donc `Configured` sur les trois profils réseau du Raw Transaction Ingest Desk. + +Cette déclaration reste une preuve de composabilité Config. `blockSubscribe` demeure unstable et validator/provider-gated ; aucun provider n'est inféré depuis son nom et aucune I/O n'est ajoutée à l'inventaire. + +Helius LaserStream reste inchangé et ne gagne pas `Block`. + +## Canaris Desk + +`pre.006-fix.001` utilisait `ConfigEnvironment::from_maps` depuis `ksp-app-raw-transaction-ingest-desk`, alors que ce helper est crate-private et compilé uniquement dans les tests de `ksp-config-lib`. + +Le fix retire cet appel invalide. Les canaris utilisent l'API publique `ConfigEnvironment::load()` pour l'environnement opérateur, valident de manière stricte les routes standard réseau-centriques et traitent les sources optionnelles Helius/Yellowstone comme conditionnelles aux secrets réellement présents. + +## Frontières conservées + +```text +aucun probe réseau dans l'inventaire ; +aucune ouverture Store ; +aucune session Transport ; +aucun Worker ; +aucun Start/Stop ; +aucune inférence provider ; +aucun secret/URL/URI/handle projeté au frontend. +``` + +## Version Cargo + +```text +header Cargo.toml : 603 -> 604 +workspace.package.version : 0.3.15-pre.6.fix.1 -> 0.3.15-pre.6.fix.2 +``` + +## Gate opérateur attendu + +```bash +cargo fmt --all +cargo fmt --all -- --check +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.15 +cargo check --workspace +cargo clippy --workspace --all-targets --all-features -- -D warnings +cargo test -p ksp-config-lib --all-targets --all-features +cargo test -p ksp-app-raw-transaction-ingest-desk --all-targets --all-features +cargo test --workspace --all-targets --all-features +(cd crates/ksp-app-raw-transaction-ingest-desk && cargo tauri dev) +``` + +Aucun `npm run build` direct n'appartient au gate. diff --git a/docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md b/docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md index 6376e0d..ea90093 100644 --- a/docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md +++ b/docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md @@ -1,5 +1,5 @@ - + # Plan v0.3.15 — Raw Transaction Ingest Desk @@ -193,10 +193,10 @@ Aucun gap Store n'est identifié. L'application ne dépend jamais de `ksp-store- | Profil/config | Réseau | Surfaces | Secret requis | Committed ? | Observation | |-------------------------|---------|---------------------------------------|-------------------|-------------|-----------------------------------------------------------------| -| `devnet_public` | devnet | HTTP + WS Solana standard | non | oui | capabilities stables déclarées ; `Logs` oui ; `Block` non | +| `devnet_public` | devnet | HTTP + WS Solana standard | non | oui | capabilities explicites ; `Logs` oui ; `Block` oui | | `helius_devnet` | devnet | HTTP public + WS Helius | API key Helius | oui | `HeliusTransaction` oui ; `Block`/`Vote` non | | `orbitflare_devnet` | devnet | HTTP + WS standard + Yellowstone gRPC | token gRPC secret | oui | `Logs` déclaré ; gRPC composable seulement si secret résolu | -| `mainnet_public` | mainnet | HTTP + WS Solana standard | non | oui | capabilities stables déclarées ; `Logs` oui ; `Block` non | +| `mainnet_public` | mainnet | HTTP + WS Solana standard | non | oui | capabilities explicites ; `Logs` oui ; `Block` oui | | `helius_mainnet` | mainnet | HTTP public + WS Helius | API key Helius | oui | `HeliusTransaction` oui ; `Block`/`Vote` non | | `mainnet_backfill_pool` | mainnet | plusieurs HTTP + WS standard | selon endpoints | oui | `Logs` déclaré ; profil conservé hors composite Raw Ingest Desk | | `publicnode_mainnet` | mainnet | HTTP + WS standard + Yellowstone gRPC | x-token secret | oui | source Yellowstone optionnelle du profil applicatif `mainnet` | @@ -240,7 +240,7 @@ Le frontend reçoit uniquement la projection sûre de ce modèle. Les ressources |-------------------------------|------------------|----------------------------------------------------------------|-------------------------------------------------|---------------------------------|-----------------------------------------------|-----------------------------------------------------------------------------------|------------------------------------------------|--------------------------------------| | `yellowstone-hydrated` | réseau du profil | Yellowstone gRPC Subscribe + HTTP `getTransaction` même réseau | scan HTTP repair si rôle compatible | gRPC + HTTP role | `RawTransactionIngestYellowstoneSource` | oui sur profil complet et secrets résolus | aucun contrat essentiel | provider-gated selon profil | | `standard-logs-hydrated` | réseau du profil | WS Solana standard `logsSubscribe` + HTTP `getTransaction` | scan HTTP repair si rôle compatible | `Logs` + HTTP role | `RawTransactionIngestStandardLogsSource` | oui côté Config sur profils déclarant `Logs` ; enforcement Worker en `pre.004` | enforcement Worker minimal | oui, notamment Devnet keyless | -| `standard-block-direct` | réseau du profil | WS Solana standard `blockSubscribe` explicitement déclaré | aucune hydration obligatoire | WS capability `Block` | `RawTransactionIngestStandardBlockSource` | non : aucun profil committed ne déclare `Block` | enforcement Worker minimal + endpoint qualifié | seulement endpoint prouvé compatible | +| `standard-block-direct` | réseau du profil | WS Solana standard `blockSubscribe` explicitement déclaré | aucune hydration obligatoire | WS capability `Block` | `RawTransactionIngestStandardBlockSource` | oui sur les profils publics standard qui déclarent explicitement `Block` | enforcement Worker minimal + endpoint qualifié | seulement endpoint prouvé compatible | | `helius-transaction-hydrated` | réseau du profil | WS Helius `transactionSubscribe` + HTTP `getTransaction` | scan HTTP repair si rôle compatible | `HeliusTransaction` + HTTP role | `RawTransactionIngestHeliusTransactionSource` | oui côté Config sur `helius_*` si secret résolu ; enforcement Worker en `pre.004` | enforcement Worker minimal | provider-gated Helius | | `http-block-polling` | réseau du profil | HTTP `getSlot` + `getBlocksWithLimit` + `getBlock` | `getTransaction` pour known-reference hydration | HTTP role | `RawTransactionIngestHttpBlockPollingSource` | oui lorsque le rôle résolu annonce les trois méthodes | aucun contrat essentiel | oui sur endpoint public compatible | @@ -282,7 +282,7 @@ capability WS explicite = Block commitment confirmed ou finalized ``` -Aucun HTTP n'est obligatoire pour le chemin nominal, car le matériau bloc est RAW-direct. Cette route reste non sélectionnable tant que la capability `Block` n'est pas explicitement déclarée dans le profil exact. +Aucun HTTP n'est obligatoire pour le chemin nominal, car le matériau bloc est RAW-direct. Cette route reste non sélectionnable tant que la capability `Block` n'est pas explicitement déclarée dans un Transport same-network. Les profils publics standard committed Devnet/Mainnet/Testnet la déclarent désormais explicitement ; la méthode reste unstable et sera revalidée au Start. ### 8.4 `helius-transaction-hydrated` @@ -728,7 +728,7 @@ Restauration du template Desk KSP clair, bornage de la grille de routes et corre Résoudre Config côté Rust et projeter les routes composables/non composables avec raisons sûres et génération d'inventaire, sans endpoint, URL, secret ou source key côté frontend. -**État : implémenté puis corrigé par `pre.006-fix.001`.** Le Desk ouvre uniquement la dépendance directe `ksp-onchain-transport-lib` nécessaire aux capabilities typées. Le composite applicatif est réseau-centrique (`devnet`, `mainnet`, `testnet`) et peut référencer plusieurs profils Transport du même réseau. Le backend agrège leurs capabilities sans inférer quoi que ce soit du provider : PublicNode Mainnet Yellowstone est donc une capability de `mainnet`, pas un pseudo-réseau ni un profil opérateur séparé. Les sources optionnelles Helius/Yellowstone dont le secret manque ne désactivent que la route qui dépend de cette source. HTTP est prouvé directement par rôles + `request_kinds`, WS par `WsProtocolKind` + `WsSubscriptionKind`, Yellowstone par présence d'un endpoint gRPC activé. Aucun Store, session Transport ou Worker n'est ouvert. +**État : implémenté puis corrigé par `pre.006-fix.001` et `pre.006-fix.002`.** Le Desk ouvre uniquement la dépendance directe `ksp-onchain-transport-lib` nécessaire aux capabilities typées. Le composite applicatif est réseau-centrique (`devnet`, `mainnet`, `testnet`) et peut référencer plusieurs profils Transport du même réseau. Le backend agrège leurs capabilities sans inférer quoi que ce soit du provider : PublicNode Mainnet Yellowstone est donc une capability de `mainnet`, pas un pseudo-réseau ni un profil opérateur séparé. Les sources optionnelles Helius/Yellowstone dont le secret manque ne désactivent que la route qui dépend de cette source. HTTP est prouvé directement par rôles + `request_kinds`, WS par `WsProtocolKind` + `WsSubscriptionKind`, Yellowstone par présence d'un endpoint gRPC activé. Aucun Store, session Transport ou Worker n'est ouvert. Budget cible : une tranche composition. diff --git a/docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md b/docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md index e1e4f20..59a7d5a 100644 --- a/docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md +++ b/docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md @@ -1,5 +1,5 @@ - + # Validation v0.3.15 — Raw Transaction Ingest Desk @@ -362,12 +362,20 @@ Le forecast durable est porté par la section 20 du plan `036` sous forme hiéra ### `pre.006` — inventaire composable -**Statut : gate opérateur de `pre.006` en échec fonctionnel, corrigé par `pre.006-fix.001`.** Le premier inventaire classait encore les capabilities par profil Transport fournisseur : `Yellowstone + HTTP hydration` Mainnet n'était donc `Configured` que sous `publicnode_mainnet`. Le fix remplace cette projection par trois profils réseau (`devnet`, `mainnet`, `testnet`) qui agrègent les sources Transport same-network. PublicNode, Helius et OrbitFlare restent des sources de capabilities et ne deviennent jamais des identités réseau. Un secret optionnel absent n'invalide que la route dépendante. Le canari qui dépendait du Config utilisateur de la machine est remplacé par un moteur Config committé et un environnement injecté. Aucun I/O runtime n'est ajouté. +**Statut : gate opérateur de `pre.006` en échec fonctionnel, puis corrections `pre.006-fix.001` et `pre.006-fix.002`.** Le premier inventaire classait encore les capabilities par profil Transport fournisseur : `Yellowstone + HTTP hydration` Mainnet n'était donc `Configured` que sous `publicnode_mainnet`. Le fix remplace cette projection par trois profils réseau (`devnet`, `mainnet`, `testnet`) qui agrègent les sources Transport same-network. PublicNode, Helius et OrbitFlare restent des sources de capabilities et ne deviennent jamais des identités réseau. Un secret optionnel absent n'invalide que la route dépendante. Le canari qui dépendait du Config utilisateur de la machine est remplacé par un moteur Config committé et un environnement injecté. Aucun I/O runtime n'est ajouté. #### `pre.006-fix.001` — correction classification réseau Attendus : `mainnet` expose Yellowstone dès que la source PublicNode Mainnet est effectivement résolue, quelle que soit la source HTTP/WS standard retenue dans le même profil réseau ; `mainnet` reste utilisable pour Standard Logs et HTTP Block Polling lorsque les secrets Helius/PublicNode sont absents. Le même principe s'applique à Devnet et Testnet. +#### `pre.006-fix.002` — Standard Block réellement composable + +Le gate de `pre.006-fix.001` révèle deux points : les canaris Desk ne compilent pas car ils appellent `ConfigEnvironment::from_maps`, helper `pub(crate)`/`cfg(test)` de `ksp-config-lib`, et `standard-block-direct` reste systématiquement indisponible parce qu'aucun endpoint standard committed ne déclare `Block`. + +Le fix déclare explicitement `Block` sur les endpoints `solana-public` Standard WS committés de Devnet, Mainnet et Testnet, sans toucher Helius LaserStream. L'inventaire réseau-centrique projette donc `standard-block-direct` en `Configured` sur les trois réseaux. Cette classification reste une preuve Config de composabilité, pas une preuve live permanente : `blockSubscribe` reste unstable et validator/provider-gated, donc `pre.007`/Start devra reconstruire et revalider la ressource exacte avant exécution. + +Les canaris Desk n'utilisent plus l'API crate-private de Config. Ils valident les routes standard à partir de `ConfigEnvironment::load()` et acceptent que les routes optionnelles Helius/Yellowstone soient soit `Configured`, soit `missing_required_secret` selon l'environnement opérateur. + ### `pre.007` — ressources Start Reconstruction/revalidation Start des ressources Transport pour les cinq routes, sans launch Worker.