Files
khadhroony-solana-project/docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md
2026-08-23 09:40:44 +02:00

46 KiB
Raw Blame History

Validation 0.2.7 — WebSocket Solana standard

Statut : matrice active, 0.2.7-pre.012. Les 9 familles standard et leurs 9 opérations unsubscribe sont matérialisées. pre.012 consolide la compliance 18/18, la surface publique, la composition Config V2 -> Transport et la non-régression HTTP avant le smoke/audit de dépendances final.

1. Baseline normative

Audit officiel effectué le 22 août 2026 contre :

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

Compte exact de l'index courant :

9 subscribe
9 unsubscribe
18 méthodes WebSocket totales

Répartition statut KSP :

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 candidate 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<String> 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 :

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 :

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<UnixTimestamp>, 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 :

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 :

--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 :

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 :

--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 Partial pre.004, capture finale future
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 Planned
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 Planned
no direct tracing dans Transport workspace audit + source audit Done pre.002

8. Dependency compliance initiale

Candidate retenue au gate :

tokio-tungstenite 0.30.0
futures-util       0.3.34

Features prévues :

tokio-tungstenite: default-features=false + connect + rustls-tls-webpki-roots
futures-util:       default-features=false + std + sink

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

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é :

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 :

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 :

WsEndpointUrl
WsProviderName
WsClusterName
WsProtocolKind::SolanaStandard
WsReconnectSettings
WsResubscribePolicy::{Never, ActiveSubscriptions}
WsSessionSettings
WsEndpointSettings
WsTransportSettings
WsSessionId
WsSubscriptionId
WsSessionState
WsSubscriptionState
WsSubscriptionKind
WsSessionSnapshot
WsSubscriptionSnapshot

Gates déterministes ajoutés :

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 :

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 :

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 :

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 :

WsSubscription<T>                     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 :

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 :

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 :

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 :

notification_queue_capacity          borne effective par WsSubscription<T>
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 :

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 :

WsSession::account_subscribe
SolanaAccountSubscribeConfig
WsSubscription<SolanaRpcResponse<SolanaAccount>>

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 :

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.006pre.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 :

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 :

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 :

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 :

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 :

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 :

9 WsSubscriptionKind publics exactement couverts
9 wrappers subscribe publics disponibles depuis la crate root
1 handle WsSubscription<T>::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 :

Transport unit tests          = 309
Transport public API tests    = 36
Transport release completeness = 24
Config unit tests             = 109

10. Validation du gate pre.001

Exécuté dans le sandbox :

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 :

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 :

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`**, compilation opérateur requise)
smoke live opt-in documenté
cargo tree inspecté
cargo test --workspace vert
README/USAGE synchronisés
prompt 0.2.8 préparé