v0.2.10-pre.002

This commit is contained in:
2026-08-25 10:29:16 +02:00
parent 7f567bc1bb
commit af807afff5
10 changed files with 667 additions and 59 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 262 # version: 263
[workspace] [workspace]
resolver = "3" 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"] 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] [workspace.package]
version = "0.2.10-pre.1" version = "0.2.10-pre.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -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", "profile_id": "mainnet_public",
"endpoints": [ "endpoints": [

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/unit_tests/transport.rs // file: crates/ksp-config-lib/unit_tests/transport.rs
// version: 10 // version: 11
#[test] #[test]
fn fixture_transport_profile_maps_complete_runtime_contract() { 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] #[test]
fn committed_v3_publicnode_profiles_map_provider_neutral_yellowstone_grpc_with_network_scoped_secret_x_tokens() { fn committed_v3_publicnode_profiles_map_provider_neutral_yellowstone_grpc_with_network_scoped_secret_x_tokens() {
let engine = committed_engine(); let engine = committed_engine();

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/README.md --> <!-- file: crates/ksp-onchain-transport-lib/README.md -->
<!-- version: 22 --> <!-- version: 23 -->
# `ksp-onchain-transport-lib` # `ksp-onchain-transport-lib`
@@ -220,7 +220,6 @@ Pour Helius, lactor 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`. LaserStream **gRPC** reste un backend distinct, hors de cette façade, de `WsProtocolKind` et de la Config WebSocket `helius_laserstream`.
## Yellowstone gRPC standard ## 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 nest réexporté. 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 nest réexporté.

View File

@@ -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<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),
};
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<ksp_onchain_transport_lib::YellowstoneSubscribeRequest> {
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"
);
}

287
deltas/0.2.10/pre.002.md Normal file
View File

@@ -0,0 +1,287 @@
<!-- file: deltas/0.2.10/pre.002.md -->
<!-- version: 1 -->
# 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.

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md --> <!-- file: docs/plans/017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Plan `0.2.10` — OrbitFlare Yellowstone gRPC # 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é ## 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 : Preuves fournies le 2026-08-25 :
| Gate | Résultat | | Gate | Résultat |
|-------------------------------------------|----------------------------------| |-------------------------------------------|------------------------------------------|
| `cargo fmt --all` | PASS | | `cargo fmt --all` | PASS |
| audit Rust workspace | PASS, 0 candidate export | | audit Rust workspace | PASS, 0 candidate export |
| audit Markdown tables | PASS, 87 tables et 261 files | | audit Markdown tables | PASS, 97 tables et 233 files sur `pre.1` |
| `cargo check --workspace` | PASS | | `cargo check --workspace` | PASS |
| `cargo clippy --workspace --all-targets` | PASS | | `cargo clippy --workspace --all-targets` | PASS |
| `cargo test --workspace` | PASS | | `cargo test --workspace` | PASS |
| Transport unit | 383 sur 383 PASS | | Transport unit | 383 sur 383 PASS |
| Transport public API | 49 sur 49 PASS | | Transport public API | 49 sur 49 PASS |
| Transport release completeness | 43 sur 43 PASS | | Transport release completeness | 43 sur 43 PASS |
| Config unit | 113 sur 113 PASS | | Config unit | 113 sur 113 PASS |
| PublicNode Yellowstone live | ignored opt-in comme attendu | | PublicNode Yellowstone live | ignored opt-in comme attendu |
| `cargo tree --duplicates` Transport | fourni et inspecté | | `cargo tree --duplicates` Transport | fourni et inspecté |
| `cargo tree -p ksp-onchain-transport-lib` | à rejouer avant commit `pre.001` | | `cargo tree -p ksp-onchain-transport-lib` | fourni sur `0.2.10-pre.1` |
Versions visibles dans le graphe fourni : 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 pas de secret tant que le Devnet Free n'en exige pas
``` ```
Profil prévu : Profil matérialisé en `pre.002` :
```text ```text
orbitflare_devnet 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 : Si le service opérateur réel impose ensuite un token gRPC :
```text ```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 ### 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 ```text
endpoint = http://devnet.rpc.orbitflare.com:10000 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 preuve : plan 017 + validation 013 + delta + baseline
pre.002 profil Config V3 OrbitFlare Devnet + smoke de caractérisation standard N2 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 statique : profil sans metadata + canari Subscribe slots confirmed + fenêtre Ping 45 s + close borné
aucune modification moteur gRPC live : Subscribe -> Slot + observation Ping serveur ; aucune modification moteur gRPC
pre.003 soit gate technique final si OrbitFlare reste 100 pourcent standard, 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 soit tranche provider-specific minimale uniquement si pre.002 démontre une divergence réelle

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_DOCUMENTATION.md --> <!-- file: docs/rules/RULES_DOCUMENTATION.md -->
<!-- version: 5 --> <!-- version: 6 -->
# Règles de documentation # Règles de documentation
@@ -41,6 +41,12 @@ Les règles `DOC-*` s'appliquent aux documents Markdown internes et à leur orga
- **DOC-TABLE-005** — Lorsquune modification touche une ligne dun tableau, le tableau entier est réaligné avant livraison. `python3 scripts/audit_markdown_tables.py <fichiers-markdown-modifiés>` 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-005** — Lorsquune modification touche une ligne dun tableau, le tableau entier est réaligné avant livraison. `python3 scripts/audit_markdown_tables.py <fichiers-markdown-modifiés>` 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** — Limmuabilité des deltas déjà publiés prime sur un reformatage rétrospectif : un ancien fichier `deltas/` nest 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`. - **DOC-TABLE-006** — Limmuabilité des deltas déjà publiés prime sur un reformatage rétrospectif : un ancien fichier `deltas/` nest 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 dune ligne vide consécutive. Une ligne composée uniquement despaces ou de tabulations est considérée comme vide.
- **DOC-BLANK-002** — `python3 scripts/audit_markdown_tables.py <fichiers-ou-répertoires>` contrôle aussi les lignes vides multiples. Lors dun 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 ## 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. - **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.

View File

@@ -1,9 +1,9 @@
<!-- file: docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md --> <!-- file: docs/validation/013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Validation `0.2.10` — OrbitFlare Yellowstone gRPC # 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 ## 1. Autorités
@@ -36,7 +36,7 @@ Yellowstone upstream actuel
| Transport release completeness | 43 sur 43 PASS | | Transport release completeness | 43 sur 43 PASS |
| Config unit | 113 sur 113 PASS | | Config unit | 113 sur 113 PASS |
| graph duplicates Transport | fourni, aucun conflit N1 visible | | 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 ## 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** | | aucune metadata gRPC | SDK Go OrbitFlare | **DOCUMENTED** |
| auth Devnet Free réelle | smoke KSP à exécuter | **PENDING** | | auth Devnet Free réelle | smoke KSP à exécuter | **PENDING** |
Aucun secret opérateur n'est demandé pour `pre.001`. Aucun secret opérateur nest demandé pour le premier smoke `pre.002` ; la tentative initiale est explicitement sans metadata.
## 7. Heartbeat ## 7. Heartbeat
@@ -163,17 +163,17 @@ Ces valeurs restent provider-owned et ne deviennent pas des validations généri
## 11. Config V3 ## 11. Config V3
| Exigence | Verdict | | Exigence | Verdict |
|-------------------------------------|-------------| |-------------------------------------|-----------------|
| nouveau format Config nécessaire | **NO** | | nouveau format Config nécessaire | **NO** |
| `provider = orbitflare` | **PLANNED** | | `provider = orbitflare` | **PASS STATIC** |
| `protocol = solana_yellowstone` | **PASS** | | `protocol = solana_yellowstone` | **PASS STATIC** |
| `cluster = devnet` | **PLANNED** | | `cluster = devnet` | **PASS STATIC** |
| Devnet URL publique | **PLANNED** | | Devnet URL publique | **PASS STATIC** |
| secret metadata par défaut | **NO** | | secret metadata par défaut | **NO / PASS** |
| `X-ORBIT-KEY` dans Transport Config | **FORBID** | | `X-ORBIT-KEY` dans Transport Config | **FORBID** |
| `x-token` si token gRPC prouvé | **DEFER** | | `x-token` si token gRPC prouvé | **DEFER** |
| heartbeat dans `grpc_defaults` | **NO** | | heartbeat dans `grpc_defaults` | **NO** |
## 12. Smoke Devnet attendu ## 12. Smoke Devnet attendu
@@ -194,15 +194,15 @@ close borné
Verdicts actuels : Verdicts actuels :
| Preuve | Verdict | | Preuve | Verdict |
|-----------------------------|-------------| |------------------------------|----------------------------|
| endpoint parsé par N1 | EXPECTED | | endpoint parsé par Config/N1 | **PASS STATIC** |
| connect Devnet | **PENDING** | | connect Devnet | **PENDING** |
| Subscribe accepté | **PENDING** | | Subscribe accepté | **PENDING** |
| Slot reçu | **PENDING** | | Slot reçu | **PENDING** |
| server Ping reçu | **PENDING** | | server Ping reçu | **PENDING** |
| auto reply KSP déterministe | **PASS** | | auto reply KSP déterministe | **PASS N1** |
| close borné | **PASS N1** | | close borné | **PASS N1 / LIVE PENDING** |
## 13. Provider overlay gate ## 13. Provider overlay gate
@@ -246,7 +246,7 @@ ownership/close provider bornés
| baseline fmt/audits/check/clippy | **PASS** | | baseline fmt/audits/check/clippy | **PASS** |
| workspace test | **PASS** | | workspace test | **PASS** |
| graph duplicates | **PASS** | | graph duplicates | **PASS** |
| graph complet Transport | **PENDING OPERATOR** | | graph complet Transport | **PASS** |
| sources OrbitFlare actuelles | **PASS** | | sources OrbitFlare actuelles | **PASS** |
| sources Yellowstone actuelles | **PASS** | | sources Yellowstone actuelles | **PASS** |
| Free Devnet gRPC confirmé | **PASS** | | Free Devnet gRPC confirmé | **PASS** |
@@ -267,6 +267,47 @@ Verdict :
```text ```text
architecture/sizing gate = POSITIVE architecture/sizing gate = POSITIVE
operator commit gate = PENDING full cargo tree + validations overlay operator pre.001 gate = PASS
live provider gate = reserved for pre.002+ 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
``` ```

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# file: scripts/audit_markdown_tables.py # 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 from __future__ import annotations
@@ -12,6 +12,7 @@ import re
import sys import sys
_SEPARATOR_CELL = re.compile(r"^:?-{3,}:?$") _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]: 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: for raw_path in paths:
path = pathlib.Path(raw_path) path = pathlib.Path(raw_path)
if path.is_dir(): 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": elif path.is_file() and path.suffix.lower() == ".md":
files.append(path) files.append(path)
else: else:
@@ -101,9 +108,47 @@ def _validate_table(path: pathlib.Path, start_line: int, rows: list[str]) -> lis
return errors 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]]: def _audit_file(path: pathlib.Path) -> tuple[int, list[str]]:
lines = path.read_text(encoding="utf-8").splitlines() lines = path.read_text(encoding="utf-8").splitlines()
errors: list[str] = [] errors = _validate_blank_lines(path, lines)
table_count = 0 table_count = 0
fence_marker: str | None = None fence_marker: str | None = None
index = 0 index = 0
@@ -135,7 +180,7 @@ def _audit_file(path: pathlib.Path) -> tuple[int, list[str]]:
def main() -> int: def main() -> int:
"""Audit Markdown tables in the explicitly selected scope.""" """Audit KSP Markdown formatting in the explicitly selected scope."""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("paths", nargs="+", help="Markdown files or directories to audit") parser.add_argument("paths", nargs="+", help="Markdown files or directories to audit")