# Delta `0.2.9-pre.010` — reconnect borné + replay prudent + continuité observable ## Base ```text 0.2.9-pre.009-fix.001 gate opérateur final : fmt/audit/check/Clippy/workspace PASS sans warning Transport : 379 unit + 48 public API + 42 release-completeness + 4 doctests ``` ## Changements runtime - ajoute l'état public `YellowstoneGrpcSubscribeState::Reconnecting` ; - ajoute `YellowstoneGrpcSubscribeSnapshot` avec compteurs sûrs de reconnect/replay/continuité ; - active le `YellowstoneGrpcReconnectSettings` déjà introduit en `pre.002` pour le stream `Subscribe` ; - rouvre le stream avec un backoff exponentiel borné et un nombre d'essais borné ; - conserve le dernier `YellowstoneSubscribeRequest` complet accepté ; - après perte du stream, resoumet ce request déterministement avec un `from_slot` au moins égal au plus haut slot déjà observé ; - consulte `SubscribeReplayInfo` avant les tentatives de replay lorsque `from_slot` est disponible ; - si `first_available > from_slot`, incrémente `continuity_gap_count` et clamp le replay à `first_available` ; - maintient un cache borné de 512 identités d'updates pour observer les duplicates autour d'un replay ; - les duplicates restent livrés : aucun contrat exactly-once n'est inventé ; - rejette les mutations request pendant `Reconnecting` pour éviter une application ambiguë entre deux streams physiques ; - un shutdown pendant le backoff interrompt immédiatement la boucle et interdit une nouvelle ouverture ; - un épuisement du budget devient terminal avec un code KSP sûr `grpc_channel_failed`. ## Sémantique de continuité ```text from_slot de reconnect = max(from_slot explicite du dernier request, last_observed_slot) ReplayInfo = information de rétention ; pas preuve de replay complet continuity gap = couverture de replay indisponible, comptée seulement si first_available > slot demandé slot manquant entre deux updates filtrés = jamais assimilé automatiquement à un gap duplicate = identité KSP bornée déjà observée ; update toujours livrée exactly-once = non garanti lossless = non garanti ordre global sans gap = non garanti ``` Identités retenues pour l'observation bornée : ```text Account slot + pubkey + write_version Slot slot + status Transaction slot + signature TransactionStatus slot + signature Block slot + blockhash BlockMeta slot + blockhash Entry slot + index + hash Ping/Pong hors déduplication ``` La couverture d'une éventuelle divergence de node reste volontairement limitée : deux Block/BlockMeta de même slot mais de blockhash différent ne sont pas classés duplicate. KSP ne généralise pas cette preuve aux familles qui ne transportent pas de blockhash. ## Fixture locale | Cas | Preuve | |------------------------------------------------|-------------| | reconnect après server half-close | PASS source | | `from_slot = last_observed_slot` | PASS source | | ReplayInfo disponible | PASS source | | replay duplicate observable | PASS source | | couverture replay indisponible prouvée + clamp | PASS source | | budget reconnect épuisé | PASS source | | mutation pendant reconnect rejetée | PASS source | | shutdown pendant backoff | PASS source | | endpoint/Status secret non réémis | PASS source | | anciens gates bidi avec reconnect `0` | PASS source | ## Frontières ```text OUT pre.010 : Config V3 / PublicNode / provider facade OUT 0.2.9 : SubscribeDeshred aucune nouvelle dépendance / feature Cargo aucun yellowstone-grpc-client runtime ``` ## Validation candidate ```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 -p ksp-core-lib --test workspace_dependencies cargo test --workspace ``` Le `cargo tree` n'est pas requis : aucune dépendance ni feature n'a changé.