# Plan `0.2.8` — Helius LaserStream WebSocket > **Statut : `pre.004` et ses deux fixes sont validés. Le premier checkpoint de `0.2.8-pre.005` a révélé quatre erreurs de type dans les canaris JSON et une visibilité `pub(crate)` injustifiée pour des helpers utilisés uniquement par le module/tests. `0.2.8-pre.005-fix.001` est préparé pour corriger ces deux points et durcir l'audit des accès `super::PrivateItem` / `crate::VisibleItem`.** ## 1. Objet, base et état courant `0.2.8` étend le moteur WebSocket publié par `0.2.7` avec la surface Helius LaserStream WebSocket réellement documentée, sans créer un second moteur/session actor et sans confondre WebSocket avec LaserStream gRPC. Base autoritaire auditée à l'ouverture : ```text v0.2.7 workspace.package.version initial = 0.2.7 deltas/0.2.7/rel.001.md présent prompts/013-V0_2_8_START_PROMPT.md présent ``` État des livraisons : ```text pre.001 gate audit/sizing Helius pre.001-fix.001 séparation des façades + fermeture gate Cargo pre.001-fix.002 forecast compact + namespace LaserStream WS/gRPC pre.002 socle protocolaire validé pre.002-fix.001 correction Clippy + normalisation structure documentaire validées pre.003 six familles standard Helius validées pre.004 Config V2 Helius validé pre.004-fix.001 redaction segmentaire + couverture Devnet Helius validées pre.004-fix.002 provenance composée validée pre.005 contrat typed transactionSubscribe/unsubscribe ; fix requis après checkpoint pre.005-fix.001 correction types de canaris + visibilité/tests/règles préparée workspace.package.version courant = 0.2.8-pre.5.fix.1 commit attendu = v0.2.8-pre.005-fix.001 aucun tag prerelease ``` Le checkpoint opérateur de `pre.003` est intégralement vert : `cargo fmt`, audit Rust, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, tests Transport et `cargo test --workspace`. Les six familles standard Helius sont donc acquises sans dette de gate avant l'ouverture de Config Helius en `pre.004`. ## 2. Forecast souple courant Le forecast suit le format historique des plans KSP `0.2.5` et `0.2.7` : un bloc compact, directement révisable, qui porte aussi l'état courant des tranches. Les `fix.*` sont rattachés à leur prerelease d'origine au lieu d'être présentés comme des tranches planifiées autonomes. ```text pre.001 DONE — audit Helius actuel + matrice + architecture + threat model + dependencies + sizing fix.001 DONE — séparation des façades standard/Helius + moteur unique + gate Cargo fermé fix.002 DONE — forecast normalisé + namespace LaserStream WebSocket/gRPC clarifié pre.002 DONE — socle protocolaire : WsProtocolKind::HeliusLaserStream + façades standard/Helius + connexion physique partagée + guards, sans duplication de l'actor fix.001 DONE — correction Clippy `implicit_return` + audit/normalisation structure docs pre.003 DONE — surface Helius standard supportée : account/logs/program/root/signature/slot + absence typée de block/slotsUpdates/vote sur Helius + non-régression standard 9/9 pre.004 DONE — Config V2 helius_laserstream + schema/fixtures + mapping Config -> Transport + secret/redaction Helius mainnet/devnet validés fix.001 DONE — redaction segmentaire corrigée + représentation Devnet Helius ajoutée fix.002 DONE — provenance composée `DocumentLiteral` + `EnvironmentProcess` corrigée pre.005 FIX REQUIRED — transactionSubscribe request typed + filters/options/tokenAccounts + transactionUnsubscribe + bounds 50k + maxSupportedTransactionVersion conditionnel ; live handle différé à pre.006 fix.001 PREPARED — assertions Vec/Value corrigées + helpers test-only privés + audit super/crate durci pre.006 transactionNotification + actor integration + reconnect/resubscribe/unsubscribe races + late notifications + backpressure ciblée pre.007 heartbeat Helius WebSocket/idle + timers + interaction reconnect/control frames/shutdown pre.008 provider adversarial lifecycle + capability guards + payload/backpressure + security/redaction pre.009 compliance Helius WebSocket + non-régressions Solana standard 18/18 + HTTP 52/14 + Config/API/dependency-firewall canaries pre.010 smoke Helius WebSocket live opt-in si stratégie sûre + README/USAGE + cargo tree direct/duplicates final pre.011 validation workspace finale + fermeture plan/matrice/indexes + prompt 0.2.9 rel.001 publication stable stricte ``` Règles de lecture et de recalibrage : - une ligne `pre.NNN` représente une tranche de travail planifiée ; ses `fix.MMM` éventuels sont regroupés dessous et n'augmentent pas artificiellement le forecast initial ; - l'état (`DONE`, puis si utile un état intermédiaire explicite) peut être mis à jour directement dans ce bloc lorsque la tranche évolue ; - chaque nouvelle prerelease vise nominalement environ **15–20 minutes** de travail effectif ; - un `fix` reste insérable après n'importe quelle prerelease ; - une tranche qui cache plusieurs problèmes indépendants ou dépasse réellement le budget est scindée ; - une nouvelle ambiguïté normative peut ajouter une prerelease ; - `pre.011` n'est ni une deadline ni un critère de clôture : `pre.012+` reste autorisé ; - les détails d'exécution restent dans les deltas ; ce bloc sert de forecast courant et de vue d'état, pas de changelog détaillé. Critères de split déjà identifiés : notification decoding multiforme non documenté, refactor générique du lifecycle nécessaire, évolution Helius normative en cours de release, ou stratégie live nécessitant une surface d'intégration séparée. ## 3. Sources, baseline et preuves d'ouverture ### 3.1 Sources internes relues Les sources internes obligatoires du prompt ont été relues depuis la base stable, notamment : ```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/RULES_DOCUMENTATION.md docs/rules/FILE_CONTRACTS.md docs/rules/VERSION_WORKFLOW.md docs/rules/PROMPT_STRUCTURE.md docs/architecture/000-README.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/008-V0_2_1_ONCHAIN_HTTP_PLAN.md docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_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 docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md deltas/0.2.7/rel.001.md docs/IDEAS.md crates/ksp-onchain-transport-lib/Cargo.toml crates/ksp-onchain-transport-lib/README.md crates/ksp-onchain-transport-lib/USAGE.md crates/ksp-onchain-transport-lib/src/** crates/ksp-onchain-transport-lib/tests/** crates/ksp-config-lib/Cargo.toml crates/ksp-config-lib/src/transport.rs crates/ksp-config-lib/unit_tests/transport.rs config/std.transport.json config/schemas/std.transport.schema.json .env.example ``` Décisions de workflow conservées : - `pre.001` reste audit + brainstorming + sizing ; - `pre.002-fix.001` corrige la conformité Clippy et normalise l’organisation documentaire sans élargir le scope runtime ; - une prerelease non-fix synchronise toujours Cargo ; - un fix qui modifie du code/test consommé par le build synchronise Cargo selon `VERSION_WORKFLOW.md` ; - `ROADMAP.md` reste global ; les détails vivent dans plan/validation/deltas ; - `CHANGELOG.md` reste principalement réservé à la publication stable. ### 3.2 Baseline opérateur sur `v0.2.7` Le log opérateur fourni avant ouverture de `0.2.8` enregistre : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py OK / clean cargo check --workspace OK cargo clippy --workspace --all-targets OK cargo test --workspace OK ``` Les tests live/bench explicitement opt-in restent ignorés par défaut, conformément à leurs contrats. ### 3.3 Graphes fournis après application de `pre.001` État observé : ```text ksp-onchain-transport-lib = 0.2.8-pre.1 futures-util = 0.3.34 tokio = 1.53.1 tokio-tungstenite = 0.30.0 reqwest = 0.13.4 new Helius dependency = aucune ``` `cargo tree --duplicates` ciblé montre uniquement des doublons transitifs attendus dans le graphe courant : ```text syn 2.0.119 / 3.0.3 webpki-roots 0.26.11 / 1.0.9 ``` Aucun doublon direct introduit par `0.2.8-pre.001`, aucun SDK Helius et aucune inversion de dépendance KSP ne sont observés. ## 4. État hérité de `v0.2.7` ### 4.1 HTTP à ne pas régresser ```text 52/52 méthodes HTTP courantes typées 14/14 méthodes historiques Deprecated/Removed conservées KSP-TRANSPORT-007 appliqué write submissions sans resend après dispatch ambigu ``` ### 4.2 WebSocket standard publié Le runtime stable expose neuf familles : ```text Account Block Logs Program Root Signature Slot SlotsUpdates Vote ``` Partition : ```text stable : Account, Logs, Program, Root, Signature, Slot unstable : Block, SlotsUpdates, Vote ``` Le moteur possède déjà : actor unique propriétaire du socket, pending JSON-RPC borné, queues bornées, local IDs stables, remote IDs internes/remappables, reconnect fini, resubscribe déterministe, cleanup late ACK, backpressure isolée, `continuity_gap_count`, one-shot Signature et `close().await` borné. ### 4.3 Contrats publics utiles `WsProtocolKind` est `#[non_exhaustive]` et ne contient en `v0.2.7` que : ```text SolanaStandard -> "solana_standard" ``` `WsEndpointSettings` fournit déjà : ```text name enabled provider cluster protocol kind WsEndpointUrl redacted WsSessionSettings ``` `WsSession` est un handle `Clone` vers un actor unique et les wrappers standard sont implémentés directement sur ce type. Cette réalité explique la correction de `pre.001-fix.001` : la façade Helius ne doit pas être le même type public surchargé de méthodes incompatibles. ### 4.4 Config V2 ```text format_version = 2 retry ws_defaults default_profile profiles[].endpoints[] profiles[].ws_endpoints[].kind ``` Après `pre.004`, le schema V2 et l'adapter acceptent : ```text solana_standard helius_laserstream ``` Le document canonique `config/std.transport.json` reste volontairement une baseline publique Solana sans credential provider. La représentation Helius est portée par la fixture Config et `config/examples/std.transport.example.json`, afin de prouver le contrat sans introduire une fausse clé Helius dans le profil runtime par défaut. ## 5. Audit Helius officiel du 2026-08-23 ### 5.1 Sources primaires utilisées ```text https://www.helius.dev/docs/rpc/websocket https://www.helius.dev/docs/api-reference/rpc/websocket-methods https://www.helius.dev/docs/api-reference/rpc/websocket/transactionsubscribe https://www.helius.dev/docs/rpc/websocket/transaction-subscribe https://www.helius.dev/docs/rpc/websocket/token-account-filtering https://www.helius.dev/docs/api-reference/rpc/websocket/accountsubscribe https://www.helius.dev/docs/rpc/websocket/account-subscribe https://www.helius.dev/docs/api-reference/rpc/websocket/programsubscribe https://www.helius.dev/docs/api-reference/rpc/websocket/slotsupdatessubscribe https://www.helius.dev/docs/faqs/websockets https://www.helius.dev/docs/api-reference/rpc/websocket/llms.txt https://www.helius.dev/docs/llms.txt ``` Les documents `llms.txt` servent d'index et de source secondaire. Lorsqu'ils divergent d'une page de référence dédiée ou de la page exhaustive des méthodes, la divergence est enregistrée. ### 5.2 Terminologie et endpoints ```text nom produit courant = LaserStream WebSocket ancien nom = Enhanced WebSockets, intégré à LaserStream WebSocket mainnet = wss://mainnet.helius-rpc.com/?api-key=... devnet = wss://devnet.helius-rpc.com/?api-key=... credential = api-key en query URL, Secret Gatekeeper beta = hors 0.2.8 LaserStream gRPC = distinct, hors 0.2.8 ``` ### 5.3 Inventaire provider retenu ```text standard supporté : account, logs, program, root, signature, slot Helius extension : transactionSubscribe / transactionUnsubscribe non supporté : block, slotsUpdates, vote ``` `transactionUnsubscribe` est documenté dans la référence `transactionSubscribe` avec : ```text method = transactionUnsubscribe params = [remoteSubscriptionId] result = true ``` La référence précise que quelques notifications in-flight peuvent encore arriver après l'unsubscribe. ### 5.4 Divergence `slotsUpdatesSubscribe` Les sources Helius se contredisent : - `websocket-methods` classe `slotsUpdatesSubscribe`/`slotsUpdatesUnsubscribe` parmi les méthodes unstable non supportées ; - la page individuelle indique que la méthode est unstable et peut ne pas être supportée ; - `websocket/llms.txt` la place aussi dans une section « Stable, Helius-supported ». Décision KSP : ```text HeliusLaserStreamWsSession n'expose pas SlotsUpdates ``` Une validation interne garde aussi un rejet avant I/O si un descriptor incompatible atteint le moteur par un chemin interne ou de compatibilité. ### 5.5 `transactionSubscribe` Filtre courant : ```text vote Option failed Option signature Option accountInclude Option max 50_000 accountExclude Option max 50_000 accountRequired Option max 50_000 tokenAccounts none | balanceChanged | all ``` Options : ```text commitment processed | confirmed | finalized encoding base58 | base64 | jsonParsed transactionDetails full | signatures | accounts | none showRewards bool maxSupportedTransactionVersion integer ``` `maxSupportedTransactionVersion` est requis lorsque `transactionDetails` vaut `accounts` ou `full`. Notification : ```text method = transactionNotification ``` La documentation ne détaille pas avec la même précision chacun des quatre modes de `transactionDetails`. KSP ne doit pas inventer ces formes : types précis pour les formes prouvées et fallback borné uniquement si nécessaire. ### 5.6 `accountSubscribe` / `programSubscribe` et `notifyOn` `notifyOn` est documenté mais marqué : ```text deprecated no-op depuis Agave 4.2 suppression future annoncée ``` Décision : ne pas l'ajouter aux DTOs KSP. Les pages continuent à employer « enhanced/filtered accountSubscribe » sans publier un wire provider-specific supplémentaire assez précis pour une API typed. Ce point reste reporté explicitement. ## 6. Matrice normative Helius WebSocket | Famille | Paire | Classe | Support Helius retenu | Surface publique Helius | Paramètres/options utiles | Notification | Stratégie KSP/test | |---------------------|-----------------------------------------------------|------------------|-----------------------|-------------------------|------------------------------------|---------------------------|----------------------------------------------------| | `account` | `accountSubscribe` / `accountUnsubscribe` | Solana standard | Oui | présente | contrat standard ; `notifyOn` omis | `accountNotification` | DTO/wire standard partagé | | `block` | `blockSubscribe` / `blockUnsubscribe` | Solana unstable | **Non** | **absente** | aucune émission provider | théorique | façade Helius sans méthode + guard interne | | `logs` | `logsSubscribe` / `logsUnsubscribe` | Solana standard | Oui | présente | contrat standard | `logsNotification` | DTO/wire standard partagé | | `program` | `programSubscribe` / `programUnsubscribe` | Solana standard | Oui | présente | contrat standard ; `notifyOn` omis | `programNotification` | DTO/wire standard partagé | | `root` | `rootSubscribe` / `rootUnsubscribe` | Solana standard | Oui | présente | aucun paramètre | `rootNotification` | wrapper partagé | | `signature` | `signatureSubscribe` / `signatureUnsubscribe` | Solana standard | Oui | présente | one-shot conservé | `signatureNotification` | wrapper partagé + terminal close | | `slot` | `slotSubscribe` / `slotUnsubscribe` | Solana standard | Oui | présente | aucun paramètre | `slotNotification` | wrapper partagé | | `slotsUpdates` | `slotsUpdatesSubscribe` / `slotsUpdatesUnsubscribe` | Solana unstable | **Non** | **absente** | aucune émission provider | théorique | façade Helius sans méthode + divergence documentée | | `vote` | `voteSubscribe` / `voteUnsubscribe` | Solana unstable | **Non** | **absente** | aucune émission provider | théorique | façade Helius sans méthode + guard interne | | `heliusTransaction` | `transactionSubscribe` / `transactionUnsubscribe` | Helius extension | Oui | présente | filtres/options/`tokenAccounts` | `transactionNotification` | DTOs Helius + actor partagé | Statut : ```text SolanaStandardWsSession : 9 familles / 18 opérations standard HeliusLaserStreamWsSession : 6 familles standard supportées + 1 famille Helius ``` La compliance standard KSP reste **18/18** pour `SolanaStandard`. ## 7. Architecture et frontières de protocole La première version du plan prévoyait un unique `WsSession` public exposant toutes les méthodes standard et une matrice de capabilities rejetant à l'exécution les méthodes incompatibles avec Helius. Cette approche est remplacée par une séparation plus forte : **les méthodes impossibles pour un protocole doivent être absentes de sa façade publique**. Architecture cible : ```text moteur physique commun WsSession actor/socket/reconnect/queues pending/remapping/backpressure/close │ ┌───────────────┴────────────────┐ │ │ ▼ ▼ SolanaStandardWsSession HeliusLaserStreamWsSession façade protocole standard façade protocole Helius │ │ 9 familles standard 6 familles communes + transaction + policy heartbeat ``` Principe : ```text ce qui est dupliqué/séparé : façade publique de protocole méthodes disponibles policy/capabilities du protocole DTOs provider-specific lorsque le wire diverge heartbeat/provider behavior ce qui reste partagé : socket physique actor unique command queue / pending JSON-RPC WsSubscription local/remote IDs et remapping reconnect/resubscribe machinery backpressure shutdown snapshots/redaction DTOs wire réellement identiques ``` Aucun `HeliusWsClient`, aucun second actor et aucune copie du lifecycle physique ne sont autorisés. ### 7.1 Surface `SolanaStandardWsSession` La façade standard expose exactement les neuf familles acquises en `0.2.7` : ```text account block logs program root signature slot slotsUpdates vote ``` Les 18 opérations standard restent donc présentes et testées : 9 subscribe + 9 unsubscribe. ### 7.2 Surface cible `HeliusLaserStreamWsSession` À terme dans `0.2.8`, la façade Helius doit exposer uniquement les familles de subscription suivantes : ```text account logs program root signature slot transaction ``` `pre.003` matérialise exactement les six familles standard ; `transaction` reste réservé aux tranches provider-specific ultérieures du forecast. La façade Helius **n'expose jamais dans le scope courant** : ```text block slotsUpdates vote ``` L'objectif est qu'un appel du genre : ```text helius.block_subscribe(...) ``` soit absent de l'API Helius et donc impossible à écrire contre la façade Helius, plutôt que simplement rejeté après construction d'une requête. La validation runtime avant I/O reste conservée en défense en profondeur pour les constructeurs, descriptors internes et chemins de compatibilité. ### 7.3 Compatibilité de `WsSession` publié en `0.2.7` `WsSession` est déjà public et ses wrappers standard sont déjà stabilisés. `0.2.8` ne les supprime pas et ne casse pas les consommateurs existants. Décision cible : ```text WsSession reste le handle/moteur physique partagé conserve la compatibilité publique standard publiée en 0.2.7 ne devient pas l'entrée publique Helius générique SolanaStandardWsSession nouvelle façade explicite/recommandée pour le protocole standard délègue au même WsSession HeliusLaserStreamWsSession nouvelle façade Helius contient/délègue à un WsSession privé n'expose aucun getter/into_inner permettant de contourner sa surface en 0.2.8 ``` Le chemin public historique `WsSession::connect` doit rester compatible avec `SolanaStandard`. Lors de l'introduction de `HeliusLaserStream`, il ne doit pas permettre d'obtenir un `WsSession` générique Helius puis d'appeler une méthode standard non supportée. Le socle de `pre.002` doit donc extraire/réutiliser un chemin de connexion physique interne partageable, tout en gardant un garde protocolaire sur les constructeurs publics. ### 7.4 DTOs communs versus DTOs Helius Les six familles communes réutilisent les DTOs standard lorsque le wire et la sémantique sont réellement identiques : ```text account logs program root signature slot ``` Ne pas créer artificiellement `HeliusAccountNotification`, `HeliusLogsNotification`, etc. si ces types seraient des copies strictes des types standard. Créer des types Helius distincts dès que le contrat diverge ou n'existe pas en standard : ```text HeliusTransactionSubscribeFilter HeliusTokenAccountsMode HeliusTransactionEncoding HeliusTransactionSubscribeOptions HeliusTransactionNotification / formes prouvées ``` Une option provider-specific ne doit jamais être injectée dans un DTO Solana standard pour éviter une duplication de façade. ### 7.5 Kind de subscription et registry interne `WsSubscriptionKind` appartient au lifecycle/snapshot commun et non à la capacité d'invocation publique. Il peut être étendu de manière `#[non_exhaustive]` avec une variante provider-explicite pour `transaction` si cela minimise la rupture du snapshot existant. Le point obligatoire n'est pas de dupliquer cet enum à tout prix ; le point obligatoire est que : ```text la façade Helius ne puisse pas invoquer Block/SlotsUpdates/Vote la façade standard ne puisse pas invoquer HeliusTransaction le registry/actor interne reste unique et provider-neutral ``` Si `pre.002` montre qu'un descriptor interne distinct rend le snapshot plus propre sans rupture publique, cette forme peut être retenue. Aucun refactor ne doit toutefois remplacer une séparation de façade simple par une hiérarchie générique lourde. ### 7.6 Réalisation de `pre.002` Le socle concret retient une séparation minimale sans dupliquer les wrappers wire ni l'actor : ```text WsSession::connect(endpoint) -> accepte uniquement WsProtocolKind::SolanaStandard -> compatibilité publique 0.2.7 conservée SolanaStandardWsSession::connect(endpoint) -> guard SolanaStandard -> WsSession::connect_for_protocol(...) interne -> même connect_physical / même run_ws_session_actor -> délègue les 9 wrappers standard existants HeliusLaserStreamWsSession::connect(endpoint) -> guard HeliusLaserStream -> WsSession::connect_for_protocol(...) interne -> même connect_physical / même run_ws_session_actor -> lifecycle seulement en pre.002 -> aucune subscription Helius exposée avant pre.003 ``` `WsProtocolKind` possède désormais : ```text SolanaStandard -> solana_standard HeliusLaserStream -> helius_laserstream ``` Les mismatches de constructeur sont rejetés avec `ERROR_CODE_INVALID_SETTINGS` et des contextes sûrs `expected_protocol` / `actual_protocol`, avant toute tentative réseau. Aucune URL ni credential n'entre dans cette erreur. La façade Helius conserve son `WsSession` interne privé et ne fournit ni `inner()` ni `into_inner()`. Des canaries `compile_fail` verrouillent l'absence de `block_subscribe` et de l'escape hatch à ce stade. Une canary source confirme aussi que le module de façade ne contient ni second `tokio::spawn`, ni `tokio_tungstenite`, ni `WsSessionCommand`. La façade standard délègue déjà les neuf familles existantes sans recopier leurs encoders/decoders : ```text account block logs program root signature slot slotsUpdates vote ``` La façade Helius a reçu les six délégations communes en `pre.003`, après que `pre.002` a volontairement limité son scope au socle protocolaire et aux guards. ### 7.7 Réalisation de `pre.003` `pre.003` ajoute uniquement les six wrappers standard que la matrice Helius autorise : ```text accountSubscribe / accountUnsubscribe programSubscribe / programUnsubscribe logsSubscribe / logsUnsubscribe signatureSubscribe / signatureUnsubscribe slotSubscribe / slotUnsubscribe rootSubscribe / rootUnsubscribe ``` Aucun encoder, decoder ou DTO Helius parallèle n'est créé : chaque méthode de `HeliusLaserStreamWsSession` délègue au même wrapper `WsSession` standard déjà validé en `0.2.7`. Pour éviter que le module de façade devienne un fichier fourre-tout, les implémentations sont rangées auprès de leur propriétaire wire : ```text ws_accounts.rs -> account / program, façades standard + Helius ws_transactions.rs -> logs / signature, façades standard + Helius ws_cluster.rs -> root / slot, plus unstable standard uniquement ws_blocks.rs -> block, façade standard uniquement ws_protocol_session.rs -> lifecycle des façades + accès crate-private au moteur partagé ``` `block_subscribe`, `slots_updates_subscribe` et `vote_subscribe` restent absents du type Helius et sont verrouillés par doctests `compile_fail`. L'accès interne au `WsSession` physique est `pub(crate)` seulement ; aucun `inner()`/`into_inner()` public n'est introduit. Une fixture locale Helius dédiée vérifie les six méthodes subscribe/unsubscribe et leurs paramètres exacts ; les tests standard existants restent propriétaires du décodage des notifications. `transactionSubscribe`, Config Helius et heartbeat restent hors scope de `pre.003`. ### 7.8 Protocol kind et namespace LaserStream Retenir pour **WebSocket uniquement** : ```text WsProtocolKind::HeliusLaserStream as_str() = "helius_laserstream" provider metadata attendu = "helius" Config owner = profiles[].ws_endpoints[].kind façade publique = HeliusLaserStreamWsSession ``` `helius_laserstream` n'est donc pas un nom de transport générique Helius : il est possédé par le namespace WebSocket (`WsProtocolKind` / `ws_endpoints`). Dans toute prose, log safe metadata, documentation ou API où ce contexte n'est pas déjà évident, employer **Helius LaserStream WebSocket** et non le raccourci ambigu « Helius LaserStream ». Le futur **Helius LaserStream gRPC** reste un backend/provider distinct, hors `0.2.8`. Il ne doit jamais être représenté par `WsProtocolKind`, `WsEndpointSettings`, `HeliusLaserStreamWsSession` ni `ws_endpoints`. Son éventuel protocol kind, discriminateur Config et façade seront décidés par l'audit de la release gRPC concernée ; ils devront porter un ownership gRPC explicite et ne pourront pas réutiliser le contrat WebSocket par simple alias de nom produit. Cette règle permet de conserver le code court `helius_laserstream` dans son conteneur WS sans créer d'ambiguïté future entre les deux produits portant le nom LaserStream. ### 7.9 Connexion et propriété du moteur Cible de `pre.002` : ```text un seul code de création/spawn de l'actor physique un seul type de socket physique un seul registry pending/subscriptions un seul lifecycle reconnect/backpressure/shutdown ``` Les façades délèguent à ce moteur commun. Le mécanisme précis d'extraction du chemin physique est choisi en `pre.002` à partir du code réel, avec les invariants suivants : ```text pas de clone du run_ws_session_actor pas de second WsSessionCommand pas de second snapshot model pas de public escape hatch Helius -> raw standard WsSession ``` ### 7.10 Capability validation defense-in-depth La séparation de façade est la première barrière. Une matrice interne reste utile : ```text SolanaStandard: Account Block Logs Program Root Signature Slot SlotsUpdates Vote -> allowed HeliusTransaction -> rejected HeliusLaserStream: Account Logs Program Root Signature Slot HeliusTransaction -> allowed Block SlotsUpdates Vote -> rejected ``` Cette matrice sert aux guards de constructor/descriptor, aux canaries et à la protection interne ; elle ne remplace plus la séparation de l'API publique. ### 7.11 Heartbeat/idle ownership Helius annonce un timeout d'inactivité de 10 minutes et recommande des pings périodiques. Décision : heartbeat provider-owned dans l'actor existant, activé pour `WsProtocolKind::HeliusLaserStream` (LaserStream **WebSocket**), sans champ public générique ajouté à `WsSessionSettings` dans cette release. Politique cible : ```text intervalle nominal = 60 s frame = WebSocket Ping control frame actif = session Active seulement close/failure = timer annulé reconnect réussi = timer réarmé write failure = chemin disconnect/reconnect existant budget reconnect = fini, inchangé ``` ### 7.12 Config V2 Contrat matérialisé par `pre.004` : ```text profiles[].ws_endpoints[].kind: solana_standard helius_laserstream helius_laserstream -> WsProtocolKind::HeliusLaserStream ``` La fixture Config contient un endpoint Helius mainnet typé ainsi qu'un profil Helius devnet distinct. L'exemple versionné matérialise lui aussi un profil mainnet et un profil devnet séparés ; aucun profil logique ne mélange les deux clusters. `config/std.transport.json` reste standard-only pour ne pas imposer de provider/credential dans la configuration par défaut. Les paramètres de `transactionSubscribe` restent runtime et n'appartiennent pas au profil endpoint Config. ### 7.13 Credentials L'api-key reste uniquement dans l'URL résolue : ```text mainnet = wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-replace-me} devnet = wss://devnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-replace-me} ``` `KSP_SECRET_HELIUS_API_KEY` est inventorié dans `.env.example` sous forme commentée/placeholder et est réutilisé pour les deux réseaux Helius. Config résout la clé au sein de chaque URL effective, marque le segment résolu comme `Secret` et conserve l'URL réelle uniquement pour le runtime légitime. Le contrat Config existant de redaction **segmentaire** s'applique aux chaînes composées : les littéraux sûrs sont conservés et le segment secret devient `********`, soit `wss://mainnet.helius-rpc.com/?api-key=********` ou `wss://devnet.helius-rpc.com/?api-key=********`. `Debug` n'expose jamais la clé réelle. Transport ne lit jamais `std::env` et reçoit uniquement un `WsEndpointUrl`. ## 8. Threat model provider ### 8.1 Secret dans query URL Risque : fuite par `Debug`, handshake error, logs tungstenite ou contexte d'erreur. Mesures : `WsEndpointUrl` opaque/redacted, mapping vers metadata sûre, canaris avec secret reconnaissable. ### 8.2 Contournement de façade Risque nouveau identifié par le fix : obtenir un handle générique Helius puis appeler une méthode standard non supportée. Mesures : ```text HeliusLaserStreamWsSession possède son inner privé aucun inner()/into_inner() public en 0.2.8 constructeur générique historique gardé standard-only validation protocolaire interne avant I/O ``` ### 8.3 Heartbeat concurrent avec reconnect/close Timer détenu par l'actor, actif seulement en `Active`, reset/cancel avec lifecycle, test déterministe par cadence raccourcie interne/test-only. ### 8.4 Late notifications et IDs distants Après `transactionUnsubscribe`, les messages in-flight sont possibles. L'unsubscribe local gagne ; un remote ID tardif ne réactive pas la subscription. ### 8.5 Payload transaction volumineux Conserver `max_message_size_bytes`, `max_frame_size_bytes`, queue bornée par subscription et absence de payload brut dans les logs/snapshots. ### 8.6 Filtres 50k Valider chaque liste avant I/O, ne pas logguer les valeurs et éviter les clones inutiles. ### 8.7 Continuité ```text reconnect oui resubscribe oui continuity_gap_count oui backfill non historical replay non lossless WebSocket non ``` ## 9. Dépendances Versions observées dans le graphe opérateur : ```text futures-util 0.3.34 tokio 1.53.1 tokio-tungstenite 0.30.0 reqwest 0.13.4 ``` Verdict : **aucune nouvelle dépendance**. Le moteur courant sait déjà gérer TLS WebSocket, Ping/Pong, timers Tokio, futures sink/stream et JSON serde. Un SDK Helius dupliquerait des choix de lifecycle que KSP possède déjà. ## 10. Stratégie de validation et smoke ### 10.1 Architecture/façades Prévoir : ```text public API canary SolanaStandardWsSession public API canary HeliusLaserStreamWsSession Helius facade sans block/slotsUpdates/vote Solana facade sans transactionSubscribe Helius facade sans escape hatch vers WsSession générique WsSession historique standard toujours disponible un seul actor/socket implementation path constructeur protocolaire invalide rejeté avant I/O ``` L'absence d'une méthode peut être couverte par compile-fail doctest/canary sans ajouter de dépendance de test si cette forme reste compatible avec les règles Rust KSP. ### 10.2 Helius transaction/lifecycle ```text transactionSubscribe serialization exacte transactionUnsubscribe exact transactionNotification method exact ack/error RPC 50k/50k/50k bounds maxSupportedTransactionVersion requirement tokenAccounts exact enum late notifications après unsubscribe remote id remapping reconnect/resubscribe provider RPC error sans session failure heartbeat timer + cancellation shutdown pendant heartbeat/reconnect oversized frame/message queue overflow isolé URL/api-key redaction ``` ### 10.3 Non-régressions ```text standard WS 18/18 pour SolanaStandard partition unstable 3/9 inchangée signature one-shot inchangée HTTP 52 current + 14 historical KSP-TRANSPORT-007 Config V1 HTTP-only backward readable Config V2 solana_standard backward readable no Transport -> Config/Wallet/Store/Program/tracing direct ``` ### 10.4 Smoke live Helius Le live reste opt-in et secondaire par rapport aux fixtures déterministes. Stratégie : Config résout le secret et construit l'endpoint Helius ; Transport ne lit jamais l'environnement. Si aucune surface d'intégration sûre n'est disponible, le smoke opérateur peut rester documenté plutôt que d'introduire une dépendance inverse. ## 11. Questions explicitement reportées ```text wire exact d'un éventuel enhanced/filtered accountSubscribe provider-specific modes transactionNotification non suffisamment documentés Gatekeeper beta provider metering/billing LaserStream gRPC / replay Yellowstone gRPC pool/scheduler automatique de sessions configuration publique arbitraire du heartbeat refonte générique WsSession

