v0.2.7-pre.007

This commit is contained in:
2026-08-22 20:42:06 +02:00
parent 435126f67a
commit b67fa89f44
11 changed files with 1352 additions and 112 deletions

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md -->
<!-- version: 8 -->
<!-- version: 9 -->
# Plan `0.2.7` — WebSocket Solana standard
> **Statut : actif, `0.2.7-pre.005`.** `pre.004-fix.001` est validé opérateur. `pre.005` durcit la session physique avec limites adversariales, control frames, purge des pending requests et shutdown explicite borné. Les subscriptions typed restent différées.
> **Statut : actif, `0.2.7-pre.007`.** `pre.006-fix.001` est validé opérateur. `pre.007` active le reconnect borné, le resubscribe déterministe, le compteur de continuity gaps et les races unsubscribe/reconnect. Les wrappers typed publics restent différés.
## 1. Objet et base vérifiée
@@ -490,6 +490,8 @@ Le handle public `WsSession` communique avec cet actor par canal bounded. Aucun
`pre.006` matérialise le registry dans ce même actor. L'ACK `*Subscribe` lie atomiquement le remote ID au `WsSubscriptionId` local avant le dispatch des notifications suivantes. La création générique typed reste crate-private ; `WsSubscription<T>` devient le handle public commun pour `id/state/recv/unsubscribe`. Les channels de notification sont bornés et typed via un dispatcher actor type-erased. Les remote IDs restent absents de l'API publique et des snapshots.
`pre.007` active la résilience physique déjà paramétrée par `WsSessionSettings`. Une perte I/O, EOF, Close distant inattendu ou violation protocolaire structurelle invalide les remote IDs et incrémente `continuity_gap_count`. Le reconnect utilise un nombre fini d'essais, un backoff exponentiel borné sans jitter et surveille le shutdown pendant backoff et handshake. Avec `ActiveSubscriptions`, les subscriptions encore désirées sont remises en `Resubscribing` puis restaurées séquentiellement par `WsSubscriptionId` croissant avant le retour complet à `Active`; avec `Never`, elles deviennent terminales sans empêcher la reconnexion physique. La cancellation locale gagne pendant reconnect et un ACK tardif déclenche un unsubscribe distant best-effort sans réactivation locale.
### 9.4 Identités
```text
@@ -879,7 +881,7 @@ pre.003 DONE — std.transport V2 HTTP+WS + backward V1 + discriminateur WS + s
pre.004 DONE — deps tokio-tungstenite/futures-util + actor physique + handshake/read/write + pending JSON-RPC + serveur local
pre.005 DONE — limites frame/message/request + control frames + cancellation/close/shutdown + adversarial socket tests
pre.006 DONE — registry subscriptions + IDs locaux + generic subscribe/unsubscribe engine + channels typed bounded
pre.007 reconnect borné + resubscribe déterministe + continuity gap + races unsubscribe/reconnect
pre.007 DONE — reconnect borné + resubscribe déterministe + continuity gap + races unsubscribe/reconnect
pre.008 backpressure per-sub + overflow/limits + leak/lifecycle adversarial tests
pre.009 wrappers stable lot A : account + program + logs, DTOs/options/KSP-TRANSPORT-007
pre.010 wrappers stable lot B : signature + slot + root, terminaison signature/KSP-TRANSPORT-007

View File

@@ -1,9 +1,9 @@
<!-- file: docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md -->
<!-- version: 8 -->
<!-- version: 9 -->
# Validation `0.2.7` — WebSocket Solana standard
> **Statut : matrice active, `0.2.7-pre.005`.** Settings/lifecycle `pre.002`, Config V2 `pre.003`, session physique `pre.004` et durcissement limits/control/shutdown `pre.005` sont matérialisés. Registry subscriptions, reconnect et wrappers typed restent ouverts.
> **Statut : matrice active, `0.2.7-pre.007`.** Settings/lifecycle `pre.002`, Config V2 `pre.003`, session physique `pre.004`, durcissement `pre.005`, registry `pre.006` et reconnect/resubscribe `pre.007` sont matérialisés. Backpressure per-sub et wrappers typed restent ouverts.
## 1. Baseline normative
@@ -216,8 +216,8 @@ Les votes observés sont gossip/pre-consensus ; aucune garantie d'entrée dans l
| JSON borné indirectement par message | oversized + malformed fixture | Planned |
| queues notifications bornées | capacité settings, channel futur | Partial `pre.002` |
| pending RPC borné + timeout | map actor bornée + timeout request | **Done `pre.004`** |
| reconnect loop bornée | repeated disconnect fixture | Planned |
| unsubscribe pendant reconnect ne resubscribe pas | race fixture | Planned |
| 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 | Planned |
| no Store/Program/Wallet/Config dep dans Transport | cargo tree + source canary | Planned |
@@ -368,7 +368,7 @@ outbound JSON-RPC > borne message/frame -> rejet avant socket write, session Act
inbound frame/message oversized -> rejet Tungstenite avant parse JSON, session Failed
pending request silencieuse -> timeout + purge de capacité, session Active
Ping distant -> Pong automatique flushé, session Active
Close distant propre -> Closed et non Failed
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é
@@ -390,7 +390,7 @@ 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, pre.007
reconnect/resubscribe non à ce checkpoint ; matérialisé en pre.007
backpressure adversarial complet non, pre.008
```
@@ -410,6 +410,43 @@ 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.
## 10. Validation du gate `pre.001`
Exécuté dans le sandbox :