Files
khadhroony-solana-project/docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md

42 KiB
Raw Blame History

Plan 0.2.7 — WebSocket Solana standard

Statut : actif, gate 0.2.7-pre.001. Cette première tranche fixe l'inventaire normatif, les frontières, le threat model, le modèle session/subscription, la stratégie de dépendances et le sizing. Elle ne matérialise pas encore le client WebSocket runtime.

1. Objet et base vérifiée

0.2.7 étend ksp-onchain-transport-lib avec le transport WebSocket Solana standard sans créer un second composant on-chain et sans déplacer de logique Config, Store, Program, worker ou métier dans Transport.

Base opérateur auditée :

archive Gitea fournie : khadhroony-solana-project-v0.2.6-from-gitea.zip
workspace.package.version avant ouverture = 0.2.6
delta stable présent = deltas/0.2.6/rel.001.md
prochaine release annoncée = 0.2.7 — WebSocket Solana standard

La clôture 0.2.6 confirme l'héritage : Wallet Desk, .kspwallet V1 historique + V2 binaire par défaut, APIs Wallet multi-version, migration V1 -> V2 explicite OWNER-authentifiée, runtime packagé commun aux Desks et transport HTTP Solana stable.

0.2.7-pre.001 ouvre techniquement :

workspace.package.version = 0.2.7-pre.1
livraison                 = 0.2.7-pre.001
commit                    = v0.2.7-pre.001

Aucune dependency WebSocket n'est ajoutée dans ce gate.

2. Sources internes relues et hiérarchie appliquée

L'audit a relu, dans l'ordre demandé par le prompt d'ouverture :

RULES.md
docs/000-README.md
docs/rules/RULES_GENERAL.md
docs/rules/RULES_KSP.md
docs/rules/RULES_RUST.md
docs/rules/RULES_DEPENDENCIES.md
docs/rules/PROMPT_STRUCTURE.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/FILE_CONTRACTS.md

docs/architecture/002-LAYERS_AND_DEPENDENCIES.md
docs/architecture/003-COMPONENT_CONTRACTS.md
docs/architecture/004-COMPONENT_INVENTORY.md
docs/architecture/005-DEPENDENCY_GRAPH.md
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md

docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/007-V0_2_0_SERIES_PLANNING.md
docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md
docs/validation/003-V0_2_1_ONCHAIN_HTTP.md
docs/validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md
docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md