: non nécessaire en 0.2.8 ``` Une nouvelle preuve normative peut rouvrir un point et insérer une tranche ; aucun report ne doit disparaître silencieusement. ## 12. Critères de split et de clôture Créer une tranche/fix supplémentaire si : - l'extraction du chemin physique de `WsSession` révèle un problème générique indépendant ; - une des six familles Helius dites communes diverge réellement du standard ; - plusieurs formes de notification transaction demandent des contrats séparés ; - Helius modifie sa surface normative pendant la release ; - le smoke live impose une intégration non disponible ; - un gate final reste rouge après `pre.011`. La release ne passe stable que si : ```text surface Helius rapprochée source par source façades protocolaires séparées moteur WsSession/actor unique unsupported Helius absent de la façade publique transaction extension sans pollution des DTOs standard standard WS 18/18 non régressé HTTP 52/14 non régressé Config -> Transport uniquement credentials redacted heartbeat borné/testé si implémenté reconnect/resubscribe/backpressure/shutdown bornés aucune promesse replay/lossless WebSocket aucune nouvelle dépendance injustifiée workspace complet vert README/USAGE synchronisés validation finale fermée prompt 0.2.9 prêt ``` ## 13. Checkpoints `pre.004`, fixes et préparation `pre.005` Le checkpoint `pre.003` est fermé par preuve opérateur : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean cargo check --workspace OK cargo clippy --workspace --all-targets OK cargo test -p ksp-onchain-transport-lib OK cargo test --workspace OK ``` Les preuves spécifiques `pre.003` sont acquises : fixture Helius 6/6, 38 tests public API, 26 release-completeness et 4 doctests `compile_fail`, tous verts. `pre.004` prépare uniquement : ```text WsProtocolKind Config mapping helius_laserstream -> HeliusLaserStream JSON Schema V2 kind accepte solana_standard + helius_laserstream fixture Config Helius mainnet + profil Helius devnet séparé, interpolation d'api-key example Config profils Helius mainnet/devnet explicites et séparés secret KSP_SECRET_HELIUS_API_KEY safe projection / Debug credential Helius redacted Config -> Transport endpoint typé HeliusLaserStream canonical std.transport.json inchangé, standard-only transactionSubscribe toujours absent heartbeat toujours absent new dependency aucune ``` Le premier checkpoint opérateur `pre.004` reçu le 2026-08-23 donne : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean cargo check --workspace OK cargo clippy --workspace --all-targets OK cargo test -p ksp-config-lib FAIL 109/110 cargo test -p ksp-onchain-transport-lib OK 314 unit + 38 public + 26 completeness + 4 doctests cargo test --workspace FAIL sur le même test Config ``` L'unique échec opérateur compare `safe_value` à `********` alors que le résolveur produit correctement `wss://mainnet.helius-rpc.com/?api-key=********`. Les canaris historiques Config prouvent déjà ce contrat de redaction segmentaire pour les chaînes composées. La revue du fix a aussi identifié que `pre.004` ne matérialisait déterministiquement que mainnet alors que le contrat Helius WebSocket couvre mainnet et devnet. `pre.004-fix.001` corrige donc l'attente du test **et** ajoute un profil/fixture Devnet Helius séparé, sans modifier le résolveur, le schema, le mapping protocol ou le runtime Transport. Le checkpoint opérateur de `pre.004-fix.001` reçu le 2026-08-23 confirme : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean cargo check --workspace OK cargo clippy --workspace --all-targets OK cargo test -p ksp-config-lib FAIL 109/110 cargo test -p ksp-onchain-transport-lib OK 314 unit + 38 public + 26 completeness + 4 doctests ``` Le nouvel échec est strictement dans le canari Helius Config : ```text provenance observée = 2 segments provenance attendue = 1 segment ``` Pour une URL composée `littéral + ${KSP_SECRET_HELIUS_API_KEY}`, le contrat Config enregistre correctement deux provenances ordonnées : `DocumentLiteral`, puis `EnvironmentProcess(KSP_SECRET_HELIUS_API_KEY)`. `pre.004-fix.002` corrige seulement cette attente pour mainnet et devnet ; aucune donnée Config ni logique runtime n'est modifiée. Le checkpoint opérateur reçu après `pre.004-fix.002` ferme cette tranche : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean cargo check --workspace OK cargo clippy --workspace --all-targets OK cargo test -p ksp-config-lib OK 110/110 + ownership/public API cargo test -p ksp-onchain-transport-lib OK 314 unit + 38 public + 26 completeness + 4 doctests cargo test --workspace OK ``` Les profils Helius mainnet/devnet, la redaction segmentaire, la provenance composée, le mapping Config -> Transport et l'absence de nouvelle dépendance sont donc acquis avant `pre.005`. `pre.005` prépare maintenant : ```text HeliusTransactionSubscribeFilter vote/failed/signature/accountInclude/accountExclude/accountRequired/tokenAccounts HeliusTokenAccountsFilter none / balanceChanged / all HeliusTransactionSubscribeOptions commitment/encoding/transactionDetails/showRewards/maxSupportedTransactionVersion HeliusTransactionSubscribeRequest validation déterministe + params exacts accountInclude/Exclude/Required <= 50_000 chacun transactionDetails accounts/full maxSupportedTransactionVersion obligatoire transactionSubscribe ack remote id numérique décodé en interne transactionUnsubscribe méthode/params/résultat bool exacts Debug filtre/request signature et valeurs d'adresses non rendues actor/socket chemin physique existant réutilisé dans fixture locale live transaction handle volontairement absent jusqu'à pre.006 transactionNotification toujours hors scope pre.005 heartbeat toujours hors scope pre.005 new dependency aucune ``` La séparation `pre.005` / `pre.006` est normative : publier dès maintenant un `transaction_subscribe()` public sans registry de notification/reconnect produirait un handle transitoire qui perdrait les notifications. Le contrat public de requête est donc stable dès `pre.005`, tandis que l'abonnement live est ajouté atomiquement avec l'actor integration en `pre.006`. ### Checkpoint `pre.005` et correctif `pre.005-fix.001` Le premier checkpoint opérateur de `pre.005` a donné : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean mais incomplet pour la règle test/private cargo check --workspace OK avec 5 warnings unused pub(crate) reexports cargo clippy --workspace --all-targets FAIL — 4 comparaisons Vec / Value cargo test -p ksp-onchain-transport-lib FAIL — mêmes 4 erreurs de type ``` Le correctif ne change ni le contrat Helius public ni le wire. Il applique la règle de visibilité KSP : un helper utilisé seulement par son module et son sous-module de tests reste strictement privé ; le test l'appelle via `super::Item`. Les éléments `pub` et `pub(crate)` restent réexportés et consommés via `crate::Item`. Les helpers transaction wire de `pre.005` ne deviendront `pub(crate)` qu'en `pre.006` si l'actor les consomme réellement. Le script `audit_rust_export_completeness.py` est étendu pour détecter dans les `unit_tests/` séparés les accès non qualifiés aux items privés du parent et les accès non canoniques aux items visibles.