# Plan `0.2.7` — WebSocket Solana standard > **Statut : actif, gate `0.2.7-pre.001`.** Cette première tranche fixe l'inventaire normatif, les frontières, le threat model, le modèle session/subscription, la stratégie de dépendances et le sizing. Elle ne matérialise pas encore le client WebSocket runtime. ## 1. Objet et base vérifiée `0.2.7` étend `ksp-onchain-transport-lib` avec le transport WebSocket Solana standard sans créer un second composant on-chain et sans déplacer de logique Config, Store, Program, worker ou métier dans Transport. Base opérateur auditée : ```text archive Gitea fournie : khadhroony-solana-project-v0.2.6-from-gitea.zip workspace.package.version avant ouverture = 0.2.6 delta stable présent = deltas/0.2.6/rel.001.md prochaine release annoncée = 0.2.7 — WebSocket Solana standard ``` La clôture `0.2.6` confirme l'héritage : Wallet Desk, `.kspwallet` V1 historique + V2 binaire par défaut, APIs Wallet multi-version, migration V1 -> V2 explicite OWNER-authentifiée, runtime packagé commun aux Desks et transport HTTP Solana stable. `0.2.7-pre.001` ouvre techniquement : ```text workspace.package.version = 0.2.7-pre.1 livraison = 0.2.7-pre.001 commit = v0.2.7-pre.001 ``` Aucune dependency WebSocket n'est ajoutée dans ce gate. ## 2. Sources internes relues et hiérarchie appliquée L'audit a relu, dans l'ordre demandé par le prompt d'ouverture : ```text RULES.md docs/000-README.md docs/rules/RULES_GENERAL.md docs/rules/RULES_KSP.md docs/rules/RULES_RUST.md docs/rules/RULES_DEPENDENCIES.md docs/rules/PROMPT_STRUCTURE.md docs/rules/VERSION_WORKFLOW.md docs/rules/FILE_CONTRACTS.md docs/architecture/002-LAYERS_AND_DEPENDENCIES.md docs/architecture/003-COMPONENT_CONTRACTS.md docs/architecture/004-COMPONENT_INVENTORY.md docs/architecture/005-DEPENDENCY_GRAPH.md docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md docs/plans/007-V0_2_0_SERIES_PLANNING.md docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md docs/validation/003-V0_2_1_ONCHAIN_HTTP.md docs/validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md crates/ksp-onchain-transport-lib/** crates/ksp-config-lib/src/transport.rs config/std.transport.json config/schemas/std.transport.schema.json contrats consommés de ksp-core-lib et ksp-logging-lib deltas/0.2.6/rel.001.md docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md ``` `docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md` reste l'autorité de numérotation courante. `007-V0_2_0_SERIES_PLANNING.md` est historique. ## 3. Baseline de démarrage Le sandbox d'analyse ne fournit pas le binaire `cargo`. Les commandes imposées ont été tentées avant modification : ```text cargo fmt --all IMPOSSIBLE : cargo absent, code 127 python3 scripts/audit_rust_workspace_rules.py OK cargo check --workspace IMPOSSIBLE : cargo absent, code 127 cargo clippy --workspace --all-targets IMPOSSIBLE : cargo absent, code 127 ``` L'audit Python retourne : ```text General Rust rule audit: clean Rust export completeness audit: 0 candidate(s) KSP workspace Rust rule audit: clean ``` Aucun gate Cargo n'est déclaré réussi. Une validation opérateur est obligatoire avant commit de `pre.001`. ## 4. État réel de Transport et Config hérité ### 4.1 Surface HTTP stable à préserver `ksp-onchain-transport-lib` contient actuellement : - settings HTTP publics (`HttpEndpointUrl`, provider/cluster/role, retry, limits, endpoint, transport) ; - `HttpEndpointClient` et `HttpTransportPool` ; - 52 wrappers HTTP courants typés et 14 méthodes historiques Deprecated tracées ; - JSON-RPC, retry/no-resend, snapshots et observabilité ; - DTOs wire communs réutilisables, notamment `SolanaCommitment`, `SolanaRpcContext`, `SolanaRpcResponse`, encodings/account DTOs, filters et `SolanaWireField` ; - émissions runtime via `ksp-logging-lib`, sans `tracing` direct. La surface WebSocket doit être additive : aucune renormalisation invasive des types `Http*` n'est requise dans `0.2.7`. ### 4.2 Config actuel est explicitement HTTP V1 `config/std.transport.json`, son schema et `ksp-config-lib/src/transport.rs` décrivent un document V1 HTTP : ```text format_version = 1 schema title = standard HTTP Transport additionalProperties = false serde(deny_unknown_fields) format_version != 1 rejeté ``` Décision : **ne pas ajouter silencieusement des clés WebSocket au V1**. `0.2.7` fera évoluer le même composant standard `std.transport` vers un **format V2 explicitement HTTP + WebSocket**, tout en conservant la lecture du V1 HTTP-only pour compatibilité. Config reste propriétaire du parsing/résolution et adapte ensuite vers les settings publics Transport. Shape cible minimale : ```text format_version = 2 retry # HTTP existant, conservé ws_defaults # defaults de session WS KSP, non provider-specific default_profile profiles[] profile_id endpoints[] # HTTP existant, conservé ws_endpoints[] # endpoints WebSocket explicites ``` `ws_endpoints[]` porte seulement les données nécessaires à Transport : nom logique, enabled, provider, cluster, URL et overrides de session éventuellement nécessaires. Les credentials restent résolus par Config comme aujourd'hui ; Transport reçoit un endpoint déjà résolu et doit redacter l'URL dans `Debug`/logs/errors. Le V1 chargé sous `0.2.7` produit une configuration HTTP valide et une collection WebSocket vide. Le V2 devient le format livré par les fixtures Config dès la tranche qui matérialise le WebSocket. ## 5. Audit historique bot3 Archive auditée uniquement comme référence : ```text khadhroony-bot3_v0.5.3-pre.005-fix010.zip ks-onchain-transport/src/standard_ws.rs ks-onchain-transport/src/ws_client.rs ks-onchain-transport/src/ws_pool.rs ks-onchain-transport/src/ws_session.rs consumers/demo et rapports de validation WebSocket ``` Invariants utiles repris : - plusieurs subscriptions sur une session physique ; - plusieurs sessions possibles ; - identifiant local stable distinct de l'identifiant serveur ; - remapping des IDs serveur après reconnect ; - reconnect borné et resubscribe ; - canaris locaux de lifecycle. Choix bot3 explicitement **non repris** : - `Transport -> Config` ; - `tracing` direct ; - rôles HTTP et `max_subscriptions` mélangés au modèle endpoint générique ; - pool/scheduler de sessions sans besoin démontré ; - API d'unsubscribe publique fondée sur l'ID serveur éphémère ; - broadcast unique comme canal principal de notifications data ; - états lifecycle trop grossiers pour les races reconnect/unsubscribe. Le retour historique bot3 sur les flux à fort débit renforce le besoin d'une policy de backpressure explicite et observable ; aucune hypothèse de livraison lossless ne doit reposer sur un canal broadcast qui peut lagger silencieusement. ## 6. Inventaire WebSocket Solana officiel au 2026-08-22 Source d'index : ```text https://solana.com/docs/rpc/websocket ``` L'index courant contient exactement **18 méthodes WebSocket : 9 subscribe + 9 unsubscribe**. ```text accountSubscribe / accountUnsubscribe blockSubscribe / blockUnsubscribe logsSubscribe / logsUnsubscribe programSubscribe / programUnsubscribe rootSubscribe / rootUnsubscribe signatureSubscribe / signatureUnsubscribe slotSubscribe / slotUnsubscribe slotsUpdatesSubscribe / slotsUpdatesUnsubscribe voteSubscribe / voteUnsubscribe ``` Statut observé : ```text 6 paires sans marque unstable/deprecated dans la documentation actuelle 3 paires dont le subscribe est explicitement Unstable : blockSubscribe slotsUpdatesSubscribe voteSubscribe 0 méthode WebSocket de cet index marquée Deprecated ``` Pour KSP, le label interne `Stable` signifie ici « documentée et non marquée unstable/deprecated par la source officielle auditée », pas une garantie de standardisation extérieure supplémentaire. Règles générales documentées : JSON-RPC 2.0 sur connexion persistante ; résultat subscribe numérique ; notifications avec `params.subscription` numérique ; lorsqu'un contexte est présent `context.slot` est fourni et `context.apiVersion` est omis ; les subscriptions qui acceptent `commitment` utilisent `finalized` par défaut. La matrice détaillée initiale est conservée dans `docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md`. ## 7. Points wire sensibles sous `KSP-TRANSPORT-007` ### `accountSubscribe` Conserver : pubkey ; `commitment` ; encodings `base58|base64|base64+zstd|binary|jsonParsed` ; `dataSlice` ; notification `accountNotification` contextualisée. Le type partagé Agave `RpcAccountInfoConfig` contient aussi `min_context_slot`, mais le handler PubSub `v3.1.8` l'ignore explicitement. KSP ne doit donc pas promettre `minContextSlot` comme option WebSocket effective tant que l'upstream ne lui donne pas une sémantique réelle ; cette non-promise doit rester visible dans la compliance pour éviter qu'une option ignorée soit comptée comme supportée. ### `programSubscribe` Conserver : program pubkey ; `commitment` ; `filters` ; mêmes encodings account ; `dataSlice` ; **`withContext` default false**. La documentation actuelle expose `withContext`, mais le code Agave `v3.1.8` lié par la page stocke bien `with_context` alors que le chemin générique de notification audité sérialise un `RpcResponse` contexté. KSP ne doit ni ignorer le paramètre, ni convertir cette incohérence amont en hypothèse stricte. Décision : exposer le flag et rendre le décodeur capable d'accepter la forme documentée non-contextée comme la forme contextée observée. ### `logsSubscribe` Conserver les trois filtres : `all`, `allWithVotes`, `{mentions:[pubkey]}`. La forme `mentions` autorise exactement une adresse dans la documentation actuelle. Conserver `commitment`; notification `signature + err + logs` contextualisée. ### `signatureSubscribe` Conserver `commitment` et `enableReceivedNotification`. Le résultat notification est une union wire : ```text "receivedSignature" OU { "err": null | transaction error } ``` La subscription se termine après la notification terminale ; elle ne doit donc pas être resubscribed après que le runtime KSP a observé cette terminaison. ### `slotSubscribe` / `rootSubscribe` Aucun paramètre. `slotNotification` conserve `slot`, `parent`, `root`; `rootNotification` conserve le root `u64`. ### `blockSubscribe` — Unstable Conserver : filtre `all` ou `{mentionsAccountOrProgram:}` ; commitment limité à `confirmed|finalized` ; encoding `binary|base58|base64|json|jsonParsed` ; `transactionDetails` `full|accounts|signatures|none` ; `maxSupportedTransactionVersion` ; `showRewards`. La méthode nécessite côté validator les flags documentés ; KSP ne les simule pas. La notification conserve `context`, `slot`, `block` nullable et `err` nullable, ainsi que les variantes de block liées à la config. ### `slotsUpdatesSubscribe` — Unstable Conserver les variantes taggées actuelles et leurs champs : ```text firstShredReceived completed createdBank frozen dead optimisticConfirmation root ``` Le runtime doit prévoir une forme `Unknown`/raw bornée pour qu'une variante ajoutée par une version upstream ne ferme pas la session entière avant réaudit KSP. ### `voteSubscribe` — Unstable Aucun paramètre. La méthode nécessite le flag validator documenté et observe des votes gossip pre-consensus. Conserver `votePubkey`, `slots`, `hash`, `timestamp`, `signature`. Agave `v3.1.8` définit `timestamp` comme `Option` : le décodeur KSP doit donc préserver cette optionalité et accepter de manière tolérante les formes wire `omitted/null/value`, sans inventer une valeur. ### `*Unsubscribe` Chaque unsubscribe reçoit l'ID serveur et retourne `true` quand la subscription est supprimée ; le handler Agave audité retourne `InvalidParams` pour un ID inconnu. Cette réalité wire ne devient pas l'API publique KSP : le caller utilise l'ID local/handle stable et la session traduit vers l'ID serveur courant. Le cross-check Agave `v3.1.8` confirme également que `logsSubscribe` impose exactement une adresse pour `mentions`, que `signatureSubscribe` ne consomme que `commitment` et `enableReceivedNotification`, et que `blockSubscribe` applique bien le jeu d'options documenté avec un commitment au moins `confirmed`. Ces constats renforcent la matrice normative sans ajouter d'options non documentées à KSP. ## 8. Dépendances WebSocket candidates Audit au 2026-08-22 : | Candidate | Version auditée | Verdict | Motif | |---------------------|----------------:|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| | `tokio-tungstenite` | `0.30.0` | **retenue** | mature, Tokio-native, TLS rustls, continuité avec bot3 mais réauditée, contrôle de `WebSocketConfig`, client + serveur local de test | | `futures-util` | `0.3.34` | **retenue comme utilitaire** | `StreamExt`/`SinkExt`; features minimales `std,sink` | | `tokio-websockets` | `0.13.3` | alternative viable, non retenue | strict/minimal et performant, mais exige davantage d'assemblage/features et n'apporte pas de besoin fonctionnel supérieur démontré pour cette foundation | | `fastwebsockets` | `0.10.0` | non retenue | plus bas niveau ; peut déléguer davantage de compliance au caller, inutile pour la première foundation KSP | Landing prévu, **pas dans `pre.001`** : ```toml # root [workspace.dependencies] tokio-tungstenite = { version = "^0.30", default-features = false } futures-util = { version = "^0.3", default-features = false } # ksp-onchain-transport-lib tokio-tungstenite = { workspace = true, features = ["connect", "rustls-tls-webpki-roots"] } futures-util = { workspace = true, features = ["std", "sink"] } tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } ``` Le serveur de test local pourra activer `tokio/net` en dev si KSP utilise directement `TcpListener`. `url` n'est pas retenu a priori : l'endpoint KSP peut être validé et passé comme chaîne/request sans ajouter la feature uniquement par habitude bot3. `handshake`/`stream` sont déjà requis transitivement par `connect`/TLS et ne doivent pas être listés sans nécessité directe. Les defaults de `tungstenite::WebSocketConfig` ne sont **pas** des limites Solana normatives. KSP configurera explicitement des plafonds finis pour messages, frames et write buffer. Les valeurs numériques KSP seront figées avec les tests adversariaux de la tranche settings/session, et documentées comme policy locale, jamais comme limite protocolaire Solana. Sources : ```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 https://docs.rs/crate/tokio-websockets/0.13.3 https://docs.rs/crate/fastwebsockets/0.10.0 ``` ## 9. Modèle public Transport retenu ### 9.1 Settings Noms cibles : ```text WsEndpointUrl WsProviderName WsClusterName WsReconnectSettings WsResubscribePolicy WsSessionSettings WsEndpointSettings WsTransportSettings ``` Les wrappers `WsProviderName`/`WsClusterName` sont parallèles aux `Http*` pour éviter une migration publique HTTP dans cette release. Une généralisation future n'est justifiée que si un troisième backend démontre un type commun réellement utile. `WsEndpointUrl` : - accepte seulement `ws://`/`wss://` après validation ; - fournit un accès explicite à la valeur au code de connexion ; - `Debug` et tout diagnostic par défaut sont redacted ; - aucun snapshot public ne contient l'URL complète. `WsSessionSettings` porte les bornes runtime nécessaires : command timeout, close timeout, reconnect, resubscribe, capacités de queues, maximum de subscriptions actives, maximum de requests JSON-RPC en vol, maximum de message/frame/write buffer. Pas de rôle HTTP ni de scheduler. ### 9.2 Cardinalité Contrat : ```text 1 WsEndpointSettings -> 0..N WsSession physiques créées explicitement par caller -> 0..N WsSubscription actives ``` Deux appels de création sur le même endpoint ouvrent deux connexions physiques. Transport ne distribue pas automatiquement les subscriptions entre elles. ### 9.3 Session actor Une tâche actor possède exclusivement : - le stream WebSocket physique ; - le compteur d'IDs JSON-RPC requests ; - la map des requests en vol ; - le registre local des subscriptions ; - le mapping `remote_subscription_id -> local_subscription_id` ; - le lifecycle reconnect/resubscribe ; - les compteurs/snapshot sûrs. Le handle public `WsSession` communique avec cet actor par canal bounded. Aucun caller ne split/manipule directement le socket. ### 9.4 Identités ```text WsSessionId # KSP local, stable pour la vie du handle WsSubscriptionId # KSP local, stable malgré reconnect remote id u64 # strictement runtime/interne, remappable ``` Une `WsSubscription` typée contient l'identité locale, la projection de lifecycle et un receiver bounded de notifications typées. L'ID remote ne devient pas un identifiant métier/public de contrôle. ## 10. State machines retenues ### 10.1 Session ```text Disconnected -> Connecting -> Active -> Failed Active -> Reconnecting -> Active -> Failed -> Closing -> Closed Reconnecting -> Closing -> Closed ``` État public cible : ```text Disconnected | Connecting | Active | Reconnecting { attempt } | Closing | Closed | Failed ``` ### 10.2 Subscription ```text Requested -> Active -> Failed Active -> Resubscribing -> Active -> Failed -> Cancelling -> Closed -> Closed # terminaison serveur connue, ex. signature terminale Requested/Resubscribing -> Cancelling -> Closed ``` État public cible : ```text Requested | Active | Resubscribing | Cancelling | Closed | Failed ``` Le motif terminal est séparé (`Unsubscribed`, `CompletedByServer`, `SessionClosed`, `BackpressureOverflow`, `ProtocolFailure`, etc.) afin de ne pas gonfler l'enum d'états. ## 11. Reconnect et resubscribe ### 11.1 Reconnect Reconnect uniquement pour perte du canal physique : EOF, close inattendue, erreur I/O/TLS/WebSocket read/write. Une erreur RPC applicative de subscribe/unsubscribe n'entraîne pas une reconnexion automatique de la session. Policy : - nombre d'essais fini et configurable ; - backoff exponentiel borné par `initial_backoff`/`max_backoff` ; - **pas de jitter dans `0.2.7`** afin d'éviter une dépendance/randomisation supplémentaire et garder les tests déterministes ; - le budget se réinitialise après retour complet à `Active` ; - exhaustion => session `Failed`, subscriptions terminales avec cause explicite ; - shutdown/close annule immédiatement backoff et interdit toute nouvelle tentative. Les valeurs numériques par défaut sont une policy KSP et seront figées avec `WsSessionSettings` après tests ; elles ne seront pas attribuées à Solana. ### 11.2 Resubscribe Enum public : ```text WsResubscribePolicy::Never WsResubscribePolicy::ActiveSubscriptions ``` Default KSP proposé : `ActiveSubscriptions`. Après perte de connexion : 1. tous les remote IDs deviennent invalides et sont retirés du mapping ; 2. la session émet/incrémente un signal de **continuity gap** ; 3. après reconnect, seules les subscriptions encore désirées actives sont restaurées ; 4. ordre déterministe : `WsSubscriptionId` croissant / ordre de création ; 5. chaque ack remappe un nouvel ID remote ; 6. une erreur de resubscribe marque cette subscription `Failed` mais ne ferme pas les autres si le socket reste sain. KSP **ne garantit pas la continuité lossless** entre la déconnexion et la restauration. Aucun backfill HTTP automatique n'est ajouté à Transport. Les consumers workers/jobs pourront réconcilier plus tard avec leur propre checkpoint/persistence. ### 11.3 Race unsubscribe pendant reconnect La cancellation locale gagne toujours : - marquer la subscription non restaurable avant de traiter les acks tardifs ; - retirer sa spec de la sélection resubscribe ; - si un subscribe/resubscribe distant tardif réussit malgré tout, envoyer un best-effort unsubscribe de son nouvel ID ; - ne jamais repasser la subscription locale à `Active` après cancellation. ## 12. Backpressure et bornes de ressources ### 12.1 Queues - canal commands session : bounded ; - notifications : **queue bounded par subscription** ; - lifecycle/snapshot : `watch`/état partagé compact, pas une file data infinie ; - requests JSON-RPC en vol : map bornée + timeout ; - subscriptions actives : plafond configurable par session. ### 12.2 Overflow notifications Aucun drop silencieux. Si la queue d'une subscription est pleine : 1. compteur overflow incrémenté ; 2. subscription passe `Failed(BackpressureOverflow)` ; 3. best-effort unsubscribe distant si possible ; 4. son receiver se termine avec la cause observable via état final ; 5. les autres subscriptions de la session restent actives. Cette policy isole un consumer lent sans sacrifier toute la connexion et sans prétendre être lossless. ### 12.3 Frames/messages/JSON Configurer des plafonds finis pour : ```text max_message_size max_frame_size max_write_buffer_size max_pending_requests max_active_subscriptions command_queue_capacity notification_queue_capacity ``` La parse JSON ne reçoit donc jamais un message WebSocket arbitrairement grand. Les payloads raw conservés pour losslessness restent eux aussi sous cette borne. ## 13. Cancellation, control frames et shutdown ### 13.1 Control frames Le runtime gère proprement Ping/Pong/Close selon la crate WebSocket retenue. Aucun heartbeat applicatif périodique n'est activé par défaut dans `0.2.7` : la documentation Solana actuelle n'impose pas un ping KSP. Un keepalive configurable sera ajouté seulement si un besoin interop réel est démontré. ### 13.2 Shutdown explicite `WsSession::close().await` doit : 1. passer `Closing` et refuser de nouvelles subscriptions ; 2. annuler reconnect/backoff/resubscribe et requests en vol ; 3. si connecté, best-effort unsubscribe des actives dans un ordre déterministe sous un budget global ; 4. envoyer Close WebSocket ; 5. fermer toutes les subscriptions localement avec raison terminale ; 6. attendre la fin de l'actor sous `close_timeout` ; 7. finir `Closed` même si le peer ne répond pas à temps. Si la session est déjà reconnecting, elle ne se reconnecte jamais seulement pour envoyer des unsubscribe. `Drop` peut déclencher un signal best-effort, mais ne remplace pas l'API async explicite pour les garanties de lifecycle. ## 14. Erreurs et anomalies de protocole Réutiliser lorsque possible les codes HTTP/JSON-RPC génériques existants : ```text invalid_settings invalid_rpc_parameters json_encode_failed json_decode_failed json_rpc_protocol_invalid rpc_application_error timeout ``` Codes WS cibles minimaux : ```text ws_connection_failed ws_protocol_error ws_session_closed ws_subscription_failed ws_backpressure_overflow ``` Policy d'anomalies : - JSON/message WebSocket structurellement invalide : erreur protocole session, fermeture/reconnect selon budget ; - notification JSON valide mais payload typed invalide pour une subscription connue : fail de cette subscription, pas de teardown automatique des autres ; - notification valide pour ID remote inconnu/stale : compteur + log safe, drop ; - method notification incompatible avec la subscription connue : fail de cette subscription ; - réponse JSON-RPC d'erreur à subscribe/unsubscribe : `rpc_application_error`, pas reconnect implicite. ## 15. Observabilité et secrets Tous les événements KSP passent par `ksp-logging-lib`. Autorisés dans logs/snapshots : ```text endpoint logical name provider label cluster label WsSessionId WsSubscriptionId subscription kind state transitions active/pending counts reconnect attempt / exhaustion continuity_gap_count overflow_count error code qualifié ``` Interdits : ```text URL WebSocket complète query token/credential Authorization/header sensible raw notification arbitraire payload massif remote request body complet ``` Snapshot public cible : état session, endpoint name/provider/cluster, counts, reconnect state, continuity gaps, subscription IDs locaux + kinds + states. L'ID serveur peut rester interne ; un booléen `remote_bound` suffit pour diagnostiquer le binding sans faire croire à sa stabilité. Tests dédiés : URL `wss://user:pass@host/path?api-key=secret` ne doit apparaître ni dans `Debug`, ni erreurs, ni logs capturés, ni snapshots. ## 16. DTOs et losslessness Réutiliser les DTOs HTTP lorsque leur sémantique wire est réellement identique : ```text SolanaCommitment SolanaRpcContext SolanaRpcResponse SolanaAccountEncoding SolanaDataSliceConfig SolanaProgramAccountFilter SolanaAccount SolanaKeyedAccount block/transaction DTOs compatibles SolanaWireField ``` Définir des DTOs WS dédiés pour : enveloppes notification, lifecycle, subscription kind/config, logs, signature union, slot/root/slotsUpdates/vote, block update et toute forme où le wire diffère. `SolanaRpcContext.api_version` est déjà optionnel ; il peut donc représenter les contexts PubSub où la documentation indique que `apiVersion` est omis. Pour unstable/évolutif, préférer des enums avec fallback `Unknown { type_name, raw }` borné plutôt qu'un rejet session-wide d'un variant upstream nouveau. ## 17. API générique provider-specific Le moteur interne doit encoder une spec générique `subscribe_method + unsubscribe_method + params + decoder`, afin que `0.2.8` puisse réutiliser la session. **Aucune API publique raw provider-extension n'est promise dans `0.2.7`.** Elle sera décidée après audit Helius de `0.2.8`. Cela évite de figer trop tôt une escape hatch qui deviendrait le contrat principal et contournerait les wrappers typés standards. ## 18. Tests déterministes attendus Le test runtime utilise un serveur WebSocket local déterministe, sans provider externe : ```text handshake + close subscribe -> notification -> unsubscribe plusieurs subscriptions sur une session deux sessions physiques sur la même URL IDs locaux stables / IDs distants remappés erreur RPC subscribe/unsubscribe JSON/message malformé notification method/payload incompatible unknown/stale remote subscription id message/frame oversized connexion interrompue reconnect borné + exhaustion + reset budget resubscribe déterministe unsubscribe pendant reconnect + ack stale signature terminale => closed, jamais resubscribe shutdown active/pending/reconnecting backpressure : overflow d'une sub n'impacte pas les autres limits active subscriptions/pending requests program notification contexted + non-contexted slotsUpdates unknown variant vote timestamp omitted/null/value URL/credentials absents de Debug/error/log/snapshot warning centralisé pour unstable à la création, pas à chaque notification ``` Le serveur local est un fixture de Transport ; aucune application Tauri n'est modifiée pour tester WebSocket. ## 19. Smoke live opt-in Après stabilisation : un test `#[ignore]` Transport pur sur Devnet, préférentiellement une subscription stable simple (`slotSubscribe`) : ```text connect slotSubscribe attendre une notification sous timeout unsubscribe close ``` Un endpoint override pourra être fourni par l'environnement Config seulement dans le smoke de composition séparé si celui-ci est réellement utile. Les unstable ne deviennent pas gates live car les validators publics peuvent les désactiver. Un rate-limit, refus provider ou indisponibilité externe ne constitue pas automatiquement une régression locale. ## 20. Threat model synthétique | Risque | Défense retenue | Preuves attendues | |--------------------------------------------------|----------------------------------------------------------------------|------------------------------| | URL/token fuit dans diagnostics | type URL redacted + snapshots sans URL + messages d'erreur qualifiés | tests Debug/error/log | | message/frame géant | limites WebSocket explicites avant JSON | tests oversized | | JSON/allocation non bornée | taille message bornée + DTOs ciblés/raw borné | adversarial fixtures | | consumer lent | queue par sub bounded, fail local explicite | overflow isolé | | reconnect infini | budget fini + backoff borné | exhaustion test | | notifications manquées pendant reconnect | continuity gap observable, aucune promesse lossless | lifecycle test/docs | | resubscribe stale après unsubscribe | ID local desired-state, cancellation gagne | race test | | remote ID réutilisé/remappé | mapping éphémère interne | reconnect/remap tests | | subscription orpheline | registry actor + close explicite + cleanup terminal | shutdown tests | | pending request leak | map bornée + timeout + purge reconnect/close | timeout tests | | task leak | actor unique joinable + close timeout | repeated connect/close tests | | shutdown bloqué | budget global + close timeout | hostile peer fixture | | unstable upstream variant casse session | typed enum + Unknown/raw borné | unknown variant fixture | | logique worker/persistence glisse dans Transport | aucun backfill/persistence/checkpoint | dependency/public API audit | ## 21. Hors périmètre confirmé ```text Helius LaserStream / provider params -> 0.2.8 Yellowstone gRPC -> 0.2.9 provider Yellowstone commercial -> plus tard shred/deshred/pre-execution -> plus tard off-chain prices -> 0.2.10 Wallet / 2FA / .kspwallet -> hors 0.2.7 Store/persistence -> 0.3.x Program decode/materialization -> plus tard frontend réseau direct -> interdit pool/scheduler automatique de sessions -> différé backfill HTTP automatique sur gap WS -> workers/jobs futurs public raw provider-extension API -> gate 0.2.8 application heartbeat/ping périodique -> différé sans besoin démontré ``` ## 22. Forecast recalibré L'inventaire officiel n'impose que 9 familles de subscriptions, mais le lifecycle concurrent est plus coûteux que le forecast initial. Le gate reste **positif sans split de release**, à condition de granulariser les tranches au lieu de compresser le moteur et les wrappers. ```text pre.001 audit interne/externe + matrice 18 méthodes + bot3 + dependencies + threat model + plan/sizing pre.002 settings WS Transport + URL redaction + IDs/states/snapshots + tests de settings pre.003 std.transport V2 HTTP+WS + backward V1 + schema/fixtures + Config -> WsTransportSettings pre.004 deps tokio-tungstenite/futures-util + actor physique + handshake/read/write + pending JSON-RPC + serveur local pre.005 limites frame/message/request + control frames + cancellation/close/shutdown + adversarial socket tests pre.006 registry subscriptions + IDs locaux + generic subscribe/unsubscribe engine + channels typed bounded pre.007 reconnect borné + resubscribe déterministe + continuity gap + races unsubscribe/reconnect pre.008 backpressure per-sub + overflow/limits + leak/lifecycle adversarial tests pre.009 wrappers stable lot A : account + program + logs, DTOs/options/KSP-TRANSPORT-007 pre.010 wrappers stable lot B : signature + slot + root, terminaison signature/KSP-TRANSPORT-007 pre.011 unstable : block + slotsUpdates + vote, warnings + fallbacks wire/KSP-TRANSPORT-007 pre.012 compliance 18/18 + canaries public API + composition Config + régressions HTTP pre.013 smoke live opt-in + README/USAGE + cargo tree/duplicates + dependency audit final pre.014 validation workspace finale + docs/compliance + prompt 0.2.8 rel.001 publication stable stricte ``` Chaque tranche reste scindable si son implémentation réelle dépasse le budget nominal de 15–20 minutes. Le nombre `pre.014` n'est pas une deadline normative. ## 23. Gates de clôture `0.2.7` La release ne peut passer stable que si : - l'inventaire officiel est réaudité et reste rapproché par noms exacts ; - 18/18 opérations standard de l'index ciblé ont un statut explicite ; - 9/9 subscribe ont leur wrapper KSP promis et 9/9 unsubscribe sont couverts via handles/registry ; - options et variantes wire sont conservées sous `KSP-TRANSPORT-007` ; - la cardinalité endpoint -> N sessions -> N subscriptions est démontrée ; - reconnect/resubscribe/backpressure/cancellation/shutdown sont bornés et testés ; - continuity gaps sont observables sans promesse lossless ; - secrets/URLs ne fuitent pas ; - Config -> Transport reste l'unique direction d'adaptation ; - aucune dépendance Store/Program/Wallet/Config/tracing direct n'apparaît dans Transport ; - HTTP 52+14 ne régresse pas ; - tests déterministes et workspace sont verts ; - smoke live retenu reste opt-in ; - README/USAGE, matrice finale, graphes Cargo et prompt `0.2.8` sont synchronisés. ## 24. Validation opérateur requise pour `pre.001` Après application de ce gate documentaire/versionné : ```bash cargo fmt --all python3 scripts/audit_rust_workspace_rules.py cargo check --workspace cargo clippy --workspace --all-targets ``` `cargo test --workspace` n'est pas imposé par cette tranche documentaire d'ouverture tant qu'aucun Rust runtime n'est ajouté, mais reste autorisé comme checkpoint opérateur.