# Validation `0.2.7` — WebSocket Solana standard > **Statut : matrice finale validée, publiée par `0.2.7-rel.001`.** La compliance 18/18, le lifecycle borné, la composition Config V2, la non-régression HTTP, le smoke WebSocket Devnet et l’audit de dépendances sont validés. Le checkpoint opérateur post-`pre.014-fix.001` confirme le workspace avant passage au tag stable. ## 1. Baseline normative Audit officiel effectué le **22 août 2026** contre : ```text https://solana.com/docs/rpc/websocket ``` Compte exact de l'index courant : ```text 9 subscribe 9 unsubscribe 18 méthodes WebSocket totales ``` Répartition statut KSP : ```text 12 méthodes appartenant à 6 paires documentées non marquées unstable/deprecated 6 méthodes appartenant à 3 paires unstable : block, slotsUpdates, vote 0 méthode de l'index courant marquée Deprecated ``` Pour une paire unstable, l'unsubscribe associé est classé `Unstable pair` dans KSP même si sa propre page n'affiche pas nécessairement le bandeau, car il n'existe que pour annuler la subscription unstable correspondante. ## 2. Matrice exhaustive des 18 opérations | # | Méthode | Type | Statut `pre.001` | Paramètres / résultat essentiels | Notification / paire | Stratégie de test | Source officielle | Compliance | |---:|---------------------------|-------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------|-------------------| | 1 | `accountSubscribe` | subscribe | Stable/documented | pubkey ; config `commitment`, `encoding`, `dataSlice` ; result numeric id ; `minContextSlot` upstream actuellement ignoré, donc non promis | `accountNotification` | fixture encodings/config + subscribe/notify | `https://solana.com/docs/rpc/websocket/accountsubscribe` | Done `pre.009` | | 2 | `accountUnsubscribe` | unsubscribe | Stable/documented | remote id ; `true` or RPC error unknown id | account pair | handle local -> remote id fixture | `https://solana.com/docs/rpc/websocket/accountunsubscribe` | Done `pre.009` | | 3 | `blockSubscribe` | subscribe | **Unstable** | `all`/mentions filter ; confirmed/finalized ; encoding ; tx details ; max tx version ; showRewards | `blockNotification` | all options + null block/error + validator capability fixture | `https://solana.com/docs/rpc/websocket/blocksubscribe` | Done `pre.011` | | 4 | `blockUnsubscribe` | unsubscribe | **Unstable pair** | remote id ; boolean/error | block pair | generic registry unsubscribe | `https://solana.com/docs/rpc/websocket/blockunsubscribe` | Done `pre.011` | | 5 | `logsSubscribe` | subscribe | Stable/documented | `all`, `allWithVotes`, exactly one `mentions`; commitment | `logsNotification` | 3 filters + invalid multi-mention + notification | `https://solana.com/docs/rpc/websocket/logssubscribe` | Done `pre.009` | | 6 | `logsUnsubscribe` | unsubscribe | Stable/documented | remote id ; boolean/error | logs pair | generic registry unsubscribe | `https://solana.com/docs/rpc/websocket/logsunsubscribe` | Done `pre.009` | | 7 | `programSubscribe` | subscribe | Stable/documented | program pubkey ; commitment ; filters ; encoding ; dataSlice ; `withContext` | `programNotification` | contexted/non-contexted fixtures + filters | `https://solana.com/docs/rpc/websocket/programsubscribe` | Done `pre.009` | | 8 | `programUnsubscribe` | unsubscribe | Stable/documented | remote id ; boolean/error | program pair | generic registry unsubscribe | `https://solana.com/docs/rpc/websocket/programunsubscribe` | Done `pre.009` | | 9 | `rootSubscribe` | subscribe | Stable/documented | no params ; numeric id | `rootNotification` => `u64` | exact root fixture | `https://solana.com/docs/rpc/websocket/rootsubscribe` | Done `pre.010` | | 10 | `rootUnsubscribe` | unsubscribe | Stable/documented | remote id ; boolean/error | root pair | generic registry unsubscribe | `https://solana.com/docs/rpc/websocket/rootunsubscribe` | Done `pre.010` | | 11 | `signatureSubscribe` | subscribe | Stable/documented | first transaction signature ; commitment ; `enableReceivedNotification` | `signatureNotification` early string or terminal error object | early + terminal + auto-close/no-resubscribe | `https://solana.com/docs/rpc/websocket/signaturesubscribe` | Done `pre.010` | | 12 | `signatureUnsubscribe` | unsubscribe | Stable/documented | remote id before terminal fire ; boolean/error | signature pair | cancel before terminal + stale after terminal | `https://solana.com/docs/rpc/websocket/signatureunsubscribe` | Done `pre.010` | | 13 | `slotSubscribe` | subscribe | Stable/documented | no params ; numeric id | `slotNotification` `{slot,parent,root}` | exact fixture + live smoke Devnet passé | `https://solana.com/docs/rpc/websocket/slotsubscribe` | Done `pre.010` | | 14 | `slotUnsubscribe` | unsubscribe | Stable/documented | remote id ; boolean/error | slot pair | generic registry unsubscribe | `https://solana.com/docs/rpc/websocket/slotunsubscribe` | Done `pre.010` | | 15 | `slotsUpdatesSubscribe` | subscribe | **Unstable** | no params ; numeric id | tagged `slotsUpdatesNotification` | each known variant + unknown fallback | `https://solana.com/docs/rpc/websocket/slotsupdatessubscribe` | Done `pre.011` | | 16 | `slotsUpdatesUnsubscribe` | unsubscribe | **Unstable pair** | remote id ; boolean/error | slotsUpdates pair | generic registry unsubscribe | `https://solana.com/docs/rpc/websocket/slotsupdatesunsubscribe` | Done `pre.011` | | 17 | `voteSubscribe` | subscribe | **Unstable** | no params ; validator flag required | `voteNotification` | fields + timestamp omitted/null/value + warning | `https://solana.com/docs/rpc/websocket/votesubscribe` | Done `pre.011` | | 18 | `voteUnsubscribe` | unsubscribe | **Unstable pair** | remote id ; boolean/error | vote pair | generic registry unsubscribe | `https://solana.com/docs/rpc/websocket/voteunsubscribe` | Done `pre.011` | ## 3. Notification matrix | Subscribe | Notification | Shape à préserver | Point lossless / lifecycle | |-------------------------|----------------------------|------------------------------------------------|--------------------------------------------------------------------------------------------------| | `accountSubscribe` | `accountNotification` | contextual account payload | reuse account DTOs/encodings/dataSlice | | `programSubscribe` | `programNotification` | keyed account, documenté avec contexte | accepter contexted/non-contexted à cause de l'écart docs/source audité ; préserver `withContext` | | `logsSubscribe` | `logsNotification` | context + `{signature, err, logs}` | err nullable ; filtre mentions exactement une adresse | | `signatureSubscribe` | `signatureNotification` | context + `"receivedSignature"` **ou** `{err}` | terminal object clôt la subscription ; early string ne la clôt pas | | `slotSubscribe` | `slotNotification` | `{slot,parent,root}` | non-contextual | | `rootSubscribe` | `rootNotification` | `u64` | non-contextual | | `blockSubscribe` | `blockNotification` | context + `{slot, block, err}` | unstable ; `block`/`err` nullable ; variants block selon config | | `slotsUpdatesSubscribe` | `slotsUpdatesNotification` | tagged union slot lifecycle | unstable ; fallback unknown/raw borné | | `voteSubscribe` | `voteNotification` | `{votePubkey,slots,hash,timestamp,signature}` | unstable/pre-consensus ; timestamp tolerant wire | Les contexts WebSocket documentés omettent `apiVersion`. `SolanaRpcContext.api_version: Option` est compatible avec cette omission. ## 4. Baselines Git Agave et SIMD ### 4.1 Hiérarchie de contrôle La compliance applique le même modèle que la compliance HTTP finale : ```text documentation Solana actuelle -> surface publique annoncée Git Agave tag courant audité v4.2.1 -> implémentation réelle et types wire SIMD main -> évolutions Activated, Review, Draft ou Idea à surveiller ``` Les pages Solana peuvent lier une révision Agave plus ancienne. KSP ne confond donc pas le lien source embarqué dans une page documentaire avec la baseline Git courante. Au 2026-08-22, **Agave `v4.2.1`** est disponible sur Git et devient la baseline d'implémentation de `0.2.7`. Sources Agave ciblées : ```text https://github.com/anza-xyz/agave/blob/v4.2.1/rpc/src/rpc_pubsub.rs https://github.com/anza-xyz/agave/blob/v4.2.1/rpc/src/rpc_subscriptions.rs https://github.com/anza-xyz/agave/blob/v4.2.1/rpc-client-types/src/response.rs ``` ### 4.2 Constats Agave `v4.2.1` Le cross-check du tag courant confirme les **9 subscribe + 9 unsubscribe** déjà inventoriés par la documentation publique et ne révèle aucune opération PubSub standard supplémentaire. Constats ciblés : - `accountSubscribe` : le type partagé `RpcAccountInfoConfig` expose `min_context_slot`, mais le handler PubSub `v4.2.1` le destructure en `_ // ignored`. KSP ne le compte donc pas comme option WebSocket effective ; - `programSubscribe` : `RpcProgramAccountsConfig.with_context` est lu et doit rester dans le contrat KSP ; la tolérance contexted/non-contexted reste requise pour ne pas convertir les divergences docs/source/provider en perte wire ; - `logsSubscribe` : le handler accepte `all`, `allWithVotes` ou `mentions` et rejette un filtre `mentions` contenant autre chose qu'exactement une adresse ; - `signatureSubscribe` : le handler utilise la signature, `commitment` et `enable_received_notification`, sans option WebSocket supplémentaire ; - `blockSubscribe` : le handler consomme le jeu d'options documenté et impose un commitment au moins `confirmed` ; - `*Unsubscribe` : un ID serveur inconnu produit `InvalidParams` dans le handler audité ; - `voteSubscribe` : `RpcVote.timestamp` est `Option`, donc optionnel au niveau wire ; - `slotsUpdatesSubscribe` : `SlotUpdate` possède toujours les sept variantes courantes `FirstShredReceived`, `Completed`, `CreatedBank`, `Frozen`, `Dead`, `OptimisticConfirmation` et `Root`. Décisions compliance : transmettre `programSubscribe.withContext`, accepter les formes contextée/non-contextée, ne pas promettre `accountSubscribe.minContextSlot` tant qu'il est ignoré upstream, conserver `vote.timestamp` optionnel et garder un fallback borné pour toute future variante `SlotUpdate` inconnue. ### 4.3 Audit SIMD ciblé Aucun SIMD audité n'ajoute actuellement une dixième famille de subscription standard à la surface Agave `v4.2.1`. Ils modifient en revanche des hypothèses que KSP doit éviter de figer. | SIMD | Statut au 2026-08-22 | Conséquence WebSocket KSP | |-----------------------------------------------|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------| | `0118` Partitioned Epoch Rewards Distribution | Activated | conserver les DTOs bloc/rewards HTTP déjà lossless, dont `numRewardPartitions` omitted, `null` ou valeur | | `0291` Commission Rate in Basis Points | Review | conserver les représentations de commission upstream indépendantes lorsqu'elles sont présentes | | `0296` Larger Transaction Size | Review | proposition jusqu'à 4096 octets : ne pas déduire les limites WS de l'ancienne taille transaction 1232 ; surveiller `blockSubscribe` | | `0298` Bank Hash in Block Footer | Idea | aucun `bankHash` spéculatif dans le wire courant ; surveiller le DTO bloc partagé | | `0301` parent bank hash | PR fermé, non mergé | aucun `parentBankHash` spéculatif ; même conclusion que la compliance HTTP | | `0307` Add Block Footer | Review | aucun `footer` spéculatif ; réauditer `blockSubscribe` lorsque l'upstream l'expose réellement | | `0326` Alpenglow | Review | ne pas figer les sémantiques TowerBFT de `voteSubscribe` ou `optimisticConfirmation` dans un contrat durable | | `0337` Alpenglow Fast Leader Handover Markers | Review | nouveaux block markers futurs : surveiller shape et taille, sans ajout WS anticipé | | `0384` Alpenglow migration | Review | des notifications RPC commitment/optimistic confirmation peuvent être suspendues pendant migration ; aucune hypothèse de séquence exhaustive | | `0385` Transaction V1 | Review | `maxSupportedTransactionVersion` et les versions transaction restent génériques ; ne pas caper KSP à v0 | Sources SIMD : ```text https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0118-partitioned-epoch-reward-distribution.md https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0291-commission-rate-in-basis-points.md https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0296-larger-transactions.md https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0298-bank-hash-in-block-footer.md https://github.com/solana-foundation/solana-improvement-documents/pull/301 https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0307-add-block-footer.md https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0326-alpenglow.md https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0337-parent-ready-update-marker.md https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0384-alpenglow-migration.md https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md ``` Les limites WebSocket sont donc des bornes KSP configurables et finies, jamais une transposition en dur de l'ancienne limite transaction de 1232 octets. ## 5. Méthodes unstable ### `blockSubscribe` Conditions officielles : ```text --rpc-pubsub-enable-block-subscription --enable-rpc-transaction-history ``` KSP : warning centralisé à la création, test fixture toujours disponible, smoke live non requis. ### `slotsUpdatesSubscribe` Le format est explicitement annoncé comme susceptible de changer. Variants actuels : ```text firstShredReceived slot,timestamp completed slot,timestamp createdBank slot,parent,timestamp frozen slot,timestamp,stats dead slot,timestamp,err optimisticConfirmation slot,timestamp root slot,timestamp ``` `stats` actuel : `numTransactionEntries`, `numSuccessfulTransactions`, `numFailedTransactions`, `maxTransactionsPerEntry`. ### `voteSubscribe` Condition officielle : ```text --rpc-pubsub-enable-vote-subscription ``` Les votes observés sont gossip/pre-consensus ; aucune garantie d'entrée dans le ledger. Transport les livre comme wire, sans interprétation métier. ## 6. Lifecycle compliance initiale | Contrat | Décision `pre.001` | Gate cible | |-------------------------------|-------------------------------------------------------------------|--------------------------------------------| | plusieurs sessions / même URL | création physique explicite ; aucun singleton/pool automatique | **Done `pre.004`**, canary final `pre.012` | | plusieurs subs / session | registry actor par session | **Done `pre.006`** | | ID public subscription | local KSP stable | **Done `pre.006`** | | ID serveur | éphémère interne et remappé | **Done `pre.006` puis `pre.007`** | | session states | Disconnected/Connecting/Active/Reconnecting/Closing/Closed/Failed | **Done `pre.002`** | | subscription states | Requested/Active/Resubscribing/Cancelling/Closed/Failed | **Done `pre.002`** | | reconnect | physique uniquement, budget/backoff finis | **Done `pre.007`** | | resubscribe | policy `Never` ou `ActiveSubscriptions`, ordre local déterministe | **Done `pre.007`** | | continuity | gap observable, aucune promesse lossless | **Done `pre.007`** | | backpressure | queue par sub bounded ; overflow => fail local explicite | **Done `pre.008`** | | shutdown | explicite, bounded, annule reconnect et subscriptions | **Done `pre.005`** | | keepalive | pas de ping applicatif périodique sans besoin démontré | **Done `pre.005`** | ## 7. Threat/security compliance initiale | Invariant | Preuve attendue | Statut | |---------------------------------------------------|----------------------------------------------|--------------------------------------------------------| | URL/credentials absents de `Debug` | unit tests URL wrapper | **Done `pre.002`** | | URL/credentials absents des erreurs | validation URL + connection errors safe | **Done through `pre.004`** | | URL/credentials absents des logs | actor logs only safe endpoint metadata | **Done through `pre.011`, source audit `pre.013`** | | snapshots sans URL/raw payload | unit shape + public contract | **Done `pre.002`** | | frame/message finis | settings bornés + `WebSocketConfig` raccordé | **Done `pre.005`** | | JSON borné indirectement par message | oversized + malformed fixture | **Done `pre.005`** | | queues notifications bornées | queue typed bornée + overflow isolé | **Done `pre.008`** | | pending RPC borné + timeout | map actor bornée + timeout request | **Done `pre.004`** | | reconnect loop bornée | repeated disconnect fixture | Done `pre.007` | | unsubscribe pendant reconnect ne resubscribe pas | race fixture | Done `pre.007` | | signature terminale ne resubscribe pas | terminal fixture | **Done `pre.010`** | | shutdown ne bloque pas | peer hostile/no close ack fixture | **Done `pre.005`** | | no Store/Program/Wallet/Config dep dans Transport | cargo tree + source canary | **Done `pre.013`, source + cargo tree opérateur** | | no direct `tracing` dans Transport | workspace audit + source audit | **Done `pre.002`** | ## 8. Dependency compliance initiale Candidates auditées à l'ouverture de release : ```text tokio-tungstenite 0.30.x futures-util 0.3.x ``` Le workspace conserve des contraintes caret `^0.30` et `^0.3` sans lockfile versionné. La version transitive effectivement résolue n'est donc pas figée dans cette matrice ; `pre.013` exige l'inspection du `cargo tree` opérateur correspondant au checkout validé. Features retenues : ```text tokio-tungstenite: default-features=false + connect + rustls-tls-webpki-roots futures-util: default-features=false + std + sink ``` Sources : ```text https://docs.rs/crate/tokio-tungstenite/0.30.0 https://docs.rs/crate/tokio-tungstenite/0.30.0/features https://docs.rs/crate/futures-util/0.3.34 ``` Alternatives auditées mais non retenues : `tokio-websockets 0.13.3`, `fastwebsockets 0.10.0`. `pre.004` matérialise `tokio-tungstenite` et `futures-util` dans `[workspace.dependencies]` sans features consumer au root. Transport active seulement `connect`, `rustls-tls-webpki-roots`, `std` et `sink`; `tokio/net` est ajouté côté dev fixture local. ## 9. Config compliance initiale V1 hérité : HTTP-only strict. Contrat de compatibilité : ```text V1 -> support de lecture conservé, WS vide V2 -> HTTP existant + ws_defaults + profiles[].ws_endpoints profiles[].ws_endpoints[].kind -> discriminateur de famille/protocole WS 0.2.7 -> seule valeur supportée : solana_standard Config -> WsTransportSettings Transport -X-> Config ``` Le type Transport correspondant au discriminateur est prévu `#[non_exhaustive]`. L'objectif est de pouvoir ajouter ultérieurement une famille telle que Helius Enhanced WebSocket sans créer un nouveau conteneur Config ni injecter des options provider-specific dans les settings Solana standard. Toute valeur inconnue reste explicitement rejetée en `0.2.7`; aucun paramètre Helius n'est implémenté par anticipation. `pre.003` remplace le schema enregistré par `urn:ksp:schema:std.transport:v2`, avec deux branches strictes : V1 HTTP-only et V2 HTTP + WebSocket. La compatibilité V1 ne relâche donc ni `additionalProperties`, ni la shape historique. Preuves `pre.003` : ```text config/std.transport.json format_version = 2 config/examples/std.transport.example.json format_version = 2 V1 fixture dédiée load + HTTP mapping OK attendu V1 ws_settings = None V2 ws_settings = Some(validated) ws_defaults globals profiles[].ws_endpoints profile-local ws_endpoints[].kind enum schema solana_standard ws_endpoints[].session overrides génériques optionnels Config -> Transport seule direction de dépendance ``` `ResolvedTransportConfig::settings()` reste l'accesseur HTTP historique. `http_settings()` l'explicite et `ws_settings()` expose `Option<&WsTransportSettings>` afin que le V1 backward ne force jamais un faux `WsTransportSettings` vide. `into_transport_settings()` permet de consommer les deux contrats ensemble. ## 9.1 Checkpoint settings/lifecycle `pre.002` Surface publique matérialisée : ```text WsEndpointUrl WsProviderName WsClusterName WsProtocolKind::SolanaStandard WsReconnectSettings WsResubscribePolicy::{Never, ActiveSubscriptions} WsSessionSettings WsEndpointSettings WsTransportSettings WsSessionId WsSubscriptionId WsSessionState WsSubscriptionState WsSubscriptionKind WsSessionSnapshot WsSubscriptionSnapshot ``` Gates déterministes ajoutés : ```text ws:// et wss:// acceptés HTTP rejeté par WsEndpointUrl Debug URL redacted erreur de scheme sans URL/credential settings session default bornés zero runtime bound rejeté reconnect backoff inversé rejeté endpoint names uniques au moins un endpoint enabled Debug WsTransportSettings sans URL/credential 9 familles standard WsSubscriptionKind snapshots sans URL ni remote subscription id public API canary crate-root ``` Logging : `TRACING_TARGET` reste défini dans `constants.rs`; les nouveaux événements utilisent exclusivement `ksp_logging_lib::trace!`, `debug!` et `warn!` avec des fields sûrs. Aucun `tracing` direct n'est ajouté. Les defaults de taille/queue sont des **policies KSP locales**, pas des limites Solana. Leur enforcement réel et leurs tests oversized/slow-consumer restent attendus dans les tranches socket/backpressure. ## 9.2 Checkpoint runtime physique `pre.004` Surface matérialisée : ```text WsSession::connect(endpoint) public, une connexion physique par appel actor socket propriétaire exclusif du WebSocket command queue tokio mpsc bounded pending JSON-RPC BTreeMap bounded par max_pending_requests request timeout command_timeout response dispatch par id numérique KSP state snapshot watch + WsSessionSnapshot raw JSON-RPC public non, primitive pub(crate) reconnect/resubscribe non, gates futurs ``` Fixtures déterministes `pre.004` : ```text handshake local + JSON-RPC round-trip deux sessions physiques distinctes sur la même URL deux requests concurrentes + réponses inversées erreur RPC applicative sans teardown de session erreur de connexion sans URL/credential dans Error Debug ``` Les limites `max_message_size`, `max_frame_size` et `max_write_buffer_size` sont raccordées à `tungstenite::WebSocketConfig`. Les fixtures oversized et le shutdown/control-frame hostile restent explicitement `pre.005`. ## 9.3 Checkpoint limits/control/shutdown `pre.005` Gates déterministes ajoutés : ```text max_pending_requests saturé -> seule la request excédentaire est rejetée outbound JSON-RPC > borne message/frame -> rejet avant socket write, session Active inbound frame/message oversized -> rejet Tungstenite avant parse JSON ; depuis pre.007, reconnect borné pending request silencieuse -> timeout + purge de capacité, session Active Ping distant -> Pong automatique flushé, session Active Close distant propre -> checkpoint `pre.005` Closed ; à partir de `pre.007`, perte physique reprise par reconnect borné WsSession::close() -> Closing -> Closed close() annule les pending requests avec ws_session_closed peer hostile qui ne répond pas au Close -> shutdown borné cycles connect/close répétés -> terminaison bornée ``` Le signal de shutdown est indépendant de la command queue et les opérations socket longues de l'actor surveillent ce signal. Aucun reconnect/resubscribe n'est activé par cette tranche. ## 9.4 Checkpoint registry subscriptions `pre.006` Surface matérialisée : ```text WsSubscription handle public typed WsSubscriptionId local stable, actor-assigned remote subscription id interne/transient uniquement registry local BTreeMap ordonnée par local ID remote -> local mapping actor-owned subscribe generic crate-private, wrappers publics futurs unsubscribe handle public, bool Solana préservé notification queue typed + bounded reconnect/resubscribe non à ce checkpoint ; matérialisé en pre.007 backpressure adversarial complet oui, pre.008 ``` Gates déterministes ajoutés : ```text subscribe ACK -> binding remote/local atomique notification -> bonne subscription typed 2 familles -> IDs locaux 1 puis 2, remote IDs indépendants unknown/stale remote ID -> safe drop, session Active notification method mismatch -> subscription Failed seulement typed decoder failure -> erreur livrée + subscription Failed seulement unsubscribe -> exact *Unsubscribe avec remote ID interne unsubscribe bool -> préservé au caller snapshot -> local IDs + remote_bound uniquement ``` Le registry est détenu par le même actor que le socket et la pending map JSON-RPC. Aucun caller ne manipule le socket ni le remote ID. Les wrappers publics `accountSubscribe`, `programSubscribe`, etc. restent volontairement différés aux lots `pre.009+` afin de ne pas exposer une API raw provider-extension intermédiaire. ## 9.5 Checkpoint reconnect/resubscribe `pre.007` Surface matérialisée : ```text reconnect triggers EOF, Close distant inattendu, I/O/TLS/WebSocket/protocole structurel reconnect budget fini, configurable backoff exponentiel borné, sans jitter shutdown pendant reconnect prioritaire, annule backoff/handshake continuity_gap_count incrémenté une fois par perte de continuité logique remote IDs après perte invalidés immédiatement ActiveSubscriptions resubscribe local-ID croissant Never aucune restauration automatique budget reset seulement après retour complet à Active unsubscribe pendant reconnect cancellation locale gagnante ACK resubscribe tardif cleanup distant best-effort, aucune réactivation locale backfill HTTP aucun lossless guarantee aucune ``` Gates déterministes ajoutés : ```text remote Close + budget insuffisant -> Reconnecting puis Failed, jamais boucle infinie 2 subscriptions -> resubscribe dans l'ordre WsSubscriptionId 1 puis 2 remote IDs 101/202 -> remappés 301/302 sans changer les IDs locaux params de subscribe -> conservés et rejoués à l'identique unsubscribe pendant backoff -> sub Closed et absente de la sélection resubscribe unsubscribe après émission resubscribe -> ACK tardif nettoyé par *Unsubscribe best-effort policy Never -> session reconnectée Active, subscription terminale Failed 2 pertes séparées avec max_retries=1 -> deux recoveries possibles, budget réinitialisé après Active continuity_gap_count -> 1 puis 2 sur deux pertes distinctes ``` Les requests applicatives en vol au moment d'une perte physique échouent ; Transport ne les rejoue pas implicitement. La restauration ne promet aucune continuité lossless et n'effectue aucun backfill HTTP. Les remote subscription IDs restent strictement internes et sont remplacés à chaque ACK de resubscribe. ## 9.6 Checkpoint backpressure/lifecycle `pre.008` Surface matérialisée : ```text notification_queue_capacity borne effective par WsSubscription overflow_count compteur session saturating, réellement incrémenté terminal_error_code ErrorCode KSP sûr sur le handle Failed queue pleine fail local uniquement, aucun drop silencieux remote cleanup *Unsubscribe best-effort après overflow/receiver drop/erreur locale max_active_subscriptions admission bornée, capacité réutilisable après terminaison receiver abandonné détecté sur notification suivante, registry libéré reconnect après overflow subscription Failed absente de toute restauration future ``` Gates déterministes ajoutés : ```text queue capacité 1 + 2 notifications -> overflow_count 1 + ws_backpressure_overflow subscription lente en overflow -> Failed, première notification déjà queueée reste lisible puis channel fermé subscription saine parallèle -> notification livrée et état Active session après overflow isolé -> Active binding distant overflow -> *Unsubscribe best-effort exact max_active_subscriptions atteint -> création excédentaire rejetée sans incrémenter overflow_count unsubscribe terminal -> capacité locale libérée puis nouvelle subscription acceptée receiver droppé -> notification suivante déclenche cleanup distant et libère la capacité method mismatch -> ws_protocol_error terminal sur le handle, session Active decode typed invalide -> code invalid_response terminal sur le handle, session Active policy Never après perte physique -> ws_connection_failed terminal sur le handle resubscribe RPC application error -> rpc_application_error terminal sur le handle ``` `overflow_count` et `continuity_gap_count` sont deux signaux distincts : le premier mesure les saturations locales de consumers, le second les ruptures de continuité physique. Aucun des deux ne déclenche de replay ou de backfill automatique. Les causes terminales n'exposent qu'un `ErrorCode` KSP, jamais le payload distant, le remote subscription ID ou l'URL. ## 9.7 Checkpoint wrappers stables lot A `pre.009` Surface publique ajoutée : ```text WsSession::account_subscribe SolanaAccountSubscribeConfig WsSubscription> WsSession::program_subscribe SolanaProgramSubscribeConfig SolanaProgramNotification = bare keyed account | contextual keyed account WsSession::logs_subscribe SolanaLogsSubscribeFilter = All | AllWithVotes | Mentions(Pubkey) SolanaLogsNotification = signature + err nullable + logs ordonnés ``` Gates déterministes ajoutés : ```text accountSubscribe -> params exacts pubkey + encoding/dataSlice/commitment accountSubscribe -> aucun minContextSlot promis ou sérialisable par le config WS accountNotification -> SolanaRpcContext + SolanaAccount partagé account handle unsubscribe -> accountUnsubscribe avec remote ID interne programSubscribe -> filters + withContext préservés, sortResults absent program filters -> >4 et raw memcmp >128 rejetés avant émission subscribe programNotification -> forme bare acceptée programNotification -> forme contextualisée acceptée program handle unsubscribe -> programUnsubscribe exact logs filter -> all/allWithVotes/mentions exactement une pubkey logsNotification -> context + signature + err null/object + ordre logs préservés logsNotification sans champ err requis -> invalid_response typed, session non concernée logs handle unsubscribe -> logsUnsubscribe exact API publique -> aucun subscribe(method, raw params) exposé ``` Les trois wrappers passent par le même moteur actor/registry acquis en `pre.006`–`pre.008`; les remote IDs ne deviennent donc pas publics et les paramètres typés initiaux restent les specs rejouées lors d'un resubscribe `ActiveSubscriptions`. ## 9.8 Checkpoint wrappers stables lot B `pre.010` Surface publique ajoutée : ```text WsSession::signature_subscribe SolanaSignatureSubscribeConfig = commitment + enableReceivedNotification SolanaSignatureNotification = ReceivedSignature | Processed { err } WsSession::slot_subscribe SolanaSlotNotification = slot + parent + root WsSession::root_subscribe notification = u64 ``` Gates déterministes ajoutés : ```text signatureSubscribe -> signature + config commitment/enableReceivedNotification exacts config signature vide explicite -> second paramètre omis receivedSignature -> notification typed non terminale, handle reste Active Processed { err:null } -> notification terminale success, handle Closed sans terminal_error_code Processed { err:object } -> transaction error wire préservée comme valeur terminale, pas erreur Transport string signature inconnue ou objet sans err -> invalid_response typed cancellation avant terminal -> signatureUnsubscribe exact avec remote ID interne terminal déjà observé -> unsubscribe local-only false, aucun remote cleanup inutile perte physique après terminal -> reconnect session possible mais signature jamais resubscribe slotSubscribe -> params vides + {slot,parent,root} exact slotUnsubscribe -> remote ID interne via handle rootSubscribe -> params vides + root u64 exact rootUnsubscribe -> remote ID interne via handle API publique -> aucun remote subscription ID ni méthode raw arbitraire exposés ``` La terminaison signature est classée après livraison dans la queue typed : le consumer reçoit donc toujours la valeur terminale avant fermeture du canal. Le registry retire ensuite la subscription one-shot avant toute future sélection de resubscribe. `slot` et `root` restent des subscriptions continues et conservent les règles génériques de reconnect, backpressure et cancellation. ## 9.9 Checkpoint familles unstable `pre.011` Surface publique ajoutée : ```text WsSession::block_subscribe SolanaBlockSubscribeFilter = All | MentionsAccountOrProgram(Pubkey) SolanaBlockSubscribeConfig = commitment + encoding + transactionDetails + maxSupportedTransactionVersion + showRewards SolanaBlockNotification = slot + block nullable + err nullable WsSession::slots_updates_subscribe SolanaSlotUpdate = 7 variantes connues + Unknown raw borné SolanaSlotUpdateStats WsSession::vote_subscribe SolanaVoteNotification = votePubkey + slots + hash + timestamp optionnel + signature ``` Gates déterministes ajoutés : ```text partition unstable exacte = block + slotsUpdates + vote warning centralisé dans le moteur typed avant création d'une famille unstable block filter all + mentionsAccountOrProgram block config -> confirmed/finalized seulement ; processed rejeté avant I/O block config -> binary/base58/base64/json/jsonParsed + full/accounts/signatures/none block maxSupportedTransactionVersion -> valeur numérique générique, pas de hardcode 0 block notification -> context + slot + block nullable + err nullable block payload > 1232 octets -> accepté tant qu'il reste sous les limites WS KSP block capability RPC error -> erreur applicative caller, session physique reste Active blockUnsubscribe -> remote ID interne via handle slotsUpdates -> 7 variantes actuelles exactes slotsUpdates future type -> Unknown avec raw conservé sous la borne message createdBank/frozen/dead -> champs spécifiques requis slotsUpdatesSubscribe/Unsubscribe -> params vides + remote ID interne vote timestamp omitted/null/value -> None/None/Some(i64) votePubkey -> Pubkey typed ; slots ordonnés ; hash/signature opaques voteSubscribe/Unsubscribe -> params vides + remote ID interne ``` La protection `Unknown` de `slotsUpdates` est volontairement limitée aux **nouvelles variantes de type**. Une variante connue avec des champs obligatoires invalides reste `invalid_response` pour la subscription concernée : KSP ne masque pas une rupture du contrat déjà audité. Le warning unstable ne contient ni filtre, ni pubkey, ni payload, ni remote subscription ID, ni URL. Les trois familles restent continues et réutilisent le lifecycle générique reconnect/resubscribe/backpressure/cancellation. Comptages attendus après compilation : ```text Transport unit tests = 309 Transport public API tests = 35 release completeness = 22 ``` ## 9.10 Checkpoint compliance consolidée `pre.012` `pre.012` n'ajoute aucune nouvelle famille WebSocket. Il transforme les preuves acquises de `pre.002` à `pre.011` en gates de release explicites et vérifie simultanément que la surface HTTP héritée reste intacte. Le réaudit de l'index public Solana effectué le **23 août 2026** confirme que la surface courante reste inchangée à 9 subscribe + 9 unsubscribe. Gates ajoutés ou consolidés : ```text 9 WsSubscriptionKind publics exactement couverts 9 wrappers subscribe publics disponibles depuis la crate root 1 handle WsSubscription::unsubscribe commun aux 9 familles 9 paires subscribe/unsubscribe = 18 opérations standard comptabilisées triplets exacts subscribe/unsubscribe/notification toujours verrouillés par le canari ws_lifecycle partition unstable exacte toujours verrouillée : block + slotsUpdates + vote Config V2 committed -> ResolvedTransportConfig -> WsTransportSettings validé WsEndpointSettings produit par Config -> type accepté directement par WsSession::connect Config V1 -> HTTP-only reste couvert par le checkpoint pre.003 HTTP courant -> 52 méthodes Supported HTTP historique -> 14 méthodes Removed surface API publique WebSocket complète -> canari crate-root dédié ``` La composition Config est testée sans réseau : l'async future `WsSession::connect` est construite à partir de l'endpoint produit par Config et laissée non pollée jusqu'à sa destruction en fin de portée. Le gate vérifie donc la compatibilité de types et de contrats entre Config et Transport sans transformer `pre.012` en smoke live ; le vrai smoke reste réservé à `pre.013`. Comptages attendus après compilation : ```text Transport unit tests = 309 Transport public API tests = 36 Transport release completeness = 24 Config unit tests = 109 ``` ## 9.11 Checkpoint smoke live et audit de dépendances `pre.013` Le smoke WebSocket live retenu est un test Transport pur `#[ignore]` qui ne lit ni Config ni environnement : ```text endpoint programmatique wss://api.devnet.solana.com WsSession::connect slotSubscribe stable attente bornée d'une slotNotification slot > 0 slotUnsubscribe via handle close explicite de la session ``` Les familles `block`, `slotsUpdates` et `vote` ne sont pas utilisées dans ce smoke car leur disponibilité dépend de capabilities validator unstable. Les fixtures locales restent les preuves reproductibles de ces familles et du lifecycle. Le canari workspace de dépendances verrouille désormais les noms directs de Transport : ```text runtime : futures-util, ksp-core-lib, ksp-logging-lib, reqwest, serde, serde_json, tokio, tokio-tungstenite dev : tokio ``` Il complète le firewall existant contre Config/Store/Program/tracing direct. L'inspection du graphe résolu reste une preuve opérateur avec : ```bash cargo tree -p ksp-onchain-transport-lib cargo tree -p ksp-onchain-transport-lib --duplicates cargo tree --duplicates ``` Une duplication transitive n'est pas considérée automatiquement comme un défaut : elle doit être comprise et n'est supprimée que si KSP peut la résoudre sans downgrade, pin artificiel ou violation des responsabilités upstream. Comptages déterministes attendus inchangés côté Transport, plus un smoke live ignoré et un canari workspace supplémentaire dans Core : ```text Transport unit tests = 309 Transport public API tests = 36 Transport release completeness = 24 Transport WebSocket live smoke = 1 ignored par défaut Config unit tests = 109 Core workspace dependency tests = 3 ``` Preuve opérateur `pre.013` : tous les contrôles déterministes ci-dessus sont verts, `cargo test --workspace` est vert et le smoke WebSocket ignoré a été exécuté explicitement avec succès sur Devnet. L'audit du graphe Transport résout notamment `reqwest 0.13.4`, `tokio 1.53.1`, `tokio-tungstenite 0.30.0` et `futures-util 0.3.34` sur ce checkout sans lockfile versionné. Les doublons `syn` 2/3 et `webpki-roots` 0.26/1.0 sont transitifs/upstream et acceptés ; aucune unification artificielle n'est requise. ## 10. Validation du gate `pre.001` Exécuté dans le sandbox : ```text archive stable v0.2.6 vérifiée OK lecture règles/architecture/plans/validation OK inventaire Transport/Config réel OK archive bot3 WebSocket auditée OK audit docs officielles WebSocket OK cross-check Git Agave v4.2.1 OK audit SIMD WebSocket ciblé OK inventaire exact 18 = 9+9 OK audit dependencies candidates OK state machines / reconnect / resubscribe DECIDED backpressure / cancellation / shutdown / secrets DECIDED shape Config V2 + discriminateur famille WS DECIDED plan/sizing OK python3 scripts/audit_rust_workspace_rules.py baseline OK ``` Tenté mais non exécutable dans le sandbox : ```text cargo fmt --all cargo absent cargo check --workspace cargo absent cargo clippy --workspace --all-targets cargo absent ``` La matrice ne considère donc pas `pre.001` techniquement validé par Cargo tant que l'opérateur n'a pas exécuté ces gates sur son checkout. ## 11. Critères finaux à transformer en preuves Avant `rel.001`, cette matrice doit obtenir : ```text 18/18 méthodes official-index accounted (**Done `pre.012`**) 9/9 subscribe wrappers public typed (**Done `pre.012`**) 9/9 unsubscribe couverts par handles/registry (**Done `pre.012`**) 0 option officielle perdue 0 fuite URL/credential N sessions same URL prouvé N subscriptions same session prouvé reconnect/resubscribe/backpressure/shutdown gates verts unstable warnings centralisés HTTP 52+14 non régressé (**Done `pre.012`**) Config V1 backward + V2 WS validés (**Done `pre.003`, revalidé opérateur `pre.013`**) smoke live opt-in documenté (**Done `pre.013`**) cargo tree inspecté (**Done opérateur `pre.013`**) cargo test --workspace vert (**Done opérateur `pre.013`**) README/USAGE synchronisés et réaudités durables (**Done `pre.014`**) prompt 0.2.8 préparé (**Done `pre.014`**) ``` ## 12. Clôture `0.2.7-rel.001` Preuve opérateur finale fournie le **23 août 2026** après `0.2.7-pre.014-fix.001` : ```text cargo fmt --all PASS python3 scripts/audit_rust_workspace_rules.py PASS — audits général/export/workspace clean cargo check --workspace PASS cargo clippy --workspace --all-targets PASS cargo test -p ksp-onchain-transport-lib PASS unit 309/309 public API 36/36 release completeness 24/24 HTTP Devnet smoke ignored par défaut WebSocket Devnet smoke ignored par défaut cargo test --workspace PASS ``` Le workspace complet couvre également les canaries Config, Core, Logging, Wallet et les deux Desks. Les tests explicitement operator-only/diagnostic/live restent ignorés par défaut conformément à leur contrat. Aucun échec n'est observé. Le smoke WebSocket Devnet explicite de `pre.013` reste la preuve live retenue : `slotSubscribe -> slotNotification -> unsubscribe -> close`. Les changements ultérieurs `pre.014` et `pre.014-fix.001` sont documentaires/versionnés ou documentaires uniquement et ne touchent pas le runtime WebSocket ni le graphe de dépendances. Verdict final : ```text 18/18 WebSocket standard accounted PASS 9/9 wrappers subscribe publics typés PASS 9/9 unsubscribe via handle/registry PASS KSP-TRANSPORT-007 / wire/options PASS reconnect/resubscribe/backpressure/shutdown PASS redaction URL/credential PASS Config V1 backward + V2 WS PASS HTTP 52 current + 14 historical PASS smoke WebSocket Devnet opt-in PASS (pre.013) dependency firewall / graph audit PASS workspace final post-fix PASS README/USAGE PASS prompt 0.2.8 version 2 PASS ``` La matrice est fermée positivement par `0.2.7-rel.001`.