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