Files
khadhroony-solana-project/deltas/0.2.7/pre.007.md
2026-08-22 20:42:06 +02:00

153 lines
8.1 KiB
Markdown

<!-- file: deltas/0.2.7/pre.007.md -->
<!-- version: 1 -->
# Delta `0.2.7-pre.007` — reconnect borné et resubscribe déterministe
## Base
Base directe validée par l'opérateur :
```text
0.2.7-pre.006-fix.001
Cargo 0.2.7-pre.6.fix.1
```
Le checkpoint précédent est vert sur `cargo fmt --all`, audit Python KSP, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, `cargo test -p ksp-onchain-transport-lib` avec 275 tests réussis, puis `cargo test --workspace` avec uniquement les smokes/diagnostics déjà attendus en ignored.
## Objectif
Matérialiser la résilience de session WebSocket prévue par le plan sans avancer sur les wrappers typed publics ni sur le backpressure per-subscription :
- reconnect automatique uniquement après perte physique ou protocolaire structurelle ;
- budget fini `max_retries` ;
- backoff exponentiel borné sans jitter ;
- shutdown prioritaire pendant backoff et handshake ;
- invalidation immédiate des remote subscription IDs après rupture ;
- incrément du `continuity_gap_count` une fois par perte de continuité ;
- restauration déterministe des subscriptions encore désirées en ordre croissant de `WsSubscriptionId` ;
- conservation et replay exact des paramètres de subscribe ;
- remapping des nouveaux remote IDs sans changer les IDs locaux ;
- policy `Never` sans restauration automatique ;
- cancellation locale gagnante pendant reconnect/resubscribe ;
- nettoyage best-effort d'un ACK de resubscribe devenu stale ;
- reset du budget seulement après retour complet à `Active` ;
- aucun replay implicite des requests applicatives en vol ;
- aucun backfill HTTP et aucune promesse lossless.
## Signal de version
```text
livraison 0.2.7-pre.007
workspace.package.version 0.2.7-pre.7
commit attendu v0.2.7-pre.007
Git tag aucun
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-core-lib/tests/workspace_dependencies.rs
crates/ksp-onchain-transport-lib/Cargo.toml
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/ws_session.rs
crates/ksp-onchain-transport-lib/src/ws_subscription.rs
crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md
docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md
deltas/0.2.7/pre.007.md
```
## Implémentation
### Session physique
Une perte détectée après activation de la session entre dans `Reconnecting { attempt }` au lieu de terminer immédiatement l'actor. Les triggers retenus restent les erreurs physiques/socket/TLS/WebSocket, EOF ou Close distant inattendu et les violations protocolaires structurelles. Les erreurs RPC applicatives continuent de ne pas déclencher de reconnect.
Chaque tentative attend un backoff exponentiel borné par les settings existants. Le signal de shutdown est surveillé séparément de la command queue pendant le backoff et le handshake de remplacement afin qu'un `WsSession::close()` puisse interrompre la reprise sans ouvrir une nouvelle connexion uniquement pour effectuer du nettoyage.
Le budget est consommé pour la perte courante et n'est considéré réinitialisé qu'après établissement d'un nouveau socket, restauration des subscriptions retenues et publication complète de `Active`.
### Continuity gaps et subscriptions
Lors d'une rupture, tous les remote IDs sont invalidés et le mapping remote vers local est vidé. `continuity_gap_count` est incrémenté une fois pour la perte logique. Ce compteur ne constitue pas une garantie de livraison et Transport n'effectue aucun backfill.
Avec `WsResubscribePolicy::ActiveSubscriptions`, les subscriptions `Active` passent en `Resubscribing`. L'actor conserve leurs paramètres de création et les rejoue séquentiellement dans l'ordre des IDs locaux. Chaque ACK réussi remappe un nouveau remote ID au même `WsSubscriptionId`.
Avec `WsResubscribePolicy::Never`, la reconnexion physique reste possible mais les anciennes subscriptions deviennent terminales et doivent être recréées par le consumer.
### Races de cancellation
Un unsubscribe reçu pendant le backoff retire immédiatement la subscription de la restauration et retourne sans reconnecter pour nettoyer l'ancien remote ID devenu invalide.
Si la cancellation arrive après l'émission d'un resubscribe mais avant son ACK, le handle local devient terminal et ne peut plus repasser à `Active`. L'actor conserve toutefois cette request interne jusqu'à son ACK ou son timeout borné avant de poursuivre la restauration séquentielle. Lorsque l'ACK tardif fournit un nouvel ID distant, l'actor envoie un `*Unsubscribe` best-effort pour cet ID sans le publier dans le registry local. Cette attente bornée évite qu'une request stale consomme `max_pending_requests` pendant la restauration de la subscription suivante.
### Dépendances
Le runtime Transport active désormais la feature Tokio `net` parce que la session de remplacement utilise le type concret `tokio::net::TcpStream` retourné par `tokio-tungstenite`. La dépendance reste déclarée au workspace root et la canary de frontière est mise à jour. Aucun nouveau package tiers n'est ajouté.
## Tests déterministes ajoutés ou recalibrés
Les fixtures locales couvrent notamment :
- remote Close avec budget insuffisant puis terminaison `Failed` bornée ;
- deux subscriptions restaurées en ordre local stable avec remote IDs remappés ;
- replay exact des paramètres ;
- unsubscribe pendant backoff empêchant tout resubscribe ;
- unsubscribe après émission du resubscribe et ACK tardif nettoyé sans réactivation ;
- policy `Never` ;
- deux pertes distinctes démontrant le reset du budget après retour complet à `Active` ;
- progression de `continuity_gap_count` de 1 à 2 ;
- backoff exponentiel borné ;
- shutdown interrompant un backoff long ;
- erreur RPC applicative sur un resubscribe ne faisant échouer que la subscription concernée.
Les tests HTTP et les canaries de registry existants ne sont pas modifiés fonctionnellement.
## Logging et sécurité
Les nouveaux diagnostics runtime passent exclusivement par `ksp-logging-lib` avec `TRACING_TARGET = "ksp-onchain-transport-lib"`.
Les champs observables restent limités aux métadonnées sûres : session ID local, subscription ID local, nom logique d'endpoint, tentative de reconnect, compteur de gap, kind et code d'erreur sûr. Aucune URL, credential, payload arbitraire ni remote subscription ID n'est projeté dans les logs publics/snapshots.
## Validation exécutée dans le sandbox
```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
direct tracing scan runtime/tests clean
question-mark scan runtime modifié clean
Rust lines > 160 0
workspace.package.version 0.2.7-pre.7
```
Le sandbox ne fournit pas `cargo`, `rustc` ni `rustfmt`. Aucune validation Cargo de cette tranche n'est donc revendiquée ici.
## Validation opérateur requise
```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
```
## Décisions
- Le reconnect initial de `WsSession::connect` n'est pas transformé en boucle automatique dans cette tranche : la policy de reprise s'applique après acquisition réussie d'une session physique.
- Les requests applicatives en vol lors d'une rupture échouent et ne sont pas rejouées automatiquement.
- Une erreur RPC applicative pendant un resubscribe fait échouer la subscription concernée sans casser la nouvelle session physique.
- La restoration est séquentielle afin de préserver un ordre déterministe et de ne pas dépasser artificiellement `max_pending_requests`.
- Le backpressure/overflow par subscription et les canaries de leak associées restent réservés à `pre.008`.
- Les wrappers standards typed publics restent réservés aux lots `pre.009+`.
## Questions ouvertes
Aucune question bloquante pour ce checkpoint. Les décisions de backpressure per-subscription restent à matérialiser dans `pre.008` conformément au plan.