crates/ksp-onchain-transport-lib/**
crates/ksp-config-lib/src/transport.rs
config/std.transport.json
config/schemas/std.transport.schema.json
contrats consommés de ksp-core-lib et ksp-logging-lib

deltas/0.2.6/rel.001.md
docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md
docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md

docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md reste l'autorité de numérotation courante. 007-V0_2_0_SERIES_PLANNING.md est historique.

3. Baseline de démarrage

Le sandbox d'analyse ne fournit pas le binaire cargo.

Les commandes imposées ont été tentées avant modification :

cargo fmt --all                         IMPOSSIBLE : cargo absent, code 127
python3 scripts/audit_rust_workspace_rules.py  OK
cargo check --workspace                 IMPOSSIBLE : cargo absent, code 127
cargo clippy --workspace --all-targets  IMPOSSIBLE : cargo absent, code 127

L'audit Python retourne :

General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean

Aucun gate Cargo n'est déclaré réussi. Une validation opérateur est obligatoire avant commit de pre.001.

4. État réel de Transport et Config hérité

4.1 Surface HTTP stable à préserver

ksp-onchain-transport-lib contient actuellement :

  • settings HTTP publics (HttpEndpointUrl, provider/cluster/role, retry, limits, endpoint, transport) ;
  • HttpEndpointClient et HttpTransportPool ;
  • 52 wrappers HTTP courants typés et 14 méthodes historiques Deprecated tracées ;
  • JSON-RPC, retry/no-resend, snapshots et observabilité ;
  • DTOs wire communs réutilisables, notamment SolanaCommitment, SolanaRpcContext, SolanaRpcResponse<T>, encodings/account DTOs, filters et SolanaWireField<T> ;
  • émissions runtime via ksp-logging-lib, sans tracing direct.

La surface WebSocket doit être additive : aucune renormalisation invasive des types Http* n'est requise dans 0.2.7.

4.2 Config actuel est explicitement HTTP V1

config/std.transport.json, son schema et ksp-config-lib/src/transport.rs décrivent un document V1 HTTP :

format_version = 1
schema title = standard HTTP Transport
additionalProperties = false
serde(deny_unknown_fields)
format_version != 1 rejeté

Décision : ne pas ajouter silencieusement des clés WebSocket au V1.

0.2.7 fera évoluer le même composant standard std.transport vers un format V2 explicitement HTTP + WebSocket, tout en conservant la lecture du V1 HTTP-only pour compatibilité. Config reste propriétaire du parsing/résolution et adapte ensuite vers les settings publics Transport.

Shape cible minimale :

format_version = 2
retry                 # HTTP existant, conservé
ws_defaults            # defaults de session WS KSP, communs aux familles WS
default_profile
profiles[]
  profile_id
  endpoints[]          # HTTP existant, conservé
  ws_endpoints[]       # endpoints WebSocket explicites
    kind               # discriminateur de famille/protocole WS

ws_endpoints[] porte seulement les données nécessaires à Transport : nom logique, enabled, provider, cluster, URL, kind et overrides de session éventuellement nécessaires. Les credentials restent résolus par Config comme aujourd'hui ; Transport reçoit un endpoint déjà résolu et doit redacter l'URL dans Debug/logs/errors.

Décision d'extensibilité : le conteneur V2 ne doit pas supposer qu'il n'existera qu'un seul type de WebSocket. 0.2.7 matérialise uniquement kind = solana_standard; toute valeur inconnue ou future est rejetée explicitement comme non supportée. Le discriminateur est mappé vers un type Transport public non exhaustif, afin qu'une release ultérieure puisse ajouter une famille telle que Helius Enhanced WebSocket sans réécrire le conteneur profiles[].ws_endpoints[] ni contaminer les settings Solana standard avec des champs provider-specific.

Cette préparation ne promet aucune option Helius dans 0.2.7. L'audit provider-specific de la release suivante décidera la relation exacte entre Helius Enhanced WebSocket, LaserStream et le moteur standard avant d'ajouter des variantes/configurations concrètes.

Le V1 chargé sous 0.2.7 produit une configuration HTTP valide et une collection WebSocket vide. Le V2 devient le format livré par les fixtures Config dès la tranche qui matérialise le WebSocket.

5. Audit historique bot3

Archive auditée uniquement comme référence :

khadhroony-bot3_v0.5.3-pre.005-fix010.zip
ks-onchain-transport/src/standard_ws.rs
ks-onchain-transport/src/ws_client.rs
ks-onchain-transport/src/ws_pool.rs
ks-onchain-transport/src/ws_session.rs
consumers/demo et rapports de validation WebSocket

Invariants utiles repris :

  • plusieurs subscriptions sur une session physique ;
  • plusieurs sessions possibles ;
  • identifiant local stable distinct de l'identifiant serveur ;
  • remapping des IDs serveur après reconnect ;
  • reconnect borné et resubscribe ;
  • canaris locaux de lifecycle.

Choix bot3 explicitement non repris :

  • Transport -> Config ;
  • tracing direct ;
  • rôles HTTP et max_subscriptions mélangés au modèle endpoint générique ;
  • pool/scheduler de sessions sans besoin démontré ;
  • API d'unsubscribe publique fondée sur l'ID serveur éphémère ;
  • broadcast unique comme canal principal de notifications data ;
  • états lifecycle trop grossiers pour les races reconnect/unsubscribe.

Le retour historique bot3 sur les flux à fort débit renforce le besoin d'une policy de backpressure explicite et observable ; aucune hypothèse de livraison lossless ne doit reposer sur un canal broadcast qui peut lagger silencieusement.

6. Inventaire WebSocket Solana officiel au 2026-08-22

Source d'index :

https://solana.com/docs/rpc/websocket

L'index courant contient exactement 18 méthodes WebSocket : 9 subscribe + 9 unsubscribe.

accountSubscribe       / accountUnsubscribe
blockSubscribe         / blockUnsubscribe
logsSubscribe          / logsUnsubscribe
programSubscribe       / programUnsubscribe
rootSubscribe          / rootUnsubscribe
signatureSubscribe     / signatureUnsubscribe
slotSubscribe          / slotUnsubscribe
slotsUpdatesSubscribe  / slotsUpdatesUnsubscribe
voteSubscribe          / voteUnsubscribe

Statut observé :

6 paires sans marque unstable/deprecated dans la documentation actuelle
3 paires dont le subscribe est explicitement Unstable :
  blockSubscribe
  slotsUpdatesSubscribe
  voteSubscribe
0 méthode WebSocket de cet index marquée Deprecated

Pour KSP, le label interne Stable signifie ici « documentée et non marquée unstable/deprecated par la source officielle auditée », pas une garantie de standardisation extérieure supplémentaire.

Règles générales documentées : JSON-RPC 2.0 sur connexion persistante ; résultat subscribe numérique ; notifications avec params.subscription numérique ; lorsqu'un contexte est présent context.slot est fourni et context.apiVersion est omis ; les subscriptions qui acceptent commitment utilisent finalized par défaut.

La matrice détaillée initiale est conservée dans docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md.

6.1 Baseline à trois niveaux : docs Solana, Git Agave, SIMD

La compliance WebSocket reprend la méthode utilisée pour la clôture HTTP et ne traite pas le site documentaire comme unique vérité technique :

1. documentation Solana actuelle
   -> surface publique annoncée, paramètres, statuts et formes documentées

2. Git Agave, tag courant audité v4.2.1
   -> parité d'implémentation réelle, types wire, options ignorées, variantes et handlers

3. solana-improvement-documents main
   -> SIMDs Activated, Review, Draft ou Idea susceptibles de modifier le wire,
      la sémantique des notifications ou les hypothèses de taille/lifecycle

Au 2026-08-22, le tag Git Agave v4.2.1 est disponible et constitue la baseline d'implémentation de cette release, même lorsque des liens de la documentation publique pointent encore vers une révision Agave plus ancienne. Le cross-check v4.2.1 confirme les 9 familles subscribe/unsubscribe actuelles et ne révèle aucune méthode PubSub standard supplémentaire à ajouter aux 18 méthodes de l'index Solana.

Les constats ciblés déjà retenus restent valides sous v4.2.1 : accountSubscribe.minContextSlot est toujours ignoré par le handler PubSub, programSubscribe.withContext reste un paramètre réel à préserver, RpcVote.timestamp reste optionnel et SlotUpdate conserve les sept variantes connues de la matrice actuelle.

6.2 Audit SIMD WebSocket ciblé

L'audit SIMD ne consiste pas à implémenter des propositions par anticipation. Il sert à éviter de figer dans KSP des hypothèses déjà fragilisées par l'évolution upstream.

SIMD Statut au 2026-08-22 Conséquence WebSocket KSP
0118 Partitioned Epoch Rewards Distribution Activated réutiliser les DTOs bloc/rewards déjà lossless acquis par HTTP, notamment numRewardPartitions omitted, null ou valeur
0291 Commission Rate in Basis Points Review préserver les champs de commission déjà exposés par Agave et les DTOs partagés, sans dérivation locale entre représentations
0296 Larger Transaction Size Review proposition jusqu'à 4096 octets : ne jamais dimensionner frames/messages sur l'ancienne limite transaction de 1232 ; surveiller blockSubscribe
0298 Bank Hash in Block Footer Idea ne pas inventer bankHash dans le wire actuel ; surveiller l'évolution du DTO bloc partagé
0301 parent bank hash PR fermé, non mergé aucun parentBankHash spéculatif ; conserver le contrôle déjà appliqué à la compliance HTTP
0307 Add Block Footer Review ne pas ajouter footer avant exposition réelle par Agave ; réauditer blockSubscribe si le DTO bloc partagé évolue
0326 Alpenglow Review voteSubscribe et slotsUpdatesSubscribe.optimisticConfirmation sont unstable ; ne pas coder de sémantique durable TowerBFT autour de ces flux
0337 Alpenglow Fast Leader Handover Markers Review nouveaux block markers potentiels : risque futur de shape/taille pour les blocs, sans nouveau champ WS courant
0384 Alpenglow migration Review la migration peut suspendre des notifications RPC de commitment/optimistic confirmation ; aucun flux KSP ne doit supposer une séquence complète
0385 Transaction V1 Review conserver maxSupportedTransactionVersion et les versions transaction comme contrats génériques ; ne pas caper localement à v0

Conséquence de sizing : les bornes WebSocket KSP sont des protections runtime configurables et finies, pas des constantes dérivées de l'ancienne taille de transaction Solana. Les tests blockSubscribe devront inclure des payloads sensiblement supérieurs aux transactions legacy tout en restant sous les limites KSP configurées.

Sources SIMD auditées :

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

7. Points wire sensibles sous KSP-TRANSPORT-007

accountSubscribe

Conserver : pubkey ; commitment ; encodings base58|base64|base64+zstd|binary|jsonParsed ; dataSlice ; notification accountNotification contextualisée.

Le type partagé Agave RpcAccountInfoConfig contient aussi min_context_slot, mais le handler PubSub v4.2.1 l'ignore explicitement. KSP ne doit donc pas promettre minContextSlot comme option WebSocket effective tant que l'upstream ne lui donne pas une sémantique réelle ; cette non-promise doit rester visible dans la compliance pour éviter qu'une option ignorée soit comptée comme supportée.

programSubscribe

Conserver : program pubkey ; commitment ; filters ; mêmes encodings account ; dataSlice ; withContext default false.

La documentation actuelle expose withContext, mais le code Agave v4.2.1 lié par la page stocke bien with_context alors que le chemin générique de notification audité sérialise un RpcResponse contexté. KSP ne doit ni ignorer le paramètre, ni convertir cette incohérence amont en hypothèse stricte. Décision : exposer le flag et rendre le décodeur capable d'accepter la forme documentée non-contextée comme la forme contextée observée.

logsSubscribe

Conserver les trois filtres : all, allWithVotes, {mentions:[pubkey]}. La forme mentions autorise exactement une adresse dans la documentation actuelle. Conserver commitment; notification signature + err + logs contextualisée.

signatureSubscribe

Conserver commitment et enableReceivedNotification. Le résultat notification est une union wire :

"receivedSignature"
OU
{ "err": null | transaction error }

La subscription se termine après la notification terminale ; elle ne doit donc pas être resubscribed après que le runtime KSP a observé cette terminaison.

slotSubscribe / rootSubscribe

Aucun paramètre. slotNotification conserve slot, parent, root; rootNotification conserve le root u64.

blockSubscribe — Unstable

Conserver : filtre all ou {mentionsAccountOrProgram:<pubkey>} ; commitment limité à confirmed|finalized ; encoding binary|base58|base64|json|jsonParsed ; transactionDetails full|accounts|signatures|none ; maxSupportedTransactionVersion ; showRewards. La méthode nécessite côté validator les flags documentés ; KSP ne les simule pas.

La notification conserve context, slot, block nullable et err nullable, ainsi que les variantes de block liées à la config.

slotsUpdatesSubscribe — Unstable

Conserver les variantes taggées actuelles et leurs champs :

firstShredReceived
completed
createdBank
frozen
dead
optimisticConfirmation
root

Le runtime doit prévoir une forme Unknown/raw bornée pour qu'une variante ajoutée par une version upstream ne ferme pas la session entière avant réaudit KSP.

voteSubscribe — Unstable

Aucun paramètre. La méthode nécessite le flag validator documenté et observe des votes gossip pre-consensus. Conserver votePubkey, slots, hash, timestamp, signature. Agave v4.2.1 définit timestamp comme Option<i64> : le décodeur KSP doit donc préserver cette optionalité et accepter de manière tolérante les formes wire omitted/null/value, sans inventer une valeur.

*Unsubscribe

Chaque unsubscribe reçoit l'ID serveur et retourne true quand la subscription est supprimée ; le handler Agave audité retourne InvalidParams pour un ID inconnu. Cette réalité wire ne devient pas l'API publique KSP : le caller utilise l'ID local/handle stable et la session traduit vers l'ID serveur courant.

Le cross-check Agave v4.2.1 confirme également que logsSubscribe impose exactement une adresse pour mentions, que signatureSubscribe ne consomme que commitment et enableReceivedNotification, et que blockSubscribe applique bien le jeu d'options documenté avec un commitment au moins confirmed. Ces constats renforcent la matrice normative sans ajouter d'options non documentées à KSP.

8. Dépendances WebSocket candidates

Audit au 2026-08-22 :

Candidate Version auditée Verdict Motif
tokio-tungstenite 0.30.0 retenue mature, Tokio-native, TLS rustls, continuité avec bot3 mais réauditée, contrôle de WebSocketConfig, client + serveur local de test
futures-util 0.3.34 retenue comme utilitaire StreamExt/SinkExt; features minimales std,sink
tokio-websockets 0.13.3 alternative viable, non retenue strict/minimal et performant, mais exige davantage d'assemblage/features et n'apporte pas de besoin fonctionnel supérieur démontré pour cette foundation
fastwebsockets 0.10.0 non retenue plus bas niveau ; peut déléguer davantage de compliance au caller, inutile pour la première foundation KSP

Landing prévu, pas dans pre.001 :

# root [workspace.dependencies]
tokio-tungstenite = { version = "^0.30", default-features = false }
futures-util = { version = "^0.3", default-features = false }

# ksp-onchain-transport-lib
tokio-tungstenite = { workspace = true, features = ["connect", "rustls-tls-webpki-roots"] }
futures-util = { workspace = true, features = ["std", "sink"] }
tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] }

Le serveur de test local pourra activer tokio/net en dev si KSP utilise directement TcpListener.

url n'est pas retenu a priori : l'endpoint KSP peut être validé et passé comme chaîne/request sans ajouter la feature uniquement par habitude bot3. handshake/stream sont déjà requis transitivement par connect/TLS et ne doivent pas être listés sans nécessité directe.

Les defaults de tungstenite::WebSocketConfig ne sont pas des limites Solana normatives. KSP configurera explicitement des plafonds finis pour messages, frames et write buffer. Les valeurs numériques KSP seront figées avec les tests adversariaux de la tranche settings/session, et documentées comme policy locale, jamais comme limite protocolaire Solana.

Sources :

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
https://docs.rs/crate/tokio-websockets/0.13.3
https://docs.rs/crate/fastwebsockets/0.10.0

9. Modèle public Transport retenu

9.1 Settings

Noms cibles :

WsEndpointUrl
WsProviderName
WsClusterName
WsProtocolKind
WsReconnectSettings
WsResubscribePolicy
WsSessionSettings
WsEndpointSettings
WsTransportSettings

Les wrappers WsProviderName/WsClusterName sont parallèles aux Http* pour éviter une migration publique HTTP dans cette release. Une généralisation future n'est justifiée que si un troisième backend démontre un type commun réellement utile.

WsProtocolKind est un discriminateur public #[non_exhaustive]. 0.2.7 n'expose concrètement que SolanaStandard. Ajouter plus tard une famille provider-specific ne doit donc ni casser les matches externes ni forcer une nouvelle forme de WsEndpointSettings. Les options spécifiques restent portées par leurs contrats dédiés et ne sont jamais ajoutées arbitrairement à WsSessionSettings.

WsEndpointUrl :

  • accepte seulement ws:///wss:// après validation ;
  • fournit un accès explicite à la valeur au code de connexion ;
  • Debug et tout diagnostic par défaut sont redacted ;
  • aucun snapshot public ne contient l'URL complète.

WsSessionSettings porte les bornes runtime nécessaires : command timeout, close timeout, reconnect, resubscribe, capacités de queues, maximum de subscriptions actives, maximum de requests JSON-RPC en vol, maximum de message/frame/write buffer. Pas de rôle HTTP ni de scheduler.

9.2 Cardinalité

Contrat :

1 WsEndpointSettings
  -> 0..N WsSession physiques créées explicitement par caller
      -> 0..N WsSubscription actives

Deux appels de création sur le même endpoint ouvrent deux connexions physiques. Transport ne distribue pas automatiquement les subscriptions entre elles.

9.3 Session actor

Une tâche actor possède exclusivement :

  • le stream WebSocket physique ;
  • le compteur d'IDs JSON-RPC requests ;
  • la map des requests en vol ;
  • le registre local des subscriptions ;
  • le mapping remote_subscription_id -> local_subscription_id ;
  • le lifecycle reconnect/resubscribe ;
  • les compteurs/snapshot sûrs.

Le handle public WsSession communique avec cet actor par canal bounded. Aucun caller ne split/manipule directement le socket.

9.4 Identités

WsSessionId        # KSP local, stable pour la vie du handle
WsSubscriptionId   # KSP local, stable malgré reconnect
remote id u64      # strictement runtime/interne, remappable

Une WsSubscription<T> typée contient l'identité locale, la projection de lifecycle et un receiver bounded de notifications typées. L'ID remote ne devient pas un identifiant métier/public de contrôle.

10. State machines retenues

10.1 Session

Disconnected
  -> Connecting
      -> Active
      -> Failed

Active
  -> Reconnecting
      -> Active
      -> Failed
  -> Closing
      -> Closed

Reconnecting
  -> Closing
      -> Closed

État public cible :

Disconnected | Connecting | Active | Reconnecting { attempt } | Closing | Closed | Failed

10.2 Subscription

Requested
  -> Active
  -> Failed

Active
  -> Resubscribing
      -> Active
      -> Failed
  -> Cancelling
      -> Closed
  -> Closed       # terminaison serveur connue, ex. signature terminale

Requested/Resubscribing
  -> Cancelling
      -> Closed

État public cible :

Requested | Active | Resubscribing | Cancelling | Closed | Failed

Le motif terminal est séparé (Unsubscribed, CompletedByServer, SessionClosed, BackpressureOverflow, ProtocolFailure, etc.) afin de ne pas gonfler l'enum d'états.

11. Reconnect et resubscribe

11.1 Reconnect

Reconnect uniquement pour perte du canal physique : EOF, close inattendue, erreur I/O/TLS/WebSocket read/write. Une erreur RPC applicative de subscribe/unsubscribe n'entraîne pas une reconnexion automatique de la session.

Policy :

  • nombre d'essais fini et configurable ;
  • backoff exponentiel borné par initial_backoff/max_backoff ;
  • pas de jitter dans 0.2.7 afin d'éviter une dépendance/randomisation supplémentaire et garder les tests déterministes ;
  • le budget se réinitialise après retour complet à Active ;
  • exhaustion => session Failed, subscriptions terminales avec cause explicite ;
  • shutdown/close annule immédiatement backoff et interdit toute nouvelle tentative.

Les valeurs numériques par défaut sont une policy KSP et seront figées avec WsSessionSettings après tests ; elles ne seront pas attribuées à Solana.

11.2 Resubscribe

Enum public :

WsResubscribePolicy::Never
WsResubscribePolicy::ActiveSubscriptions

Default KSP proposé : ActiveSubscriptions.

Après perte de connexion :

  1. tous les remote IDs deviennent invalides et sont retirés du mapping ;
  2. la session émet/incrémente un signal de continuity gap ;
  3. après reconnect, seules les subscriptions encore désirées actives sont restaurées ;
  4. ordre déterministe : WsSubscriptionId croissant / ordre de création ;
  5. chaque ack remappe un nouvel ID remote ;
  6. une erreur de resubscribe marque cette subscription Failed mais ne ferme pas les autres si le socket reste sain.

KSP ne garantit pas la continuité lossless entre la déconnexion et la restauration. Aucun backfill HTTP automatique n'est ajouté à Transport. Les consumers workers/jobs pourront réconcilier plus tard avec leur propre checkpoint/persistence.

11.3 Race unsubscribe pendant reconnect

La cancellation locale gagne toujours :

  • marquer la subscription non restaurable avant de traiter les acks tardifs ;
  • retirer sa spec de la sélection resubscribe ;
  • si un subscribe/resubscribe distant tardif réussit malgré tout, envoyer un best-effort unsubscribe de son nouvel ID ;
  • ne jamais repasser la subscription locale à Active après cancellation.

12. Backpressure et bornes de ressources

12.1 Queues

  • canal commands session : bounded ;
  • notifications : queue bounded par subscription ;
  • lifecycle/snapshot : watch/état partagé compact, pas une file data infinie ;
  • requests JSON-RPC en vol : map bornée + timeout ;
  • subscriptions actives : plafond configurable par session.

12.2 Overflow notifications

Aucun drop silencieux.

Si la queue d'une subscription est pleine :

  1. compteur overflow incrémenté ;
  2. subscription passe Failed(BackpressureOverflow) ;
  3. best-effort unsubscribe distant si possible ;
  4. son receiver se termine avec la cause observable via état final ;
  5. les autres subscriptions de la session restent actives.

Cette policy isole un consumer lent sans sacrifier toute la connexion et sans prétendre être lossless.

12.3 Frames/messages/JSON

Configurer des plafonds finis pour :

max_message_size
max_frame_size
max_write_buffer_size
max_pending_requests
max_active_subscriptions
command_queue_capacity
notification_queue_capacity

La parse JSON ne reçoit donc jamais un message WebSocket arbitrairement grand. Les payloads raw conservés pour losslessness restent eux aussi sous cette borne.

13. Cancellation, control frames et shutdown

13.1 Control frames

Le runtime gère proprement Ping/Pong/Close selon la crate WebSocket retenue. Aucun heartbeat applicatif périodique n'est activé par défaut dans 0.2.7 : la documentation Solana actuelle n'impose pas un ping KSP. Un keepalive configurable sera ajouté seulement si un besoin interop réel est démontré.

13.2 Shutdown explicite

WsSession::close().await doit :

  1. passer Closing et refuser de nouvelles subscriptions ;
  2. annuler reconnect/backoff/resubscribe et requests en vol ;
  3. si connecté, best-effort unsubscribe des actives dans un ordre déterministe sous un budget global ;
  4. envoyer Close WebSocket ;
  5. fermer toutes les subscriptions localement avec raison terminale ;
  6. attendre la fin de l'actor sous close_timeout ;
  7. finir Closed même si le peer ne répond pas à temps.

Si la session est déjà reconnecting, elle ne se reconnecte jamais seulement pour envoyer des unsubscribe.

Drop peut déclencher un signal best-effort, mais ne remplace pas l'API async explicite pour les garanties de lifecycle.

14. Erreurs et anomalies de protocole

Réutiliser lorsque possible les codes HTTP/JSON-RPC génériques existants :

invalid_settings
invalid_rpc_parameters
json_encode_failed
json_decode_failed
json_rpc_protocol_invalid
rpc_application_error
timeout

Codes WS cibles minimaux :

ws_connection_failed
ws_protocol_error
ws_session_closed
ws_subscription_failed
ws_backpressure_overflow

Policy d'anomalies :

  • JSON/message WebSocket structurellement invalide : erreur protocole session, fermeture/reconnect selon budget ;
  • notification JSON valide mais payload typed invalide pour une subscription connue : fail de cette subscription, pas de teardown automatique des autres ;
  • notification valide pour ID remote inconnu/stale : compteur + log safe, drop ;
  • method notification incompatible avec la subscription connue : fail de cette subscription ;
  • réponse JSON-RPC d'erreur à subscribe/unsubscribe : rpc_application_error, pas reconnect implicite.

15. Observabilité et secrets

Tous les événements KSP passent par ksp-logging-lib.

Autorisés dans logs/snapshots :

endpoint logical name
provider label
cluster label
WsSessionId
WsSubscriptionId
subscription kind
state transitions
active/pending counts
reconnect attempt / exhaustion
continuity_gap_count
overflow_count
error code qualifié

Interdits :

URL WebSocket complète
query token/credential
Authorization/header sensible
raw notification arbitraire
payload massif
remote request body complet

Snapshot public cible : état session, endpoint name/provider/cluster, counts, reconnect state, continuity gaps, subscription IDs locaux + kinds + states. L'ID serveur peut rester interne ; un booléen remote_bound suffit pour diagnostiquer le binding sans faire croire à sa stabilité.

Tests dédiés : URL wss://user:pass@host/path?api-key=secret ne doit apparaître ni dans Debug, ni erreurs, ni logs capturés, ni snapshots.

16. DTOs et losslessness

Réutiliser les DTOs HTTP lorsque leur sémantique wire est réellement identique :

SolanaCommitment
SolanaRpcContext
SolanaRpcResponse<T>
SolanaAccountEncoding
SolanaDataSliceConfig
SolanaProgramAccountFilter
SolanaAccount
SolanaKeyedAccount
block/transaction DTOs compatibles
SolanaWireField<T>

Définir des DTOs WS dédiés pour : enveloppes notification, lifecycle, subscription kind/config, logs, signature union, slot/root/slotsUpdates/vote, block update et toute forme où le wire diffère.

SolanaRpcContext.api_version est déjà optionnel ; il peut donc représenter les contexts PubSub où la documentation indique que apiVersion est omis.

Pour unstable/évolutif, préférer des enums avec fallback Unknown { type_name, raw } borné plutôt qu'un rejet session-wide d'un variant upstream nouveau.

17. API générique provider-specific

Le moteur interne doit encoder une spec générique subscribe_method + unsubscribe_method + params + decoder, afin qu'une release provider-specific puisse réutiliser la session.

La préparation Config/Transport de 0.2.7 se limite au discriminateur WsProtocolKind et au conteneur commun ws_endpoints[]. Elle doit permettre d'ajouter ultérieurement au moins une famille comme Helius Enhanced WebSocket, tout en laissant l'audit de la release suivante décider si Helius Enhanced WebSocket, LaserStream WebSocket et d'autres surfaces Helius partagent exactement le même moteur, les mêmes credentials et les mêmes paramètres.

Aucune API publique raw provider-extension et aucun paramètre Helius ne sont promis dans 0.2.7. Ils seront décidés après audit Helius. Cela évite de figer trop tôt une escape hatch qui deviendrait le contrat principal et contournerait les wrappers typés standards.

18. Tests déterministes attendus

Le test runtime utilise un serveur WebSocket local déterministe, sans provider externe :

handshake + close
subscribe -> notification -> unsubscribe
plusieurs subscriptions sur une session
deux sessions physiques sur la même URL
IDs locaux stables / IDs distants remappés
erreur RPC subscribe/unsubscribe
JSON/message malformé
notification method/payload incompatible
unknown/stale remote subscription id
message/frame oversized
connexion interrompue
reconnect borné + exhaustion + reset budget
resubscribe déterministe
unsubscribe pendant reconnect + ack stale
signature terminale => closed, jamais resubscribe
shutdown active/pending/reconnecting
backpressure : overflow d'une sub n'impacte pas les autres
limits active subscriptions/pending requests
program notification contexted + non-contexted
slotsUpdates unknown variant
vote timestamp omitted/null/value
URL/credentials absents de Debug/error/log/snapshot
warning centralisé pour unstable à la création, pas à chaque notification

Le serveur local est un fixture de Transport ; aucune application Tauri n'est modifiée pour tester WebSocket.

19. Smoke live opt-in

Après stabilisation : un test #[ignore] Transport pur sur Devnet, préférentiellement une subscription stable simple (slotSubscribe) :

connect
slotSubscribe
attendre une notification sous timeout
unsubscribe
close

Un endpoint override pourra être fourni par l'environnement Config seulement dans le smoke de composition séparé si celui-ci est réellement utile. Les unstable ne deviennent pas gates live car les validators publics peuvent les désactiver.

Un rate-limit, refus provider ou indisponibilité externe ne constitue pas automatiquement une régression locale.

20. Threat model synthétique

Risque Défense retenue Preuves attendues
URL/token fuit dans diagnostics type URL redacted + snapshots sans URL + messages d'erreur qualifiés tests Debug/error/log
message/frame géant limites WebSocket explicites avant JSON tests oversized
JSON/allocation non bornée taille message bornée + DTOs ciblés/raw borné adversarial fixtures
consumer lent queue par sub bounded, fail local explicite overflow isolé
reconnect infini budget fini + backoff borné exhaustion test
notifications manquées pendant reconnect continuity gap observable, aucune promesse lossless lifecycle test/docs
resubscribe stale après unsubscribe ID local desired-state, cancellation gagne race test
remote ID réutilisé/remappé mapping éphémère interne reconnect/remap tests
subscription orpheline registry actor + close explicite + cleanup terminal shutdown tests
pending request leak map bornée + timeout + purge reconnect/close timeout tests
task leak actor unique joinable + close timeout repeated connect/close tests
shutdown bloqué budget global + close timeout hostile peer fixture
unstable upstream variant casse session typed enum + Unknown/raw borné unknown variant fixture
logique worker/persistence glisse dans Transport aucun backfill/persistence/checkpoint dependency/public API audit

21. Hors périmètre confirmé

Helius Enhanced WS / provider params     -> audit provider-specific futur
Helius LaserStream WebSocket              -> 0.2.8 selon séquence courante
Yellowstone gRPC                          -> 0.2.9
provider Yellowstone commercial          -> plus tard
shred/deshred/pre-execution               -> plus tard
off-chain prices                          -> 0.2.10
Wallet / 2FA / .kspwallet                 -> hors 0.2.7
Store/persistence                         -> 0.3.x
Program decode/materialization            -> plus tard
frontend réseau direct                    -> interdit
pool/scheduler automatique de sessions    -> différé
backfill HTTP automatique sur gap WS      -> workers/jobs futurs
public raw provider-extension API         -> gate 0.2.8
application heartbeat/ping périodique     -> différé sans besoin démontré

22. Forecast recalibré

L'inventaire officiel n'impose que 9 familles de subscriptions, mais le lifecycle concurrent est plus coûteux que le forecast initial. Le gate reste positif sans split de release, à condition de granulariser les tranches au lieu de compresser le moteur et les wrappers.

pre.001  audit interne/externe + matrice 18 méthodes + bot3 + dependencies + threat model + plan/sizing
pre.002  settings WS Transport + URL redaction + IDs/states/snapshots + tests de settings
pre.003  std.transport V2 HTTP+WS + backward V1 + discriminateur WS + schema/fixtures + Config -> WsTransportSettings
pre.004  deps tokio-tungstenite/futures-util + actor physique + handshake/read/write + pending JSON-RPC + serveur local
pre.005  limites frame/message/request + control frames + cancellation/close/shutdown + adversarial socket tests
pre.006  registry subscriptions + IDs locaux + generic subscribe/unsubscribe engine + channels typed bounded
pre.007  reconnect borné + resubscribe déterministe + continuity gap + races unsubscribe/reconnect
pre.008  backpressure per-sub + overflow/limits + leak/lifecycle adversarial tests
pre.009  wrappers stable lot A : account + program + logs, DTOs/options/KSP-TRANSPORT-007
pre.010  wrappers stable lot B : signature + slot + root, terminaison signature/KSP-TRANSPORT-007
pre.011  unstable : block + slotsUpdates + vote, warnings + fallbacks wire/KSP-TRANSPORT-007
pre.012  compliance 18/18 + canaries public API + composition Config + régressions HTTP
pre.013  smoke live opt-in + README/USAGE + cargo tree/duplicates + dependency audit final
pre.014  validation workspace finale + docs/compliance + prompt 0.2.8
rel.001  publication stable stricte

Chaque tranche reste scindable si son implémentation réelle dépasse le budget nominal de 1520 minutes. Le nombre pre.014 n'est pas une deadline normative.

23. Gates de clôture 0.2.7

La release ne peut passer stable que si :

  • l'inventaire officiel est réaudité et reste rapproché par noms exacts ;
  • 18/18 opérations standard de l'index ciblé ont un statut explicite ;
  • 9/9 subscribe ont leur wrapper KSP promis et 9/9 unsubscribe sont couverts via handles/registry ;
  • options et variantes wire sont conservées sous KSP-TRANSPORT-007 ;
  • la cardinalité endpoint -> N sessions -> N subscriptions est démontrée ;
  • reconnect/resubscribe/backpressure/cancellation/shutdown sont bornés et testés ;
  • continuity gaps sont observables sans promesse lossless ;
  • secrets/URLs ne fuitent pas ;
  • Config -> Transport reste l'unique direction d'adaptation ;
  • aucune dépendance Store/Program/Wallet/Config/tracing direct n'apparaît dans Transport ;
  • HTTP 52+14 ne régresse pas ;
  • tests déterministes et workspace sont verts ;
  • smoke live retenu reste opt-in ;
  • README/USAGE, matrice finale, graphes Cargo et prompt 0.2.8 sont synchronisés.

24. Validation opérateur requise pour pre.001

Après application de ce gate documentaire/versionné :

cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets

cargo test --workspace n'est pas imposé par cette tranche documentaire d'ouverture tant qu'aucun Rust runtime n'est ajouté, mais reste autorisé comme checkpoint opérateur.