From 1967e845b0b49b890df12d74f7ab9620471df836 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Tue, 25 Aug 2026 14:17:40 +0200 Subject: [PATCH] v0.2.10-pre.002-fix.001 --- .env.example | 6 +- Cargo.toml | 4 +- config/std.transport.json | 7 +- crates/ksp-config-lib/unit_tests/transport.rs | 19 +- .../tests/yellowstone_orbitflare_smoke.rs | 56 +++-- deltas/0.2.10/pre.002-fix.001.md | 196 +++++++++++++++++ ...0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md | 145 +++++++------ ...013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md | 205 +++++++++++------- 8 files changed, 464 insertions(+), 174 deletions(-) create mode 100644 deltas/0.2.10/pre.002-fix.001.md diff --git a/.env.example b/.env.example index 46f759b..b7d4b70 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # file: .env.example -# version: 8 +# version: 9 # 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. @@ -42,6 +42,10 @@ KSP_PUBLIC_SOLANA_MAINNET_WS_URL=wss://api.mainnet-beta.solana.com # Keep the real value only in the process environment or local .env; do not assume that a Mainnet token is authorized on Testnet. # KSP_SECRET_PUBLICNODE_TESTNET_GRPC_X_TOKEN=replace-me +# OrbitFlare Solana Free License Key used as secret x-token metadata by the committed Devnet Yellowstone gRPC profile. +# Keep the real ORBIT-* value only in the process environment or local .env; do not substitute the Customer API X-ORBIT-KEY. +# KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN=replace-me + # Helius API key used by the LaserStream WebSocket endpoint in config/examples/std.transport.example.json. # Keep the real credential only in the process environment or local .env; never commit it. # KSP_SECRET_HELIUS_API_KEY=replace-me diff --git a/Cargo.toml b/Cargo.toml index 131ffa2..5f7d50a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 263 +# version: 264 [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.2" +version = "0.2.10-pre.2.fix.1" 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 4ca177b..2a83af6 100644 --- a/config/std.transport.json +++ b/config/std.transport.json @@ -128,7 +128,12 @@ "protocol": "solana_yellowstone", "url": "http://devnet.rpc.orbitflare.com:10000", "metadata": [], - "secret_metadata": [] + "secret_metadata": [ + { + "key": "x-token", + "value": "${KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN}" + } + ] } ] }, diff --git a/crates/ksp-config-lib/unit_tests/transport.rs b/crates/ksp-config-lib/unit_tests/transport.rs index c5c89ab..f15a81f 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: 11 +// version: 12 #[test] fn fixture_transport_profile_maps_complete_runtime_contract() { @@ -134,15 +134,18 @@ fn committed_transport_document_maps_default_and_explicit_profiles() { } #[test] -fn committed_v3_orbitflare_devnet_profile_maps_provider_neutral_yellowstone_grpc_without_metadata() { +fn committed_v3_orbitflare_devnet_profile_maps_provider_neutral_yellowstone_grpc_with_license_x_token() { 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 canary = "ORBITFLARE-DEVNET-LICENSE-X-TOKEN-CANARY"; + let mut process = std::collections::BTreeMap::::new(); + process.insert("KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN".to_owned(), canary.to_owned()); + let environment = crate::ConfigEnvironment::from_maps(process, 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:?}"); + assert!(resolved.is_ok(), "committed OrbitFlare Devnet Transport profile should map from its License Key x-token: {resolved:?}"); if let std::result::Result::Ok(resolved) = resolved { assert_eq!(resolved.profile_id(), "orbitflare_devnet"); assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Explicit); @@ -164,9 +167,13 @@ fn committed_v3_orbitflare_devnet_profile_maps_provider_neutral_yellowstone_grpc 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_eq!(endpoint.metadata().len(), 1); + assert_eq!(endpoint.metadata()[0].key(), "x-token"); + assert!(endpoint.metadata()[0].is_secret()); + assert!(grpc.validate().is_ok(), "Config-produced OrbitFlare Yellowstone gRPC settings should satisfy Transport validation"); + let debug = format!("{grpc:?}"); assert!(!debug.contains("orbitflare.com"), "Transport settings Debug must not expose OrbitFlare gRPC endpoint URLs"); + assert!(!debug.contains(canary), "OrbitFlare License Key x-token must stay redacted from Transport settings Debug"); assert!(!debug.contains("X-ORBIT-KEY"), "OrbitFlare Customer API credentials must never enter Yellowstone Transport settings"); } } diff --git a/crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs b/crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs index ac9102f..c0a14c8 100644 --- a/crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs +++ b/crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs @@ -1,9 +1,26 @@ // file: crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs -// version: 1 +// version: 2 -//! Opt-in live OrbitFlare Devnet characterization smoke for provider-neutral Yellowstone gRPC Subscribe. +//! Opt-in live OrbitFlare Devnet characterization smoke for authenticated provider-neutral Yellowstone gRPC Subscribe. -fn orbitflare_devnet_endpoint() -> ksp_core_lib::Result { +use std::io::IsTerminal; // rust-rules: trait-import + +fn orbitflare_license_x_token() -> std::string::String { + assert!( + !std::io::stdin().is_terminal(), + "pipe the OrbitFlare Solana Free License Key to this ignored smoke on stdin; never pass it as a command-line argument" + ); + let mut token = std::string::String::new(); + match std::io::stdin().read_line(&mut token) { + std::result::Result::Ok(_) => {}, + std::result::Result::Err(error) => panic!("OrbitFlare License Key x-token must be readable from smoke stdin: {error}"), + } + let token = token.trim().to_owned(); + assert!(!token.is_empty(), "OrbitFlare License Key x-token provided on smoke stdin must not be empty"); + return token; +} + +fn orbitflare_devnet_endpoint(x_token: &str) -> 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), @@ -18,14 +35,19 @@ fn orbitflare_devnet_endpoint() -> ksp_core_lib::Result value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return endpoint.with_metadata(vec![metadata]); } fn slot_request() -> ksp_core_lib::Result { @@ -43,22 +65,26 @@ fn slot_request() -> ksp_core_lib::Result value, - std::result::Result::Err(error) => panic!("programmatic OrbitFlare Devnet Yellowstone settings must be valid without metadata: {error:?}"), + std::result::Result::Err(error) => { + panic!("programmatic OrbitFlare Devnet Yellowstone settings must accept the License Key as secret x-token metadata: {error:?}") + }, }; - assert!(endpoint.metadata().is_empty(), "OrbitFlare Devnet characterization must not invent gRPC metadata"); + assert_eq!(endpoint.metadata().len(), 1); + assert_eq!(endpoint.metadata()[0].key(), "x-token"); + assert!(endpoint.metadata()[0].is_secret()); 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_token.as_str()), "OrbitFlare License Key x-token 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:?}") - }, + std::result::Result::Err(error) => panic!("OrbitFlare Devnet Yellowstone authenticated connection must succeed: {error:?}"), }; assert_eq!(channel.endpoint_name(), "orbitflare_devnet_yellowstone"); assert_eq!(channel.provider().as_str(), "orbitflare"); @@ -69,9 +95,7 @@ async fn orbitflare_devnet_yellowstone_streams_slots_and_emits_standard_server_p }; 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:?}") - }, + std::result::Result::Err(error) => panic!("OrbitFlare Devnet standard Subscribe with License Key x-token must open: {error:?}"), }; let deadline = tokio::time::sleep(std::time::Duration::from_secs(45)); tokio::pin!(deadline); diff --git a/deltas/0.2.10/pre.002-fix.001.md b/deltas/0.2.10/pre.002-fix.001.md new file mode 100644 index 0000000..bc9e366 --- /dev/null +++ b/deltas/0.2.10/pre.002-fix.001.md @@ -0,0 +1,196 @@ + + + +# Delta `0.2.10-pre.002-fix.001` — OrbitFlare License Key `x-token` + +## 1. Base et identité + +Base exacte : + +```text +0.2.10-pre.002 commité +``` + +Le fix porte une correction runtime/configuration de l’auth provider. La version Cargo devient donc : + +```text +workspace.package.version = 0.2.10-pre.2.fix.1 +commit attendu = v0.2.10-pre.002-fix.001 +aucun tag prerelease +``` + +## 2. Défaut démontré par le live `pre.002` + +Le gate statique/workspace `pre.002` est vert. Le smoke live sans metadata a atteint OrbitFlare puis l’ouverture du stream a échoué de manière sûre : + +```text +grpc_operation = SubscribeOpen +grpc_status = Unauthenticated +grpc_code = The request does not have valid authentication credentials +``` + +Le Dashboard opérateur montre un produit `Solana Free` avec License Key `ORBIT-*` et `API Key Mode Active`. + +La référence Yellowstone OrbitFlare précise ensuite le contrat data-plane : + +```text +ORBITFLARE_LICENSE_KEY + -> metadata gRPC x-token + -> Yellowstone +``` + +Le `X-ORBIT-KEY` et le Bearer Device Flow restent des credentials Customer API et ne sont pas utilisés par Transport Yellowstone. + +## 3. Correction Config V3 + +`config/std.transport.json` conserve le même endpoint et ajoute uniquement la metadata secrète requise : + +```text +provider = orbitflare +cluster = devnet +protocol = solana_yellowstone +url = http://devnet.rpc.orbitflare.com:10000 +metadata = [] +secret_metadata = x-token <- ${KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN} +``` + +Aucun format Config V4 n’est créé. + +`.env.example` inventorie désormais : + +```text +KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN +``` + +La valeur locale attendue est la License Key `ORBIT-*`. Une `X-ORBIT-KEY` Customer API ne doit jamais être placée dans cette variable. + +## 4. Preuve Config déterministe + +Le test `committed_v3_orbitflare_devnet_profile_maps_provider_neutral_yellowstone_grpc_with_license_x_token` injecte un canari secret via `ConfigEnvironment` et prouve : + +```text +profil explicite orbitflare_devnet +companions HTTP/WS Devnet inchangés +endpoint Yellowstone OrbitFlare inchangé +metadata key = x-token +metadata classée secret +validation Transport PASS +URL provider absente du Debug +canari secret absent du Debug +X-ORBIT-KEY absent du Transport +``` + +## 5. Smoke live corrigé + +`yellowstone_orbitflare_smoke.rs` ne dépend toujours pas de Config et ne lit aucun environnement. Il suit le pattern de sécurité déjà utilisé par les smokes PublicNode : + +```text +une License Key lue sur stdin +aucun secret en argument de ligne de commande +YellowstoneGrpcMetadataEntry::secret("x-token", ...) +reconnect max_retries = 0 +Subscribe slots confirmed +fenêtre Slot + Ping = 45 s +close borné +``` + +Le smoke vérifie également que la License Key n’apparaît pas dans `Debug`. + +Commande opérateur : + +```bash +read -rsp 'OrbitFlare License Key: ' ORBITFLARE_LICENSE_KEY; echo +printf '%s\n' "$ORBITFLARE_LICENSE_KEY" | cargo test -p ksp-onchain-transport-lib --test yellowstone_orbitflare_smoke -- --ignored --nocapture +unset ORBITFLARE_LICENSE_KEY +``` + +## 6. Invariant architectural + +Toujours inchangés : + +```text +src/grpc_settings.rs +src/grpc_channel.rs +src/grpc_unary.rs +src/grpc_subscribe.rs +src/grpc_stream.rs +``` + +Le standard Yellowstone N2 reste inchangé. Aucun SDK OrbitFlare runtime n’est ajouté. + +L’auth provider est entièrement composée par la surface metadata secrète déjà existante. + +## 7. Heartbeat + +Ce fix ne matérialise aucun heartbeat OrbitFlare. + +Le rerun authentifié doit d’abord qualifier : + +```text +Slot non nul +SubscribeUpdate::Ping serveur +``` + +Si les deux sont observés, N1 répond déjà automatiquement au Ping standard et aucune couche provider heartbeat n’est requise. + +Si le stream authentifié fonctionne mais n’émet aucun Ping serveur dans la fenêtre de caractérisation, `pre.003` devient une tranche provider-specific au-dessus de N1/N2. Il reste interdit d’ajouter un timer OrbitFlare au moteur gRPC. + +## 8. Validation disponible dans l’environnement de génération + +Exécuté : + +```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 + Markdown table audit: clean (99 table(s), 99 file(s)) + +config/std.transport.json + JSON parse PASS + JSON Schema validation PASS + +comparaison N1 avec pre.002 + grpc_settings.rs inchangé + grpc_channel.rs inchangé + grpc_unary.rs inchangé + grpc_subscribe.rs inchangé + grpc_stream.rs inchangé +``` + +Aucune commande Cargo n’est déclarée exécutée dans l’environnement de génération. + +## 9. Gates opérateur + +Après application du fix : + +```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 +``` + +Puis exécuter le live authentifié décrit en section 5. + +## 10. Fichiers ajoutés/modifiés + +```text +Cargo.toml +.env.example +config/std.transport.json +crates/ksp-config-lib/unit_tests/transport.rs +crates/ksp-onchain-transport-lib/tests/yellowstone_orbitflare_smoke.rs +docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md +docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md +deltas/0.2.10/pre.002-fix.001.md +``` + +Le delta commité `deltas/0.2.10/pre.002.md` reste immuable. 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 22615c5..f50bd19 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.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.** +**Statut courant : `0.2.10-pre.002-fix.001` corrige l’hypothèse d’auth de `pre.002`. Le live sans metadata a atteint OrbitFlare puis `SubscribeOpen` a été rejeté `Unauthenticated`. Le Dashboard opérateur et la référence Yellowstone OrbitFlare établissent désormais que la License Key `ORBIT-*` du produit Solana Free doit être envoyée comme metadata secrète `x-token`. Config V3 et le smoke sont corrigés sans aucune modification N1/N2 ; le rerun authentifié `Slot + Ping` reste le gate live.** ## 1. Base et autorité @@ -235,30 +235,40 @@ https://docs.orbitflare.com/data-streaming/yellowstone-quickstart L'endpoint Dashboard opérateur reste autoritaire lorsqu'il existe. KSP ne transforme jamais automatiquement une URL `http` en `https`, ne change pas de région et ne construit pas un hostname provider non documenté. -## 7. Auth OrbitFlare : classification actuelle +## 7. Auth OrbitFlare : classification live corrigée -Les sources officielles restent hétérogènes, mais leur rôle peut être séparé sans supposition. +Le live `pre.002` et les sources OrbitFlare permettent désormais de séparer les credentials sans supposition. -| Credential ou mécanisme | Plan observé | Classification `pre.001` | -|-------------------------|-----------------------------------------|-------------------------------------------------------| -| `X-ORBIT-KEY` | Customer API | control-plane, jamais injecté automatiquement en gRPC | -| Bearer Customer API | Customer API v2 | control-plane | -| `api_key` RPC | Solana HTTP RPC | data-plane HTTP, pas Yellowstone | -| API key du compte | login CLI / Customer API | ne prouve pas une auth Yellowstone | -| token gRPC Dashboard | certains endpoints/licences gRPC | possible data-plane Yellowstone | -| `x-token` Yellowstone | mécanisme standard upstream | utiliser seulement si le service OrbitFlare le prouve | -| aucune metadata | SDK Go / endpoints régionaux documentés | premier mode à tester sur Devnet Free | -| IP/network entitlement | certains services partagés/dédiés | provider/account dependent | +| Credential ou mécanisme | Usage observé | Classification après `pre.002` | +|-------------------------|---------------------------|------------------------------------------------------| +| `X-ORBIT-KEY` | Customer API | control-plane, interdit sur Yellowstone | +| Bearer Device Flow | Customer API v2 | control-plane | +| `api_key` RPC | Solana HTTP RPC | data-plane HTTP | +| License Key `ORBIT-*` | produit Solana Free | data-plane provider | +| metadata `x-token` | Yellowstone gRPC | transport prouvé de la License Key | +| aucune metadata | premier smoke KSP | rejetée `Unauthenticated` au `SubscribeOpen` | +| IP whitelist | autre mode d’auth produit | non retenu pour le service opérateur en API Key Mode | -Le quickstart TypeScript OrbitFlare demande un endpoint + token de licence et passe ce token au client Yellowstone. Le client upstream Yellowstone utilise classiquement la metadata `x-token`. Cela rend `x-token` plausible pour cette classe de service, mais **ne transforme pas la clé Customer API du compte en token gRPC**. +La référence Yellowstone OrbitFlare donne explicitement le modèle suivant : + +```text +ORBITFLARE_LICENSE_KEY + -> metadata gRPC x-token + -> Yellowstone +``` + +Le Dashboard opérateur confirme parallèlement que le produit `Solana Free` est en `API Key Mode Active`, donc utilisable depuis toute IP avec la License Key. Le `X-ORBIT-KEY` et le Bearer issus du Device Flow restent réservés au Customer API. + +Le CLI OrbitFlare courant ne constitue pas un canari gRPC d’auth fiable : son `ping` ouvre le canal sans injecter la License Key et reçoit `invalid x-token: api key not found`. Ce défaut du CLI ne modifie pas le contrat provider documenté. Décision : ```text -ne jamais demander ou versionner la clé opérateur Customer API +ne jamais versionner la License Key réelle ne jamais injecter X-ORBIT-KEY dans Yellowstone -commencer le smoke Devnet Free sans metadata secrète -si le Dashboard fournit un token gRPC distinct, classifier son wire avant Config +représenter la License Key par secret_metadata x-token dans Config V3 +faire lire le secret du smoke par stdin, jamais par argument CLI +ne modifier ni N1 ni N2 pour cette auth provider ``` ## 8. Heartbeat : réconciliation KSP / Yellowstone / OrbitFlare @@ -399,7 +409,7 @@ Ces valeurs commerciales/opérationnelles ne deviennent pas des constantes du st ## 11. Config V3 -Le schéma V3 existant sait déjà représenter le besoin minimal : +Le schéma V3 existant représente directement le contrat live corrigé : ```text provider = orbitflare @@ -407,76 +417,69 @@ cluster = devnet protocol = solana_yellowstone url = http://devnet.rpc.orbitflare.com:10000 metadata = [] -secret_metadata = [] +secret_metadata = x-token <- ${KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN} ``` +La variable porte la License Key `ORBIT-*` du produit Solana Free. Elle ne porte jamais `X-ORBIT-KEY`. + Décision : ```text pas de format_version 4 -pas de champ region si l'URL suffit +pas de champ region si l’URL suffit pas de heartbeat dans grpc_defaults -pas de secret tant que le Devnet Free n'en exige pas +secret provider géré par Config V3 existante +redaction KSP obligatoire dans safe/debug projections ``` -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 -secret_metadata = x-token <- ${KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN} -``` - -Cette variable n'est ajoutée à `.env.example` qu'après preuve qu'un secret data-plane est réellement requis. +Le profil `orbitflare_devnet` conserve les companions HTTP/WS Solana Devnet standards et ajoute exactement un endpoint gRPC authentifié par secret metadata `x-token`. ## 12. Stratégie live Devnet -### 12.1 Premier canari +### 12.1 Résultat du premier canari `pre.002` -`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 : +Le smoke initial utilisait le standard N2 directement, sans Config et sans metadata. Le canal physique a atteint le service OrbitFlare, puis l’ouverture du Subscribe a retourné : + +```text +grpc_operation = SubscribeOpen +grpc_status = Unauthenticated +grpc_code = The request does not have valid authentication credentials +``` + +Cette preuve ferme l’hypothèse « Devnet Free sans metadata ». + +### 12.2 Canari corrigé `pre.002-fix.001` + +Le même test reste provider-neutral et ne dépend toujours pas de Config. Il lit une seule License Key sur stdin puis construit la metadata secrète standard : ```text endpoint = http://devnet.rpc.orbitflare.com:10000 provider = orbitflare cluster = devnet protocol = solana_yellowstone -auth = aucune metadata en première tentative +auth = x-token <- License Key lue sur stdin request = Subscribe slots à commitment confirmed preuve = au moins un Slot non nul close = borné ``` -### 12.2 Preuve heartbeat +La valeur secrète ne doit apparaître ni dans Debug ni dans la ligne de commande. -Le smoke de caractérisation doit aussi attendre suffisamment pour observer : +### 12.3 Preuve heartbeat + +Le smoke corrigé attend encore suffisamment pour observer : ```text SubscribeUpdate::Ping ``` -Un `Ping` serveur live, combiné au test déterministe N1 qui prouve la réponse automatique, ferme le besoin heartbeat sans code OrbitFlare. +Un `Ping` serveur live, combiné au test déterministe N1 qui prouve la réponse automatique, ferme le besoin heartbeat sans code OrbitFlare. Si aucun Ping serveur n’est observé après authentification, cette divergence est traitée dans une tranche provider dédiée au-dessus de N1/N2. -Le smoke final durable peut rester court après cette caractérisation ; il n'a pas besoin de patienter 10 minutes à chaque workspace run. +Le smoke final durable peut rester court après cette caractérisation ; il n’a pas besoin de patienter 10 minutes à chaque workspace run. -### 12.3 Unary et replay +### 12.4 Unary et replay -Après le canari Subscribe : +Après le canari Subscribe authentifié : ```text probe 7 unary N2 @@ -538,38 +541,40 @@ README/USAGE dans la tranche documentaire finale ## 15. Forecast recalibré -Le chemin nominal est plus court que le forecast du prompt. +Le fix d’auth ne change pas l’ordre des couloirs finaux. ```text pre.001 audit actuel + architecture immuable N1/N2 + auth/endpoints + free Devnet + heartbeat + sizing preuve : plan 017 + validation 013 + delta + baseline pre.002 profil Config V3 OrbitFlare Devnet + smoke de caractérisation standard N2 - statique : profil sans metadata + canari Subscribe slots confirmed + fenêtre Ping 45 s + close borné + hypothèse initiale sans metadata ; live classifie Unauthenticated au SubscribeOpen + +pre.002-fix.001 + corrige l’auth par License Key -> secret x-token + adapte .env.example, Config test et smoke stdin 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 +pre.003 soit gate technique/live final si le rerun authentifié reste 100 pourcent standard, + soit tranche provider-specific minimale uniquement si le live démontre une divergence heartbeat réelle -pre.004 gate technique/live final si pre.003 a porté une divergence ; sinon cette responsabilité est absorbée par pre.003 +pre.004 gate technique/live final si pre.003 a porté une divergence ; sinon réconciliation documentaire finale -pre.005 réconciliation documentaire finale après dernier gate technique - plan + validation + README + USAGE + références durables +pre.005 publication minimale si pre.004 est documentaire ; sinon réconciliation documentaire finale -pre.006 préparation de publication minimale si une pre.005 documentaire existe à ce numéro - prompt 0.2.11 + CHANGELOG + ROADMAP seulement, hors Cargo/delta mécaniques +pre.006 publication minimale uniquement si la divergence a décalé les couloirs précédents rel.001 publication stable stricte ``` -Numérotation effective : +Chemins effectifs : ```text -chemin standard probable pre.001 -> pre.002 -> pre.003 technique -> pre.004 docs -> pre.005 publication -> rel.001 -chemin divergence pre.001 -> pre.002 -> pre.003 provider -> pre.004 technique -> pre.005 docs -> pre.006 publication -> rel.001 +standard pre.001 -> pre.002 -> pre.002-fix.001 -> pre.003 technique -> pre.004 docs -> pre.005 publication -> rel.001 +divergence pre.001 -> pre.002 -> pre.002-fix.001 -> pre.003 provider -> pre.004 technique -> pre.005 docs -> pre.006 publication -> rel.001 ``` -Le numéro n'est jamais le critère de clôture ; l'ordre des couloirs finaux reste obligatoire. +Le numéro n’est jamais le critère de clôture ; l’ordre technique, documentaire, publication reste obligatoire. ## 16. Critères de split @@ -577,7 +582,7 @@ Ouvrir une tranche provider dédiée avant le gate final si et seulement si le l ```text OrbitFlare n'émet pas le Ping standard et exige un Ping client proactif -le token gRPC ne peut pas être représenté par secret_metadata V3 existant +l’auth x-token fonctionne mais révèle une exigence provider supplémentaire non composable au-dessus de N1/N2 le service requiert un mécanisme TLS/channel absent du moteur stable un method/filter standard est remplacé par une extension wire OrbitFlare le replay/from_slot exige une policy provider visible au consumer 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 98271cd..8eb28e1 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 : `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.** +**Statut courant : `0.2.10-pre.002` est commité et son gate statique/workspace est vert. Son live sans metadata a atteint OrbitFlare mais `SubscribeOpen` a été rejeté `Unauthenticated`. `0.2.10-pre.002-fix.001` corrige l’auth en License Key `ORBIT-*` transmise comme secret `x-token`, sans modification N1/N2. Le rerun authentifié `Subscribe -> Slot + Ping` reste `PENDING OPERATOR`.** ## 1. Autorités @@ -55,17 +55,17 @@ Toute modification de `grpc_stream.rs`, `grpc_channel.rs`, `grpc_settings.rs`, ` ## 4. Pricing et réseau -| Point | Preuve actuelle | Verdict | -|----------------------|------------------------------------------|-------------| -| plan Solana Free | 0 USD par mois | **PASS** | -| gRPC Free | Devnet only | **PASS** | -| RPS Free | 10 | INFO | -| TPS Free | 1 | INFO | -| crédit | unlimited dans le RPS | INFO | -| Devnet gRPC endpoint | `http://devnet.rpc.orbitflare.com:10000` | **PASS** | -| Devnet live KSP | non encore exécuté | **PENDING** | -| Mainnet gratuit | non documenté | **OUT** | -| Testnet endpoint | non prouvé | **UNKNOWN** | +| Point | Preuve actuelle | Verdict | +|----------------------|------------------------------------------|---------------| +| plan Solana Free | 0 USD par mois | **PASS** | +| gRPC Free | Devnet only | **PASS** | +| RPS Free | 10 | INFO | +| TPS Free | 1 | INFO | +| crédit | unlimited dans le RPS | INFO | +| Devnet gRPC endpoint | `http://devnet.rpc.orbitflare.com:10000` | **PASS** | +| Devnet live KSP | service atteint, Subscribe auth rejeté | **AUTH GATE** | +| Mainnet gratuit | non documenté | **OUT** | +| Testnet endpoint | non prouvé | **UNKNOWN** | Sources : @@ -88,18 +88,29 @@ KSP ne prétend pas qu'un canal gRPC plaintext bénéficie d'une sécurité TLS ## 6. Auth classification -| Élément | Usage prouvé | Verdict | -|-----------------------------|--------------------------------|-----------------------| -| `X-ORBIT-KEY` | Customer API | **CONTROL-PLANE** | -| Bearer Customer API | Customer API v2 | **CONTROL-PLANE** | -| RPC `api_key` | Solana HTTP RPC | **HTTP ONLY** | -| API key compte opérateur | CLI/Customer API | **NOT GRPC PROOF** | -| token gRPC Dashboard | quickstart Yellowstone | **ACCOUNT DEPENDENT** | -| metadata upstream `x-token` | mécanisme Yellowstone standard | **POSSIBLE** | -| aucune metadata gRPC | SDK Go OrbitFlare | **DOCUMENTED** | -| auth Devnet Free réelle | smoke KSP à exécuter | **PENDING** | +| Élément | Usage prouvé | Verdict | +|-----------------------|-----------------------------|------------------------------| +| `X-ORBIT-KEY` | Customer API | **CONTROL-PLANE** | +| Bearer Device Flow | Customer API v2 | **CONTROL-PLANE** | +| RPC `api_key` | Solana HTTP RPC | **HTTP ONLY** | +| License Key `ORBIT-*` | produit Solana Free | **DATA-PLANE PROVIDER** | +| metadata `x-token` | Yellowstone gRPC OrbitFlare | **REQUIRED / PROVEN DOC** | +| sans metadata | live KSP `pre.002` | **REJECTED UNAUTHENTICATED** | +| IP whitelist | autre mode produit | **NOT ACTIVE FOR OPERATOR** | -Aucun secret opérateur n’est demandé pour le premier smoke `pre.002` ; la tentative initiale est explicitement sans metadata. +Preuve live `pre.002` : + +```text +grpc_operation = SubscribeOpen +grpc_status = Unauthenticated +grpc_code = The request does not have valid authentication credentials +``` + +Preuve provider : la référence Yellowstone OrbitFlare construit le client avec `X_TOKEN = ORBITFLARE_LICENSE_KEY` et indique que le token est porté par la metadata gRPC `x-token`. + +Le Dashboard opérateur classe le produit `Solana Free` en `API Key Mode Active`. La License Key est donc le secret data-plane à utiliser ; elle n’est pas une `X-ORBIT-KEY` Customer API. + +Le CLI OrbitFlare courant n’est pas utilisé comme oracle positif pour l’auth gRPC : après stockage de la License Key, `orbitflare ping --service grpc --network devnet` retourne `invalid x-token: api key not found`, et son implémentation courante n’injecte pas le secret dans le canal gRPC. ## 7. Heartbeat @@ -163,28 +174,36 @@ 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` | **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** | +| 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 `x-token` | **REQUIRED** | +| valeur License Key via `KSP_SECRET_*` | **PASS DESIGN** | +| `X-ORBIT-KEY` dans Transport Config | **FORBID** | +| heartbeat dans `grpc_defaults` | **NO** | + +`pre.002-fix.001` corrige le profil en : + +```text +secret_metadata = x-token <- ${KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN} +``` + +La valeur effective doit rester redacted dans toutes les projections safe/debug. ## 12. Smoke Devnet attendu -Premier smoke : +Smoke corrigé : ```text -Config/fixture -> YellowstoneGrpcEndpointSettings +License Key lue sur stdin +YellowstoneGrpcMetadataEntry::secret("x-token", ...) provider = orbitflare cluster = devnet url = http://devnet.rpc.orbitflare.com:10000 -metadata vide open standard Subscribe request slots confirmed receive Slot non nul @@ -194,15 +213,18 @@ close borné Verdicts actuels : -| 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** | +| Preuve | Verdict | +|----------------------------------------------|--------------------------------| +| endpoint parsé par N1 | **PASS STATIC** | +| sans metadata | **FAIL EXPECTED / CLASSIFIED** | +| secret `x-token` représentable par Config V3 | **PASS STATIC** | +| License Key redacted | **PASS STATIC** | +| connect authentifié | **PENDING OPERATOR** | +| Subscribe authentifié | **PENDING OPERATOR** | +| Slot reçu | **PENDING OPERATOR** | +| server Ping reçu | **PENDING OPERATOR** | +| auto reply KSP déterministe | **PASS N1** | +| close borné | **PASS N1 / LIVE PENDING** | ## 13. Provider overlay gate @@ -266,48 +288,75 @@ ownership/close provider bornés Verdict : ```text -architecture/sizing gate = POSITIVE -operator pre.001 gate = PASS -pre.002 static gate = READY FOR OPERATOR VALIDATION -live provider gate = PENDING pre.002 characterization +architecture/sizing gate = POSITIVE +operator pre.001 gate = PASS +pre.002 static/workspace gate = PASS +pre.002 live auth hypothesis = FAIL CLASSIFIED +pre.002-fix.001 static gate = READY FOR OPERATOR VALIDATION +live provider gate = PENDING authenticated characterization ``` -## 16. Gate `pre.002` candidat +## 16. Gate `pre.002` commité -| 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** | +Validation opérateur du 2026-08-25 : -Commande live dédiée : +| Critère | Verdict | +|------------------------------------------|---------------------------------| +| Cargo `0.2.10-pre.2` | **PASS** | +| `cargo fmt --all` | **PASS** | +| audit Rust | **PASS** | +| audit Markdown | **PASS / 98 tables / 98 files** | +| `cargo check --workspace` | **PASS** | +| `cargo clippy --workspace --all-targets` | **PASS** | +| Config unit | **114 sur 114 PASS** | +| Transport unit | **383 sur 383 PASS** | +| Transport public API | **49 sur 49 PASS** | +| Transport release completeness | **43 sur 43 PASS** | +| workspace test | **PASS** | +| graphes Transport | **FOURNIS / PAS DE CONFLIT N1** | +| moteur N1 modifié | **NO** | +| standard N2 modifié | **NO** | +| live sans metadata | **FAIL CLASSIFIED** | +| `SubscribeOpen` | **UNAUTHENTICATED** | + +Le failure live est un résultat de caractérisation utile et non une régression du moteur. Il impose un fix de configuration/auth provider parce que `pre.002` est déjà commité. + +## 17. Gate `pre.002-fix.001` + +| Critère | Verdict | +|-----------------------------------------------------------------|----------------------| +| Cargo `0.2.10-pre.2.fix.1` | **PASS STATIC** | +| `.env.example` inventorie le secret OrbitFlare | **PASS STATIC** | +| Config `secret_metadata` contient `x-token` | **PASS STATIC** | +| valeur provenant de `KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN` | **PASS STATIC** | +| test Config prouve redaction et classification secrète | **PASS STATIC** | +| smoke lit la License Key sur stdin | **PASS STATIC** | +| secret absent des arguments CLI | **PASS STATIC** | +| moteur N1 modifié | **NO** | +| standard N2 modifié | **NO** | +| SDK OrbitFlare ajouté | **NO** | +| connect authentifié | **PENDING OPERATOR** | +| `Subscribe -> Slot` live | **PENDING OPERATOR** | +| `SubscribeUpdate::Ping` live | **PENDING OPERATOR** | +| close live | **PENDING OPERATOR** | + +Commande live dédiée, avec secret saisi sans l’inscrire dans l’historique : ```bash -cargo test -p ksp-onchain-transport-lib --test yellowstone_orbitflare_smoke -- --ignored --nocapture +read -rsp 'OrbitFlare License Key: ' ORBITFLARE_LICENSE_KEY; echo +printf '%s\n' "$ORBITFLARE_LICENSE_KEY" | cargo test -p ksp-onchain-transport-lib --test yellowstone_orbitflare_smoke -- --ignored --nocapture +unset ORBITFLARE_LICENSE_KEY ``` -Le résultat live décide la suite : +Interprétation : ```text Slot + Ping PASS - -> OrbitFlare reste standard ; aucun provider heartbeat/facade + -> auth et heartbeat standards composables ; pre.003 devient gate technique/live final -connection/auth failure - -> classifier le service/licence réel avant toute metadata +Subscribe/Slot PASS mais Ping absent + -> divergence heartbeat provider à traiter au-dessus de N1/N2 dans pre.003 -Slot PASS mais Ping absent - -> divergence heartbeat à prouver avant une couche provider-specific +auth encore rejetée avec la License Key x-token + -> blocker provider/account à qualifier ; ne jamais modifier N1 pour contourner le gate ```