v0.2.8-pre.007-fix.003

This commit is contained in:
2026-08-23 16:44:31 +02:00
parent 9cc140fb84
commit 68f4384c5b
5 changed files with 234 additions and 25 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 229
# version: 230
[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.8-pre.7.fix.2"
version = "0.2.8-pre.7.fix.3"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
// version: 10
// version: 11
use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import
@@ -189,16 +189,17 @@ async fn yield_runtime_steps() {
return;
}
async fn wait_for_observed_ping(ping_rx: &mut tokio::sync::mpsc::UnboundedReceiver<()>) {
for _ in 0..256 {
match ping_rx.try_recv() {
std::result::Result::Ok(()) => return,
std::result::Result::Err(tokio::sync::mpsc::error::TryRecvError::Empty) => tokio::task::yield_now().await,
std::result::Result::Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => panic!("heartbeat observation channel disconnected before Ping"),
}
}
assert!(matches!(ping_rx.try_recv(), std::result::Result::Ok(())), "heartbeat Ping was not observed after bounded scheduler progress");
return;
async fn wait_for_observed_ping_with_io_progress(ping_rx: &mut tokio::sync::mpsc::UnboundedReceiver<()>) -> std::time::Duration {
let started = tokio::time::Instant::now();
tokio::time::resume();
let observed = tokio::time::timeout(std::time::Duration::from_secs(1), ping_rx.recv()).await;
tokio::time::pause();
let elapsed = tokio::time::Instant::now().duration_since(started);
assert!(
matches!(observed, std::result::Result::Ok(std::option::Option::Some(()))),
"heartbeat Ping was not observed during bounded real I/O progress"
);
return elapsed;
}
#[tokio::test(flavor = "current_thread")]
@@ -469,12 +470,14 @@ async fn helius_heartbeat_sends_ping_at_sixty_seconds_and_rearms() {
yield_runtime_steps().await;
assert!(matches!(ping_rx.try_recv(), std::result::Result::Err(tokio::sync::mpsc::error::TryRecvError::Empty)));
tokio::time::advance(std::time::Duration::from_secs(1)).await;
wait_for_observed_ping(&mut ping_rx).await;
tokio::time::advance(std::time::Duration::from_secs(59)).await;
let first_io_elapsed = wait_for_observed_ping_with_io_progress(&mut ping_rx).await;
let pre_second_interval =
std::time::Duration::from_secs(59).checked_sub(first_io_elapsed).expect("bounded first Ping I/O progress must remain below one second");
tokio::time::advance(pre_second_interval).await;
yield_runtime_steps().await;
assert!(matches!(ping_rx.try_recv(), std::result::Result::Err(tokio::sync::mpsc::error::TryRecvError::Empty)));
tokio::time::advance(std::time::Duration::from_secs(1)).await;
wait_for_observed_ping(&mut ping_rx).await;
tokio::time::advance(std::time::Duration::from_secs(1) + first_io_elapsed).await;
let _second_io_elapsed = wait_for_observed_ping_with_io_progress(&mut ping_rx).await;
session.close().await.expect("Helius heartbeat fixture session must close");
tokio::time::resume();
server.await.expect("Helius heartbeat fixture server must complete");

View File

@@ -0,0 +1,105 @@
<!-- file: deltas/0.2.8/pre.007-fix.003.md -->
<!-- version: 1 -->
# Delta `0.2.8-pre.007-fix.003` — driver I/O sous horloge Tokio pausée
## 1. Base et défaut restant
Base appliquée :
```text
0.2.8-pre.7.fix.2
```
Le replay opérateur du `fix.002` est propre pour fmt/audit/check/Clippy mais reste à **330/331 unit** sur le même canari :
```text
helius_heartbeat_sends_ping_at_sixty_seconds_and_rearms
```
Après 256 `yield_now()` bornés, le Ping n'est toujours pas visible par le canal de la fixture TCP locale.
## 2. Version technique
```text
workspace.package.version = 0.2.8-pre.7.fix.3
commit attendu = v0.2.8-pre.007-fix.003
Git tag = aucun tag prerelease
```
Le header root `Cargo.toml` passe en version `230`.
## 3. Diagnostic affiné
La revue du runtime confirme que `src/ws_session.rs` possède déjà le chemin attendu :
```text
heartbeat_deadline = now + 60 s
tokio::select! -> sleep_until(heartbeat_deadline)
send_helius_heartbeat() -> websocket.send(Ping)
succès -> heartbeat_deadline = now + 60 s
```
Le canari de reconnexion actor réel reste vert et observe un Ping après réarmement. Aucun défaut runtime nouveau n'est donc démontré.
Le défaut est dans le harness : avec l'horloge Tokio pausée, `yield_now()` fait progresser les tâches runnable mais ne garantit pas à lui seul un tour du driver I/O OS. Une frame écrite vers le socket loopback peut donc ne pas encore être remontée jusqu'au `websocket.next()` de la fixture.
## 4. Correction du canari
Le helper yield-only est remplacé par une observation bornée qui :
```text
1. reprend temporairement l'horloge Tokio ;
2. attend `ping_rx.recv()` avec un timeout réel de 1 s maximum ;
3. mesure le temps réel consommé ;
4. repause immédiatement l'horloge ;
5. exige qu'un Ping ait été reçu.
```
La fenêtre n'est ouverte **qu'après** que l'horloge virtuelle a atteint le deadline attendu. Une seconde réelle ne peut donc pas masquer l'absence d'un heartbeat dont la cadence nominale est 60 s.
Pour le second intervalle, le temps réel consommé lors de la première propagation est soustrait de la phase pré-deadline puis réinjecté lors du franchissement suivant. Cela garantit :
```text
avant contrôle négatif : moins de 59 s depuis le réarmement réel
après avance positive : au moins 60 s depuis le réarmement réel
```
## 5. Runtime inchangé
Ce fix ne modifie pas :
```text
crates/ksp-onchain-transport-lib/src/ws_session.rs
HELIUS_WS_HEARTBEAT_INTERVAL = 60 s
WebSocket Ping control frame
actor/reconnect/shutdown
WsSessionSettings
Config / schema / env
transactionSubscribe / transactionNotification lifecycle
```
Aucune dépendance, feature, API ou visibilité n'est ajoutée.
## 6. Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md
docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md
deltas/0.2.8/pre.007-fix.003.md
```
## 7. Gate opérateur
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
cargo test --workspace
```
Critère de fermeture : zéro warning, audit clean, **331 unit / 40 public API / 29 completeness / 4 doctests** et workspace vert.

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md -->
<!-- version: 19 -->
<!-- version: 20 -->
# Plan `0.2.8` — Helius LaserStream WebSocket
> **Statut : `0.2.8-pre.007-fix.001` corrige le lint et le canari close, mais le replay laisse un seul canari périodique en course scheduler. `0.2.8-pre.007-fix.002` borne l'observation du Ping sans avancer l'horloge virtuelle ; le runtime heartbeat Helius 60 s reste inchangé.**
> **Statut : `0.2.8-pre.007-fix.002` passe fmt/audit/check/Clippy mais laisse le même unique canari périodique à 330/331. Le diagnostic est affiné : `yield_now()` ne garantit pas la progression du driver I/O OS sous horloge Tokio pausée. `0.2.8-pre.007-fix.003` conserve le runtime heartbeat byte-identical et borne la propagation TCP en temps réel après franchissement du deadline virtuel.**
## 1. Objet, base et état courant
@@ -36,10 +36,11 @@ pre.005-fix.002 suppression warnings dead_code via cfg(test) validée
pre.006 transactionNotification + lifecycle actor validé
pre.007 heartbeat Helius WebSocket / idle — checkpoint gate à corriger
pre.007-fix.001 lint + close canary corrigés ; un canari périodique reste non déterministe
pre.007-fix.002 observation bornée du Ping après deadline — préparé
pre.007-fix.002 observation yield-only insuffisante — checkpoint 330/331
pre.007-fix.003 progression I/O réelle bornée après deadline virtuel — préparé
workspace.package.version courant = 0.2.8-pre.7.fix.2
commit attendu = v0.2.8-pre.007-fix.002
workspace.package.version courant = 0.2.8-pre.7.fix.3
commit attendu = v0.2.8-pre.007-fix.003
aucun tag prerelease
```
@@ -1230,3 +1231,48 @@ Ainsi, le test peut laisser la propagation async finir **sans faire avancer l'ho
Le runtime reste byte-for-byte hors du fix : cadence 60 s, Ping control frame, actor unique, reconnect, shutdown, Config et lifecycle transaction inchangés. `pre.008` reste bloqué jusqu'au replay entièrement vert de `pre.007-fix.002`.
## 18. Replay `pre.007-fix.002` et préparation `pre.007-fix.003`
Checkpoint opérateur `0.2.8-pre.7.fix.2` reçu le 2026-08-23 :
```text
[x] cargo fmt --all
[x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate
[x] cargo check --workspace = vert, sans warning
[x] cargo clippy --workspace --all-targets = vert, sans warning
[!] cargo test -p ksp-onchain-transport-lib = 330/331 unit ; même unique canari périodique
[ ] cargo test --workspace — non retenu tant que Transport n'est pas vert
```
Le `fix.002` démontre que le problème n'est pas un simple manque de tours scheduler : même 256 `yield_now()` bornés ne rendent pas le Ping observable par la fixture TCP locale. La revue du runtime montre en parallèle que :
```text
- le deadline heartbeat est construit par `next_helius_heartbeat_deadline()` ;
- la branche `sleep_until(heartbeat_deadline)` est bien présente dans le `tokio::select!` de l'actor ;
- un succès de `send_helius_heartbeat()` réarme à `now + 60 s` ;
- le canari reconnect actor réel continue de prouver un Ping après le réarmement de connexion.
```
Diagnostic affiné : sous horloge Tokio pausée, `yield_now()` garantit une cession au scheduler mais **ne garantit pas à lui seul un tour du driver I/O OS**. Le chemin d'observation TCP :
```text
actor -> websocket.send(Ping) -> socket loopback -> reactor I/O -> fixture websocket.next() -> channel
```
peut donc rester invisible au `try_recv()` malgré un timer actor expiré et une écriture déjà engagée.
`fix.003` reste test-only côté Rust et conserve `src/ws_session.rs` byte-identical. Le canari périodique :
```text
- garde l'horloge virtuelle pausée pour prouver l'absence avant le deadline ;
- avance exactement jusqu'au deadline nominal ;
- reprend ensuite le temps réel uniquement pendant une fenêtre I/O bornée <= 1 s pour laisser le reactor TCP propager le Ping ;
- repause immédiatement l'horloge après observation ;
- compense le temps réel consommé avant le contrôle du second intervalle afin de rester strictement avant le second deadline ;
- franchit ensuite le second deadline avant une nouvelle fenêtre I/O bornée.
```
Cette fenêtre réelle ne peut pas masquer un heartbeat manquant : elle ne dure qu'une seconde au maximum, très inférieure à l'intervalle provider-owned de 60 s, et n'est ouverte qu'après franchissement du deadline virtuel attendu.
Le runtime, Config, `WsSessionSettings`, le lifecycle transaction et la cadence 60 s restent inchangés. `pre.008` reste bloqué jusqu'au replay entièrement vert de `pre.007-fix.003`.

View File

@@ -1,9 +1,9 @@
<!-- file: docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md -->
<!-- version: 18 -->
<!-- version: 19 -->
# Validation `0.2.8` — Helius LaserStream WebSocket
> **Statut : `pre.007-fix.001` passe fmt/audit/check/Clippy et corrige le close canary ; un seul canari périodique reste non déterministe. `pre.007-fix.002` est préparé comme correction test-only d'observation scheduler, sans changement runtime heartbeat.**
> **Statut : `pre.007-fix.002` passe fmt/audit/check/Clippy mais reste à 330/331 sur le même canari périodique. `pre.007-fix.003` corrige le harness TCP/horloge pausée par une progression I/O réelle bornée après deadline virtuel, sans changement runtime heartbeat.**
## 1. Références
@@ -27,6 +27,7 @@ pre.006 actor transaction deltas/0.2.8/pre.006.md
pre.007 heartbeat Helius deltas/0.2.8/pre.007.md
pre.007 test determinism fix deltas/0.2.8/pre.007-fix.001.md
pre.007 scheduler observation fix deltas/0.2.8/pre.007-fix.002.md
pre.007 paused-I/O observation fix deltas/0.2.8/pre.007-fix.003.md
validation standard WS docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md
HTTP compliance docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md
KSP-TRANSPORT-007 docs/validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md
@@ -345,7 +346,6 @@ Constats `pre.002-fix.001` :
Aucun split en fichiers supplémentaires n'est retenu : les plans historiques `0.2.5``0.2.7` sont de taille comparable ou supérieure, et le contenu du plan `015` reste entièrement centré sur une seule release. Le problème identifié était **l'ordre interne et la duplication de responsabilités**, pas la nécessité d'un nouveau type de document.
## 12. Gates opérateur `pre.004` / `fix.001` / `fix.002`
Premier passage `pre.004` reçu le 2026-08-23 :
@@ -733,3 +733,58 @@ Critères `pre.007-fix.002` :
Verdict courant : **`pre.007` FIX REQUIRED ; `pre.007-fix.001` CHECKPOINT ; `pre.007-fix.002` PREPARED ; `pre.008` bloqué.**
## 20. Replay `pre.007-fix.002` et gate `pre.007-fix.003`
Résultat opérateur `0.2.8-pre.7.fix.2` :
```text
[x] cargo fmt --all
[x] audit Rust workspace = clean / 0 candidate
[x] cargo check --workspace = vert, sans warning
[x] cargo clippy --workspace --all-targets = vert, sans warning
[!] Transport unit = 330 passed / 1 failed sur 331
```
Le seul échec reste :
```text
helius_heartbeat_sends_ping_at_sixty_seconds_and_rearms
-> aucun Ping observé après 256 yield_now() bornés
```
Les autres canaris heartbeat restent verts :
```text
[x] policy Helius-only + intervalle 60 s
[x] SolanaStandard sans heartbeat provider
[x] close avant deadline
[x] write failure -> reconnect existant
[x] réarmement après reconnect avec Ping actor réel
```
Le `fix.002` invalide donc l'hypothèse « quelques yields supplémentaires suffisent ». Sous temps Tokio pausé, la progression scheduler n'implique pas la progression du driver I/O TCP local.
Critères `pre.007-fix.003` :
```text
[ ] aucune modification de `src/ws_session.rs`
[ ] cadence Helius 60 s inchangée
[ ] aucune API/Config/dependency supplémentaire
[ ] absence de Ping prouvée avant le premier deadline avec horloge virtuelle pausée
[ ] premier deadline virtuel franchi avant toute fenêtre temps réel
[ ] propagation I/O réelle bornée à <= 1 s et Ping exigé
[ ] horloge repausée immédiatement après observation
[ ] temps réel consommé compensé avant le contrôle pré-second-deadline
[ ] aucun second Ping avant au moins 59 s depuis le réarmement
[ ] second deadline franchi avant la seconde fenêtre I/O bornée
[ ] second Ping exigé
[ ] cargo fmt --all
[ ] audit Rust = clean
[ ] cargo check = sans warning
[ ] cargo clippy = sans warning
[ ] Transport = 331 unit + 40 public API + 29 completeness + 4 doctests
[ ] cargo test --workspace = vert
```
Verdict courant : **`pre.007` FIX REQUIRED ; `fix.001` CHECKPOINT ; `fix.002` CHECKPOINT ; `fix.003` PREPARED ; `pre.008` bloqué.**