40 KiB
Validation 0.2.7 — WebSocket Solana standard
Statut : matrice active,
0.2.7-pre.007. Settings/lifecyclepre.002, Config V2pre.003, session physiquepre.004, durcissementpre.005, registrypre.006et reconnect/resubscribepre.007sont matérialisés. Backpressure per-sub et wrappers typed restent ouverts.
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 |
Planned 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 |
Planned 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 |
Planned pre.011 |
| 4 | blockUnsubscribe |
unsubscribe | Unstable pair | remote id ; boolean/error | block pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/blockunsubscribe |
Planned 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 |
Planned pre.009 |
| 6 | logsUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | logs pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/logsunsubscribe |
Planned 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 |
Planned pre.009 |
| 8 | programUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | program pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/programunsubscribe |
Planned pre.009 |
| 9 | rootSubscribe |
subscribe | Stable/documented | no params ; numeric id | rootNotification => u64 |
exact root fixture | https://solana.com/docs/rpc/websocket/rootsubscribe |
Planned pre.010 |
| 10 | rootUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | root pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/rootunsubscribe |
Planned 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 |
Planned 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 |
Planned 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 |
Planned pre.010 |
| 14 | slotUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | slot pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/slotunsubscribe |
Planned pre.010 |
| 15 | slotsUpdatesSubscribe |
subscribe | Unstable | no params ; numeric id | tagged slotsUpdatesNotification |
each known variant + unknown fallback | https://solana.com/docs/rpc/websocket/slotsupdatessubscribe |
Planned pre.011 |
| 16 | slotsUpdatesUnsubscribe |
unsubscribe | Unstable pair | remote id ; boolean/error | slotsUpdates pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/slotsupdatesunsubscribe |
Planned 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 |
Planned pre.011 |
| 18 | voteUnsubscribe |
unsubscribe | Unstable pair | remote id ; boolean/error | vote pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/voteunsubscribe |
Planned 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éRpcAccountInfoConfigexposemin_context_slot, mais le handler PubSubv4.2.1le destructure en_ // ignored. KSP ne le compte donc pas comme option WebSocket effective ;programSubscribe:RpcProgramAccountsConfig.with_contextest 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 accepteall,allWithVotesoumentionset rejette un filtrementionscontenant autre chose qu'exactement une adresse ;signatureSubscribe: le handler utilise la signature,commitmentetenable_received_notification, sans option WebSocket supplémentaire ;blockSubscribe: le handler consomme le jeu d'options documenté et impose un commitment au moinsconfirmed;*Unsubscribe: un ID serveur inconnu produitInvalidParamsdans le handler audité ;voteSubscribe:RpcVote.timestampestOption<UnixTimestamp>, donc optionnel au niveau wire ;slotsUpdatesSubscribe:SlotUpdatepossède toujours les sept variantes courantesFirstShredReceived,Completed,CreatedBank,Frozen,Dead,OptimisticConfirmationetRoot.
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.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. Le lot B (signature, slot, root) reste explicitement différé à pre.010.
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
9/9 subscribe wrappers public typed
9/9 unsubscribe couverts par handles/registry
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é
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é