v0.2.10-pre.002-fix.001

This commit is contained in:
2026-08-25 14:17:40 +02:00
parent af807afff5
commit 1967e845b0
8 changed files with 464 additions and 174 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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}"
}
]
}
]
},

View File

@@ -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::<String, String>::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");
}
}

View File

@@ -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<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
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<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
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<ksp_onchain_transport_li
16 * 1024 * 1024,
4 * 1024 * 1024,
);
return std::result::Result::Ok(ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings::new(
let endpoint = 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,
));
);
let metadata = match ksp_onchain_transport_lib::YellowstoneGrpcMetadataEntry::secret("x-token", x_token) {
std::result::Result::Ok(value) => 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<ksp_onchain_transport_lib::YellowstoneSubscribeRequest> {
@@ -43,22 +65,26 @@ fn slot_request() -> ksp_core_lib::Result<ksp_onchain_transport_lib::Yellowstone
}
#[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() {
#[ignore = "opt-in live OrbitFlare Devnet Yellowstone gRPC characterization; reads one Solana Free License Key x-token from stdin, waits for Slot plus standard server Ping, then closes"]
async fn orbitflare_devnet_yellowstone_streams_slots_and_emits_standard_server_ping_with_license_x_token() {
let x_token = orbitflare_license_x_token();
let endpoint = match orbitflare_devnet_endpoint(x_token.as_str()) {
std::result::Result::Ok(value) => 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);

View File

@@ -0,0 +1,196 @@
<!-- file: deltas/0.2.10/pre.002-fix.001.md -->
<!-- version: 1 -->
# 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 lauth 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 louverture 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 nest 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 napparaî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 nest ajouté.
Lauth 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 dabord 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 nest 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 dajouter un timer OrbitFlare au moteur gRPC.
## 8. Validation disponible dans lenvironnement 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 nest déclarée exécutée dans lenvironnement 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.

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# 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 lhypothèse dauth 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 dauth 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 dauth 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 lURL 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 louverture du Subscribe a retourné :
```text
grpc_operation = SubscribeOpen
grpc_status = Unauthenticated
grpc_code = The request does not have valid authentication credentials
```
Cette preuve ferme lhypothè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 nest 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 na 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 dauth ne change pas lordre 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 lauth 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 nest jamais le critère de clôture ; lordre 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
lauth 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

View File

@@ -1,9 +1,9 @@
<!-- file: docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# 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 lauth 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 nest 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 nest pas une `X-ORBIT-KEY` Customer API.
Le CLI OrbitFlare courant nest pas utilisé comme oracle positif pour lauth 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 ninjecte 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 linscrire dans lhistorique :
```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
```