From af807afff56337b642615d766cf7d884edf05513 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Tue, 25 Aug 2026 10:29:16 +0200 Subject: [PATCH] v0.2.10-pre.002 --- Cargo.toml | 4 +- config/std.transport.json | 53 ++++ crates/ksp-config-lib/unit_tests/transport.rs | 41 ++- crates/ksp-onchain-transport-lib/README.md | 3 +- .../tests/yellowstone_orbitflare_smoke.rs | 126 ++++++++ deltas/0.2.10/pre.002.md | 287 ++++++++++++++++++ ...0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md | 54 ++-- docs/rules/RULES_DOCUMENTATION.md | 8 +- ...013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md | 95 ++++-- scripts/audit_markdown_tables.py | 55 +++- 10 files changed, 667 insertions(+), 59 deletions(-) create mode 100644 crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs create mode 100644 deltas/0.2.10/pre.002.md diff --git a/Cargo.toml b/Cargo.toml index 0f45ffc..131ffa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 262 +# version: 263 [workspace] resolver = "3" members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"] [workspace.package] -version = "0.2.10-pre.1" +version = "0.2.10-pre.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 ef2b678..4ca177b 100644 --- a/config/std.transport.json +++ b/config/std.transport.json @@ -79,6 +79,59 @@ } ] }, + { + "profile_id": "orbitflare_devnet", + "endpoints": [ + { + "name": "solana_devnet_public", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "url": "${KSP_PUBLIC_SOLANA_DEVNET_HTTP_URL:-https://api.devnet.solana.com}", + "connect_timeout_ms": 5000, + "request_timeout_ms": 15000, + "max_idle_connections_per_host": 8, + "roles": [ + { + "role": "default", + "enabled": true, + "request_kinds": [ + "*" + ], + "priority": 100, + "limits": { + "requests_per_second": 5, + "burst_capacity": 10, + "max_concurrent_requests": 8, + "pause_after_rate_limit_ms": 1000 + } + } + ] + } + ], + "ws_endpoints": [ + { + "name": "solana_devnet_public_ws", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "kind": "solana_standard", + "url": "${KSP_PUBLIC_SOLANA_DEVNET_WS_URL:-wss://api.devnet.solana.com}" + } + ], + "grpc_endpoints": [ + { + "name": "orbitflare_solana_devnet_yellowstone", + "enabled": true, + "provider": "orbitflare", + "cluster": "devnet", + "protocol": "solana_yellowstone", + "url": "http://devnet.rpc.orbitflare.com:10000", + "metadata": [], + "secret_metadata": [] + } + ] + }, { "profile_id": "mainnet_public", "endpoints": [ diff --git a/crates/ksp-config-lib/unit_tests/transport.rs b/crates/ksp-config-lib/unit_tests/transport.rs index 7d8dfae..c5c89ab 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: 10 +// version: 11 #[test] fn fixture_transport_profile_maps_complete_runtime_contract() { @@ -133,6 +133,45 @@ fn committed_transport_document_maps_default_and_explicit_profiles() { } } +#[test] +fn committed_v3_orbitflare_devnet_profile_maps_provider_neutral_yellowstone_grpc_without_metadata() { + 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("orbitflare_devnet"), &environment); + assert!(resolved.is_ok(), "committed OrbitFlare Devnet Transport profile should map without gRPC metadata: {resolved:?}"); + if let std::result::Result::Ok(resolved) = resolved { + assert_eq!(resolved.profile_id(), "orbitflare_devnet"); + assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Explicit); + assert_eq!(resolved.settings().endpoints()[0].cluster().as_str(), "devnet"); + assert_eq!(resolved.settings().endpoints()[0].url().as_str(), "https://api.devnet.solana.com"); + let ws = resolved.ws_settings(); + assert!(ws.is_some(), "OrbitFlare Devnet profile should retain the standard Solana Devnet WebSocket companion endpoint"); + if let std::option::Option::Some(ws) = ws { + 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); + } + let grpc = resolved.grpc_settings(); + assert!(grpc.is_some(), "OrbitFlare Devnet profile should expose one provider-neutral Yellowstone gRPC endpoint"); + if let std::option::Option::Some(grpc) = grpc { + assert_eq!(grpc.endpoints().len(), 1); + let endpoint = &grpc.endpoints()[0]; + assert_eq!(endpoint.name(), "orbitflare_solana_devnet_yellowstone"); + assert_eq!(endpoint.provider().as_str(), "orbitflare"); + assert_eq!(endpoint.cluster().as_str(), "devnet"); + assert_eq!(endpoint.url().as_str(), "http://devnet.rpc.orbitflare.com:10000"); + assert!(!endpoint.url().uses_tls(), "OrbitFlare shared Devnet URL must remain explicitly HTTP/2 plaintext"); + assert!(endpoint.metadata().is_empty(), "OrbitFlare Devnet Free profile must not invent gRPC metadata before live proof requires it"); + let debug = format!("{endpoint:?}"); + assert!(!debug.contains("orbitflare.com"), "Transport settings Debug must not expose OrbitFlare gRPC endpoint URLs"); + assert!(!debug.contains("X-ORBIT-KEY"), "OrbitFlare Customer API credentials must never enter Yellowstone Transport settings"); + } + } +} + #[test] fn committed_v3_publicnode_profiles_map_provider_neutral_yellowstone_grpc_with_network_scoped_secret_x_tokens() { let engine = committed_engine(); diff --git a/crates/ksp-onchain-transport-lib/README.md b/crates/ksp-onchain-transport-lib/README.md index ced8cf7..608dee5 100644 --- a/crates/ksp-onchain-transport-lib/README.md +++ b/crates/ksp-onchain-transport-lib/README.md @@ -1,5 +1,5 @@ - + # `ksp-onchain-transport-lib` @@ -220,7 +220,6 @@ Pour Helius, l’actor envoie automatiquement un control frame WebSocket `Ping` LaserStream **gRPC** reste un backend distinct, hors de cette façade, de `WsProtocolKind` et de la Config WebSocket `helius_laserstream`. - ## Yellowstone gRPC standard La foundation `0.2.9` ajoute un troisième backend réseau distinct de HTTP et WebSocket. Le moteur est KSP-owned : `yellowstone-grpc-proto` fournit le wire publié, tandis que Tonic reste encapsulé derrière les types crate-root KSP. Aucun client Tonic brut ni type protobuf upstream n’est réexporté. diff --git a/crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs b/crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs new file mode 100644 index 0000000..ac9102f --- /dev/null +++ b/crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs @@ -0,0 +1,126 @@ +// file: crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs +// version: 1 + +//! Opt-in live OrbitFlare Devnet characterization smoke for provider-neutral Yellowstone gRPC Subscribe. + +fn orbitflare_devnet_endpoint() -> ksp_core_lib::Result { + let url = match ksp_onchain_transport_lib::YellowstoneGrpcEndpointUrl::parse("http://devnet.rpc.orbitflare.com:10000") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let session = ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings::new( + std::time::Duration::from_secs(10), + std::time::Duration::from_secs(10), + std::time::Duration::from_secs(5), + ksp_onchain_transport_lib::YellowstoneGrpcReconnectSettings::new(0, std::time::Duration::from_millis(250), std::time::Duration::from_secs(2)), + 8, + 8, + 16 * 1024 * 1024, + 4 * 1024 * 1024, + ); + return std::result::Result::Ok(ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings::new( + "orbitflare_devnet_yellowstone", + true, + ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new("orbitflare"), + ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new("devnet"), + url, + session, + )); +} + +fn slot_request() -> ksp_core_lib::Result { + let mut request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new(); + let name = match ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("slots") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match request.insert_slot_filter(name, ksp_onchain_transport_lib::YellowstoneSubscribeSlotFilter::new()) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + request.set_commitment(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)); + return std::result::Result::Ok(request); +} + +#[tokio::test(flavor = "current_thread")] +#[ignore = "opt-in live OrbitFlare Devnet Yellowstone gRPC characterization; connects without metadata, waits for Slot plus standard server Ping, then closes"] +async fn orbitflare_devnet_yellowstone_streams_slots_and_emits_standard_server_ping_without_metadata() { + let endpoint = match orbitflare_devnet_endpoint() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("programmatic OrbitFlare Devnet Yellowstone settings must be valid without metadata: {error:?}"), + }; + assert!(endpoint.metadata().is_empty(), "OrbitFlare Devnet characterization must not invent gRPC metadata"); + assert!(!endpoint.url().uses_tls(), "the documented OrbitFlare shared Devnet endpoint is intentionally HTTP/2 plaintext"); + let endpoint_debug = format!("{endpoint:?}"); + assert!(!endpoint_debug.contains("orbitflare.com"), "OrbitFlare endpoint URL must not appear in endpoint Debug"); + assert!(!endpoint_debug.contains("X-ORBIT-KEY"), "OrbitFlare Customer API credentials must not enter Yellowstone settings"); + let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::connect(&endpoint).await { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("OrbitFlare Devnet Yellowstone connection without metadata must succeed or classify the provider auth gate: {error:?}") + }, + }; + assert_eq!(channel.endpoint_name(), "orbitflare_devnet_yellowstone"); + assert_eq!(channel.provider().as_str(), "orbitflare"); + assert_eq!(channel.cluster().as_str(), "devnet"); + let request = match slot_request() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("OrbitFlare Yellowstone slot request must be valid: {error:?}"), + }; + let mut session = match channel.open_standard_subscribe(request).await { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("OrbitFlare Devnet standard Subscribe without metadata must open or classify the provider auth gate: {error:?}") + }, + }; + let deadline = tokio::time::sleep(std::time::Duration::from_secs(45)); + tokio::pin!(deadline); + let mut slot_seen = false; + let mut ping_seen = false; + loop { + tokio::select! { + () = &mut deadline => break, + next_update = session.next_update() => { + let next_update = match next_update { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("OrbitFlare Devnet Yellowstone Subscribe update must decode: {error:?}"), + }; + match next_update { + std::option::Option::Some(ksp_onchain_transport_lib::YellowstoneSubscribeUpdate::Slot(update)) => { + if update.slot() > 0 { + slot_seen = true; + } + }, + std::option::Option::Some(ksp_onchain_transport_lib::YellowstoneSubscribeUpdate::Ping(_)) => ping_seen = true, + std::option::Option::Some(_) => {}, + std::option::Option::None => break, + } + if slot_seen && ping_seen { + break; + } + }, + } + } + let close_timeout_result = tokio::time::timeout(std::time::Duration::from_secs(7), session.close()).await; + let close_result = match close_timeout_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("KSP Yellowstone Subscribe close must remain bounded beyond the configured five-second provider half-close deadline: {error}") + }, + }; + match close_result { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + assert_eq!( + error.code(), + ksp_onchain_transport_lib::ERROR_CODE_TIMEOUT, + "after live characterization, OrbitFlare close may time out waiting for provider half-close but must not fail for another reason: {error:?}" + ); + }, + } + assert!(slot_seen, "OrbitFlare Devnet Yellowstone must publish at least one non-zero confirmed slot during the 45-second characterization window"); + assert!( + ping_seen, + "OrbitFlare Devnet must emit the standard Yellowstone server Ping during the 45-second characterization window before any provider-specific heartbeat is justified" + ); +} diff --git a/deltas/0.2.10/pre.002.md b/deltas/0.2.10/pre.002.md new file mode 100644 index 0000000..c5993db --- /dev/null +++ b/deltas/0.2.10/pre.002.md @@ -0,0 +1,287 @@ + + + +# Delta `0.2.10-pre.002` — OrbitFlare Devnet Config + characterization smoke + +## 1. Base et version + +Base de travail : + +```text +0.2.10-pre.001 +``` + +Validation opérateur fournie sur cette base le 2026-08-25 : + +```text +cargo fmt --all PASS +python3 scripts/audit_rust_workspace_rules.py PASS +python3 scripts/audit_markdown_tables.py PASS / 97 tables / 233 files +cargo check --workspace PASS +cargo clippy --workspace --all-targets PASS +cargo test --workspace PASS +cargo tree -p ksp-onchain-transport-lib fourni +cargo tree -p ksp-onchain-transport-lib --duplicates fourni +``` + +Le graphe confirme notamment : + +```text +yellowstone-grpc-proto 12.6.0 +tonic 0.14.6 +tonic-prost 0.14.6 +prost 0.14.4 +tokio 1.53.1 +``` + +Cette livraison passe à : + +```text +workspace.package.version = 0.2.10-pre.2 +commit attendu = v0.2.10-pre.002 +aucun tag prerelease +``` + +## 2. Invariant architectural + +Aucun moteur physique n'est modifié : + +```text +src/grpc_settings.rs inchangé +src/grpc_channel.rs inchangé +src/grpc_unary.rs inchangé +src/grpc_subscribe.rs inchangé +src/grpc_stream.rs inchangé +``` + +Le standard Yellowstone N2 reste également inchangé. + +OrbitFlare est matérialisé uniquement par : + +```text +Config provider profile +provider-neutral Yellowstone smoke +provider validation/documentation +``` + +Aucun SDK OrbitFlare, second client Tonic, proto provider, heartbeat moteur ou metadata secrète n'est ajouté. + +## 3. Profil Config V3 `orbitflare_devnet` + +`config/std.transport.json` ajoute un profil dédié : + +```text +profile_id = orbitflare_devnet +``` + +Il conserve les companions standards déjà utilisés sur Devnet : + +```text +HTTP = https://api.devnet.solana.com +WS = wss://api.devnet.solana.com +``` + +et ajoute exactement un endpoint Yellowstone : + +```text +name = orbitflare_solana_devnet_yellowstone +provider = orbitflare +cluster = devnet +protocol = solana_yellowstone +url = http://devnet.rpc.orbitflare.com:10000 +metadata = [] +secret_metadata = [] +``` + +Le format reste V3. `.env.example` n'est pas modifié et aucune API key de compte OrbitFlare n'est reclassifiée comme credential gRPC. + +`ksp-config-lib` ajoute un test déterministe qui prouve : + +```text +sélection explicite orbitflare_devnet +companions HTTP/WS Devnet préservés +provider/cluster/protocol gRPC corrects +URL plaintext conservée telle quelle +aucune metadata inventée +Debug gRPC sans URL provider +absence de X-ORBIT-KEY dans Transport settings +``` + +## 4. Smoke OrbitFlare Devnet + +Nouveau test opt-in : + +```text +crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs +``` + +Le test utilise directement les API publiques Yellowstone existantes de Transport, sans dépendre de Config : + +```text +endpoint Devnet OrbitFlare sans metadata +reconnect max_retries = 0 +Subscribe slots +commitment = confirmed +fenêtre de caractérisation = 45 s +preuve Slot non nul +preuve SubscribeUpdate::Ping serveur +close borné +``` + +Le `Ping` reçu reste traité par le moteur N1 déjà livré en `0.2.9`, qui répond automatiquement sur le même stream. Le test n'ajoute aucune policy heartbeat. + +Commande live : + +```bash +cargo test -p ksp-onchain-transport-lib --test yellowstone_orbitflare_smoke -- --ignored --nocapture +``` + +Interprétation : + +```text +Slot + Ping PASS + OrbitFlare reste standard ; aucune façade/policy provider heartbeat requise + +connection/auth failure + classifier le service/licence réel avant toute metadata + +Slot PASS mais Ping absent + qualifier une divergence heartbeat provider avant toute implémentation spécifique +``` + +## 5. Audit Markdown étendu + +`scripts/audit_markdown_tables.py` passe en version 2 et contrôle désormais aussi l'équivalent structurel de : + +```text +\n\s*\n\s*\n +``` + +c'est-à-dire au moins deux lignes vides consécutives hors fenced code block. + +Le contrôle : + +```text +considère une ligne whitespace-only comme vide +signale fichier + première ligne + longueur de la séquence +n'interprète pas les blancs internes aux ``` / ~~~ fenced blocks +ignore les répertoires générés ou tiers en parcours récursif +``` + +Répertoires récursifs exclus : + +```text +.git +.idea +.venv +__pycache__ +dist +node_modules +target +``` + +Cette exclusion rend le résultat déterministe entre une archive Gitea propre et un checkout développeur contenant des `node_modules`. Le nombre de fichiers audités peut donc diminuer par rapport à l'ancien script sans signifier une perte de couverture KSP-owned. + +Une seule séquence de deux lignes vides existait dans le scope KSP actif de l'archive : elle est supprimée dans `crates/ksp-onchain-transport-lib/README.md`. + +`docs/rules/RULES_DOCUMENTATION.md` formalise `DOC-BLANK-001` à `DOC-BLANK-003`, y compris l'immuabilité des anciens deltas publiés. + +## 6. Documents de plan/validation + +Le plan 017 et la validation 013 passent en version 2 pour enregistrer : + +```text +pre.001 operator gate fermé +profil orbitflare_devnet matérialisé +smoke de caractérisation matérialisé +N1/N2 inchangés +live Devnet encore PENDING +``` + +Aucun README/USAGE OrbitFlare durable n'est finalisé ici ; cette responsabilité reste réservée au couloir documentaire final. + +## 7. Validation disponible dans l'environnement de génération + +Exécuté sans Cargo : + +```text +python3 scripts/audit_rust_workspace_rules.py + General Rust rule audit: clean + Rust export completeness audit: 0 candidate(s) + KSP workspace Rust rule audit: clean + +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.10 + clean / 98 tables / 98 KSP-owned files + +config/std.transport.json + JSON parse PASS + JSON Schema validation PASS +``` + +Le nouvel audit Markdown a aussi été caractérisé avec des fixtures temporaires : + +```text +deux lignes vides hors fence FAIL attendu +plusieurs lignes vides dans fence PASS attendu +node_modules récursif ignoré attendu +``` + +Aucune commande Cargo n'est déclarée exécutée dans l'environnement de génération. + +## 8. Gates opérateur `pre.002` + +Exécuter : + +```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.2.10 +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-config-lib +cargo test -p ksp-onchain-transport-lib +cargo test -p ksp-core-lib --test workspace_dependencies +cargo test --workspace +cargo tree -p ksp-onchain-transport-lib +cargo tree -p ksp-onchain-transport-lib --duplicates +``` + +Puis le live dédié : + +```bash +cargo test -p ksp-onchain-transport-lib --test yellowstone_orbitflare_smoke -- --ignored --nocapture +``` + +`pre.002` ne ferme son gate provider que lorsque le résultat live est classifié. Un échec d'auth ou l'absence de `Ping` ne doit jamais être corrigé par modification du moteur N1. + +## 9. Fichiers ajoutés/modifiés + +```text +Cargo.toml +config/std.transport.json +crates/ksp-config-lib/unit_tests/transport.rs +crates/ksp-onchain-transport-lib/README.md +crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs +docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md +docs/rules/RULES_DOCUMENTATION.md +docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md +scripts/audit_markdown_tables.py +deltas/0.2.10/pre.002.md +``` + +## 10. Suite conditionnelle + +Si le live retourne `Slot + Ping PASS` : + +```text +pre.003 = gate technique/live final +``` + +Si une divergence provider réelle est démontrée : + +```text +pre.003 = tranche provider-specific minimale au-dessus de N1/N2 +pre.004 = gate technique/live final +``` + +Le couloir documentaire final puis la prerelease de publication restent distincts dans les deux cas. diff --git a/docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md b/docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md index 1e137b0..22615c5 100644 --- a/docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md +++ b/docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md @@ -1,9 +1,9 @@ - + # Plan `0.2.10` — OrbitFlare Yellowstone gRPC -**Statut courant : `0.2.10-pre.001` ouvre la release par un audit provider actuel. Le gate d'architecture est positif avec une contrainte renforcée : le moteur gRPC N1 et le standard Yellowstone N2 livrés par `0.2.9` sont immuables pour cette release. OrbitFlare doit être composé au-dessus de ces couches ; aucune modification du moteur n'est autorisée pour satisfaire une policy provider.** +**Statut courant : `0.2.10-pre.002` matérialise le profil Config V3 `orbitflare_devnet` et un smoke opt-in de caractérisation `Subscribe -> Slot + Ping` construit exclusivement sur le standard Yellowstone existant. Le moteur gRPC N1 et le standard N2 restent inchangés ; le verdict live Devnet est un gate opérateur de cette tranche.** ## 1. Base et autorité @@ -158,21 +158,21 @@ Une façade OrbitFlare publique n'est créée que si un consumer KSP doit réell Preuves fournies le 2026-08-25 : -| Gate | Résultat | -|-------------------------------------------|----------------------------------| -| `cargo fmt --all` | PASS | -| audit Rust workspace | PASS, 0 candidate export | -| audit Markdown tables | PASS, 87 tables et 261 files | -| `cargo check --workspace` | PASS | -| `cargo clippy --workspace --all-targets` | PASS | -| `cargo test --workspace` | PASS | -| Transport unit | 383 sur 383 PASS | -| Transport public API | 49 sur 49 PASS | -| Transport release completeness | 43 sur 43 PASS | -| Config unit | 113 sur 113 PASS | -| PublicNode Yellowstone live | ignored opt-in comme attendu | -| `cargo tree --duplicates` Transport | fourni et inspecté | -| `cargo tree -p ksp-onchain-transport-lib` | à rejouer avant commit `pre.001` | +| Gate | Résultat | +|-------------------------------------------|------------------------------------------| +| `cargo fmt --all` | PASS | +| audit Rust workspace | PASS, 0 candidate export | +| audit Markdown tables | PASS, 97 tables et 233 files sur `pre.1` | +| `cargo check --workspace` | PASS | +| `cargo clippy --workspace --all-targets` | PASS | +| `cargo test --workspace` | PASS | +| Transport unit | 383 sur 383 PASS | +| Transport public API | 49 sur 49 PASS | +| Transport release completeness | 43 sur 43 PASS | +| Config unit | 113 sur 113 PASS | +| PublicNode Yellowstone live | ignored opt-in comme attendu | +| `cargo tree --duplicates` Transport | fourni et inspecté | +| `cargo tree -p ksp-onchain-transport-lib` | fourni sur `0.2.10-pre.1` | Versions visibles dans le graphe fourni : @@ -419,12 +419,24 @@ pas de heartbeat dans grpc_defaults pas de secret tant que le Devnet Free n'en exige pas ``` -Profil prévu : +Profil matérialisé en `pre.002` : ```text orbitflare_devnet ``` +Le profil conserve les companions HTTP/WS Solana Devnet standards et ajoute exactement un endpoint gRPC : + +```text +name = orbitflare_solana_devnet_yellowstone +provider = orbitflare +cluster = devnet +protocol = solana_yellowstone +url = http://devnet.rpc.orbitflare.com:10000 +metadata = [] +secret = [] +``` + Si le service opérateur réel impose ensuite un token gRPC : ```text @@ -437,7 +449,7 @@ Cette variable n'est ajoutée à `.env.example` qu'après preuve qu'un secret da ### 12.1 Premier canari -Le premier canari doit utiliser **le standard N2 existant directement** : +`pre.002` ajoute `tests/yellowstone_orbitflare_smoke.rs`. Ce canari opt-in utilise **le standard N2 existant directement** et ne dépend pas de Config, conformément à la frontière Transport -> Config interdite : ```text endpoint = http://devnet.rpc.orbitflare.com:10000 @@ -533,8 +545,8 @@ pre.001 audit actuel + architecture immuable N1/N2 + auth/endpoints + free Devn preuve : plan 017 + validation 013 + delta + baseline pre.002 profil Config V3 OrbitFlare Devnet + smoke de caractérisation standard N2 - preuve : Subscribe -> Slot + observation Ping serveur si disponible + probes provider ciblés - aucune modification moteur gRPC + statique : profil sans metadata + canari Subscribe slots confirmed + fenêtre Ping 45 s + close borné + live : Subscribe -> Slot + observation Ping serveur ; aucune modification moteur gRPC pre.003 soit gate technique final si OrbitFlare reste 100 pourcent standard, soit tranche provider-specific minimale uniquement si pre.002 démontre une divergence réelle diff --git a/docs/rules/RULES_DOCUMENTATION.md b/docs/rules/RULES_DOCUMENTATION.md index 24c9dff..abad4df 100644 --- a/docs/rules/RULES_DOCUMENTATION.md +++ b/docs/rules/RULES_DOCUMENTATION.md @@ -1,5 +1,5 @@ - + # Règles de documentation @@ -41,6 +41,12 @@ Les règles `DOC-*` s'appliquent aux documents Markdown internes et à leur orga - **DOC-TABLE-005** — Lorsqu’une modification touche une ligne d’un tableau, le tableau entier est réaligné avant livraison. `python3 scripts/audit_markdown_tables.py ` est le canari mécanique recommandé pour les fichiers concernés ; les blocs de code fenced ne sont pas interprétés comme des tableaux. - **DOC-TABLE-006** — L’immuabilité des deltas déjà publiés prime sur un reformatage rétrospectif : un ancien fichier `deltas/` n’est jamais réécrit uniquement pour satisfaire une règle de présentation introduite ultérieurement. Tout nouveau delta et tout autre tableau modifiable créé ou touché doivent en revanche respecter immédiatement `DOC-TABLE-001` à `DOC-TABLE-005`. +## Espacement vertical Markdown + +- **DOC-BLANK-001** — Hors bloc de code fenced, un document Markdown KSP ne contient jamais plus d’une ligne vide consécutive. Une ligne composée uniquement d’espaces ou de tabulations est considérée comme vide. +- **DOC-BLANK-002** — `python3 scripts/audit_markdown_tables.py ` contrôle aussi les lignes vides multiples. Lors d’un parcours récursif, les répertoires générés ou tiers tels que `node_modules`, `dist`, `target`, `.git`, `.idea`, `.venv` et `__pycache__` sont exclus afin que le résultat porte uniquement sur les Markdown KSP-owned. Les blocs de code fenced conservent librement leur espacement interne. +- **DOC-BLANK-003** — Les deltas déjà publiés restent immuables et ne sont jamais réécrits uniquement pour satisfaire `DOC-BLANK-001`. Les audits de release portent sur le répertoire de delta actif et sur les autres Markdown modifiables ; un audit historique explicite peut donc signaler des écarts hérités sans autoriser leur correction rétroactive. + ## Contenu et exactitude - **DOC-CONTENT-001** — Une documentation décrit l'état réellement décidé ou validé et distingue explicitement les hypothèses, propositions, TODO et questions ouvertes. diff --git a/docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md b/docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md index fa6c417..98271cd 100644 --- a/docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md +++ b/docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md @@ -1,9 +1,9 @@ - + # Validation `0.2.10` — OrbitFlare Yellowstone gRPC -**Statut courant : matrice ouverte par `0.2.10-pre.001`. Le gate d'architecture est positif. Le premier objectif live est le Devnet gratuit OrbitFlare via le standard Yellowstone KSP inchangé. Les verdicts provider non encore testés restent explicitement `PENDING` ou `UNKNOWN`.** +**Statut courant : `0.2.10-pre.002` matérialise le profil Config V3 OrbitFlare Devnet et le smoke opt-in de caractérisation sur le standard Yellowstone KSP inchangé. Les preuves statiques sont fermées ; `connect`, `Subscribe`, `Slot` et `Ping` restent `PENDING` jusqu'au run live opérateur.** ## 1. Autorités @@ -36,7 +36,7 @@ Yellowstone upstream actuel | Transport release completeness | 43 sur 43 PASS | | Config unit | 113 sur 113 PASS | | graph duplicates Transport | fourni, aucun conflit N1 visible | -| graph complet Transport | PENDING avant commit | +| graph complet Transport | fourni sur `0.2.10-pre.1` PASS | ## 3. Invariant architecture @@ -99,7 +99,7 @@ KSP ne prétend pas qu'un canal gRPC plaintext bénéficie d'une sécurité TLS | aucune metadata gRPC | SDK Go OrbitFlare | **DOCUMENTED** | | auth Devnet Free réelle | smoke KSP à exécuter | **PENDING** | -Aucun secret opérateur n'est demandé pour `pre.001`. +Aucun secret opérateur n’est demandé pour le premier smoke `pre.002` ; la tentative initiale est explicitement sans metadata. ## 7. Heartbeat @@ -163,17 +163,17 @@ Ces valeurs restent provider-owned et ne deviennent pas des validations généri ## 11. Config V3 -| Exigence | Verdict | -|-------------------------------------|-------------| -| nouveau format Config nécessaire | **NO** | -| `provider = orbitflare` | **PLANNED** | -| `protocol = solana_yellowstone` | **PASS** | -| `cluster = devnet` | **PLANNED** | -| Devnet URL publique | **PLANNED** | -| secret metadata par défaut | **NO** | -| `X-ORBIT-KEY` dans Transport Config | **FORBID** | -| `x-token` si token gRPC prouvé | **DEFER** | -| heartbeat dans `grpc_defaults` | **NO** | +| Exigence | Verdict | +|-------------------------------------|-----------------| +| nouveau format Config nécessaire | **NO** | +| `provider = orbitflare` | **PASS STATIC** | +| `protocol = solana_yellowstone` | **PASS STATIC** | +| `cluster = devnet` | **PASS STATIC** | +| Devnet URL publique | **PASS STATIC** | +| secret metadata par défaut | **NO / PASS** | +| `X-ORBIT-KEY` dans Transport Config | **FORBID** | +| `x-token` si token gRPC prouvé | **DEFER** | +| heartbeat dans `grpc_defaults` | **NO** | ## 12. Smoke Devnet attendu @@ -194,15 +194,15 @@ close borné Verdicts actuels : -| Preuve | Verdict | -|-----------------------------|-------------| -| endpoint parsé par N1 | EXPECTED | -| connect Devnet | **PENDING** | -| Subscribe accepté | **PENDING** | -| Slot reçu | **PENDING** | -| server Ping reçu | **PENDING** | -| auto reply KSP déterministe | **PASS** | -| close borné | **PASS N1** | +| Preuve | Verdict | +|------------------------------|----------------------------| +| endpoint parsé par Config/N1 | **PASS STATIC** | +| connect Devnet | **PENDING** | +| Subscribe accepté | **PENDING** | +| Slot reçu | **PENDING** | +| server Ping reçu | **PENDING** | +| auto reply KSP déterministe | **PASS N1** | +| close borné | **PASS N1 / LIVE PENDING** | ## 13. Provider overlay gate @@ -246,7 +246,7 @@ ownership/close provider bornés | baseline fmt/audits/check/clippy | **PASS** | | workspace test | **PASS** | | graph duplicates | **PASS** | -| graph complet Transport | **PENDING OPERATOR** | +| graph complet Transport | **PASS** | | sources OrbitFlare actuelles | **PASS** | | sources Yellowstone actuelles | **PASS** | | Free Devnet gRPC confirmé | **PASS** | @@ -267,6 +267,47 @@ Verdict : ```text architecture/sizing gate = POSITIVE -operator commit gate = PENDING full cargo tree + validations overlay -live provider gate = reserved for pre.002+ +operator pre.001 gate = PASS +pre.002 static gate = READY FOR OPERATOR VALIDATION +live provider gate = PENDING pre.002 characterization +``` + +## 16. Gate `pre.002` candidat + +| Critère | Verdict | +|-------------------------------------------|----------------------| +| Cargo `0.2.10-pre.2` | **PASS STATIC** | +| profil Config `orbitflare_devnet` | **PASS STATIC** | +| endpoint gRPC sans metadata | **PASS STATIC** | +| companions HTTP/WS Devnet préservés | **PASS STATIC** | +| nouveau format Config | **NO** | +| `.env.example` / secret OrbitFlare ajouté | **NO** | +| moteur N1 modifié | **NO** | +| standard N2 modifié | **NO** | +| SDK OrbitFlare ajouté | **NO** | +| smoke provider dans Transport | **PASS STATIC** | +| reconnect du smoke | zéro retry | +| fenêtre caractérisation | 45 secondes | +| `Subscribe -> Slot` live | **PENDING OPERATOR** | +| `SubscribeUpdate::Ping` live | **PENDING OPERATOR** | +| close live | **PENDING OPERATOR** | +| auth Devnet Free sans metadata | **PENDING OPERATOR** | + +Commande live dédiée : + +```bash +cargo test -p ksp-onchain-transport-lib --test yellowstone_orbitflare_smoke -- --ignored --nocapture +``` + +Le résultat live décide la suite : + +```text +Slot + Ping PASS + -> OrbitFlare reste standard ; aucun provider heartbeat/facade + +connection/auth failure + -> classifier le service/licence réel avant toute metadata + +Slot PASS mais Ping absent + -> divergence heartbeat à prouver avant une couche provider-specific ``` diff --git a/scripts/audit_markdown_tables.py b/scripts/audit_markdown_tables.py index 27a08a1..5ea487b 100755 --- a/scripts/audit_markdown_tables.py +++ b/scripts/audit_markdown_tables.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # file: scripts/audit_markdown_tables.py -# version: 1 +# version: 2 -"""Validate KSP Markdown table formatting for explicitly supplied files or directories.""" +"""Validate KSP Markdown tables and vertical spacing for explicitly supplied files or directories.""" from __future__ import annotations @@ -12,6 +12,7 @@ import re import sys _SEPARATOR_CELL = re.compile(r"^:?-{3,}:?$") +_IGNORED_DIRECTORY_NAMES = frozenset({".git", ".idea", ".venv", "__pycache__", "dist", "node_modules", "target"}) def _markdown_files(paths: list[str]) -> list[pathlib.Path]: @@ -19,7 +20,13 @@ def _markdown_files(paths: list[str]) -> list[pathlib.Path]: for raw_path in paths: path = pathlib.Path(raw_path) if path.is_dir(): - files.extend(sorted(candidate for candidate in path.rglob("*.md") if candidate.is_file())) + files.extend( + sorted( + candidate + for candidate in path.rglob("*.md") + if candidate.is_file() and not any(part in _IGNORED_DIRECTORY_NAMES for part in candidate.parts) + ) + ) elif path.is_file() and path.suffix.lower() == ".md": files.append(path) else: @@ -101,9 +108,47 @@ def _validate_table(path: pathlib.Path, start_line: int, rows: list[str]) -> lis return errors +def _validate_blank_lines(path: pathlib.Path, lines: list[str]) -> list[str]: + errors: list[str] = [] + fence_marker: str | None = None + blank_run_start: int | None = None + blank_run_length = 0 + + def flush_blank_run() -> None: + nonlocal blank_run_start, blank_run_length + if blank_run_start is not None and blank_run_length >= 2: + errors.append( + f"{path}:{blank_run_start}: {blank_run_length} consecutive blank lines are forbidden outside fenced code blocks; keep at most one" + ) + blank_run_start = None + blank_run_length = 0 + + for line_number, line in enumerate(lines, start=1): + stripped = line.lstrip() + if stripped.startswith("```") or stripped.startswith("~~~"): + flush_blank_run() + marker = stripped[:3] + if fence_marker is None: + fence_marker = marker + elif marker == fence_marker: + fence_marker = None + continue + if fence_marker is not None: + continue + if line.strip() == "": + if blank_run_start is None: + blank_run_start = line_number + blank_run_length += 1 + continue + flush_blank_run() + + flush_blank_run() + return errors + + def _audit_file(path: pathlib.Path) -> tuple[int, list[str]]: lines = path.read_text(encoding="utf-8").splitlines() - errors: list[str] = [] + errors = _validate_blank_lines(path, lines) table_count = 0 fence_marker: str | None = None index = 0 @@ -135,7 +180,7 @@ def _audit_file(path: pathlib.Path) -> tuple[int, list[str]]: def main() -> int: - """Audit Markdown tables in the explicitly selected scope.""" + """Audit KSP Markdown formatting in the explicitly selected scope.""" parser = argparse.ArgumentParser() parser.add_argument("paths", nargs="+", help="Markdown files or directories to audit")