# Plan `0.2.8` — Helius LaserStream WebSocket > **Statut : release stable `0.2.8 — Helius LaserStream WebSocket` publiée par `rel.001` après candidate `pre.011` entièrement verte : fmt/audit/check/Clippy, 335 unit Transport, 41 API, 34 completeness, 4 doctests et workspace complet. Les trois audits `cargo tree` finaux restent ceux de `pre.010`. Le prompt autonome `0.2.9` est prêt.** ## 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 validé pre.005-fix.001 correction types de canaris + visibilité/tests/règles validée pre.005-fix.002 suppression warnings dead_code via cfg(test) validée pre.006 transactionNotification + lifecycle actor validé pre.007 heartbeat Helius WebSocket / idle validé pre.007-fix.001 lint + close canary corrigés pre.007-fix.002 observation yield-only insuffisante — checkpoint historique pre.007-fix.003 heartbeat Transport fermé pre.007-fix.004 alignement canari dependency-firewall Tokio dev validé pre.008 adversarial provider/capabilities/payload/security validé pre.009 compliance provider/current + slotsUpdates Helius validé pre.010 documentation/smoke policy/cargo tree final validé pre.011 clôture candidate plan/matrice/indexes + prompt 0.2.9 — validé rel.001 publication stable workspace.package.version courant = 0.2.8 commit attendu = v0.2.8-rel.001 tag stable attendu = v0.2.8 ``` Le checkpoint opérateur de `pre.011` est intégralement vert. Cette candidate n’a rouvert aucune capacité runtime : elle a synchronisé uniquement la documentation de clôture et le contrat de démarrage `0.2.9`. `rel.001` publie le même état fonctionnel avec le signal Cargo stable. ## 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 DONE — transactionSubscribe request typed + filters/options/tokenAccounts + transactionUnsubscribe + bounds 50k + maxSupportedTransactionVersion conditionnel ; live handle différé à pre.006 fix.001 DONE — assertions Vec/Value corrigées + helpers test-only privés + audit super/crate durci fix.002 DONE — helpers wire non consommés en production bornés à #[cfg(test)] ; zéro warning dead_code pre.006 DONE — transactionNotification + handle live + actor registry/reconnect/resubscribe/unsubscribe races + late notifications + backpressure ciblée, sans second actor/socket pre.007 DONE — heartbeat Helius WebSocket/idle + timers + interaction reconnect/control frames/shutdown fix.001 DONE — lint et close canary corrigés fix.002 DONE — diagnostic de propagation scheduler établi fix.003 DONE — canari heartbeat périodique stabilisé sans changer le runtime fix.004 DONE — dependency-firewall aligné sur `io-util, net, rt, test-util` pre.008 DONE — provider adversarial lifecycle + capability guards + payload/backpressure + security/redaction pre.009 DONE — compliance Helius WebSocket + réconciliation documentaire `slotsUpdates` + non-régressions Solana standard 18/18 + HTTP 52/14 + Config/API/dependency-firewall canaries pre.010 DONE — stratégie smoke Helius live fermée sans nouveau cross-crate owner + README/USAGE + cargo tree direct/duplicates final exécutés et inspectés pre.011 DONE — validation workspace finale + fermeture plan/matrice/indexes + prompt 0.2.9 autonome 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, slotsUpdates Helius extension : transactionSubscribe / transactionUnsubscribe non supporté : block, 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 Réconciliation `slotsUpdatesSubscribe` au réaudit `pre.009` La documentation Helius courante a évolué depuis `pre.001` : - l'overview LaserStream WebSocket affirme désormais supporter le jeu complet des méthodes standard et énumère explicitement `slotsUpdatesSubscribe` avec son unsubscribe ; - la page spécifique `slotsUpdatesSubscribe` la documente comme unstable, avec endpoints mainnet/devnet, request, notification et subscription ID, sans mention « Helius does not support » ; - la page spécifique `slotsUpdatesUnsubscribe` documente l'ID numérique et la réponse booléenne ; - à l'inverse, les pages spécifiques `blockSubscribe` et `voteSubscribe` gardent explicitement « Helius does not support ». Arbitrage KSP `pre.009` : la page spécifique de chaque méthode prévaut sur une formule globale lorsqu'elles divergent. ```text slotsUpdatesSubscribe / slotsUpdatesUnsubscribe = support Helius retenu, unstable blockSubscribe / blockUnsubscribe = non supporté Helius voteSubscribe / voteUnsubscribe = non supporté Helius ``` `HeliusLaserStreamWsSession` expose donc `slots_updates_subscribe()` à partir de `pre.009`, en réutilisant exactement le DTO, le wire, l'unsubscribe par handle, le warning unstable et l'actor standard existants. Aucun type provider-specific n'est créé pour cette famille. ### 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 | Oui | présente | aucun paramètre | `slotsUpdatesNotification` | DTO/wire standard partagé + warning unstable | | `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 : 7 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 slotsUpdates transaction ``` `pre.003` matérialisait les six familles standard alors retenues ; `pre.009` ajoute `slotsUpdates` après évolution de la documentation Helius, tandis que `transaction` reste la famille provider-specific. La façade Helius **n'expose pas dans le scope courant** les deux familles dont les pages spécifiques Helius restent explicitement non supportées : ```text block 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/vote ; slotsUpdates présent et unstable 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. ### Checkpoint `pre.005-fix.001` et correctif `pre.005-fix.002` Le checkpoint opérateur après `fix.001` confirme que les erreurs `Vec`/`Value` sont corrigées et que les tests Transport passent (`322` unit, `39` public API, `27` release completeness, `4` doctests). Il révèle toutefois neuf warnings `dead_code` pendant `cargo check`, `clippy` et la compilation des tests : deux sérialiseurs privés, cinq helpers de contrôle subscribe/unsubscribe et le helper d'insertion de listes. Ces éléments ne sont pas encore consommés par un chemin de production en `pre.005`; leur seul consommateur légitime est le sous-module `unit_tests` rattaché au module propriétaire. `fix.002` applique donc `RUST-API-008` : ils restent strictement privés et sont placés sous `#[cfg(test)]`. Le contrat public typed et ses validations restent compilés en production. Aucune visibilité n'est élargie, aucun `#[allow(dead_code)]` n'est ajouté et le wire préparé reste couvert par les mêmes canaris. Leur promotion éventuelle en code de production est différée à `pre.006`, exactement au moment où l'actor WebSocket les consommera réellement ; si cette promotion exige `pub(crate)`, elle suivra alors la façade crate-root et les appels `crate::Item`. ## 14. Fermeture `pre.005` et préparation `pre.006` Le checkpoint opérateur reçu après `pre.005-fix.002` ferme entièrement la tranche transaction-request : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean cargo check --workspace OK, sans warning cargo clippy --workspace --all-targets OK, sans warning cargo test -p ksp-onchain-transport-lib OK 322 unit + 39 public API + 27 completeness + 4 doctests cargo test --workspace OK ``` `pre.006` promeut uniquement les helpers désormais réellement consommés par le chemin live. Ils restent strictement privés dans leur module lorsque leur consommation est locale ; aucun élargissement de visibilité n'est effectué pour les tests. Les tests continuent d'accéder aux privés du parent via `super::Item`, tandis que les types publics passent par la façade crate-root `crate::Item`. La tranche ajoute : ```text WsSubscriptionKind::HeliusTransaction subscribe method transactionSubscribe unsubscribe method transactionUnsubscribe notification method transactionNotification HeliusLaserStreamWsSession::transaction_subscribe(request) -> WsSubscription HeliusTransactionNotification Full transaction + signature + slot + transactionIndex Signature signature + slot + transactionIndex + champs optionnels conservés Unknown payload provider futur/none conservé sans faire tomber la souscription ``` Le variant `Unknown` est volontairement borné à la valeur `result` de `transactionNotification` : KSP ne publie ni l'enveloppe JSON-RPC complète ni l'identifiant remote de subscription. Le remote ID reste propriété exclusive de l'actor et peut donc être remappé après reconnexion sans changer l'identité locale du handle. Les canaris locaux `pre.006` doivent prouver ensemble : ```text [ ] requête live exacte via HeliusLaserStreamWsSession::transaction_subscribe [ ] transactionNotification Full décodée par le handle public [ ] formes Signature et Unknown préservées [ ] transactionUnsubscribe utilise le remote ID courant [ ] reconnexion renvoie le même params transactionSubscribe [ ] remote ID 41 -> 99 remappé sans changer WsSubscriptionId [ ] notification tardive après demande unsubscribe ignorée [ ] overflow transaction échoue seulement la souscription lente [ ] cleanup overflow appelle transactionUnsubscribe [ ] une souscription Helius root saine continue de recevoir ses notifications [ ] standard 9 familles / 18 opérations non régressées [ ] aucun heartbeat ajouté avant pre.007 [ ] aucun second actor/socket/registry/queue [ ] aucune nouvelle dépendance ``` Hors scope inchangé : heartbeat/idle (`pre.007`), adversarial provider/security élargi (`pre.008`), smoke Helius live (`pre.010`) et LaserStream gRPC. ## 15. Fermeture `pre.006` et préparation `pre.007` Checkpoint opérateur `0.2.8-pre.006` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean [x] cargo check --workspace = sans warning [x] cargo clippy --workspace --all-targets = sans warning [x] cargo test -p ksp-onchain-transport-lib = 325 unit + 40 public API + 28 completeness + 4 doctests [x] cargo test --workspace = vert ``` Le lifecycle Helius transaction est donc fermé : `transactionNotification`, handle live, remap remote ID, resubscribe, unsubscribe tardif et backpressure ciblée sont acquis sans second actor/socket. `pre.007` n'ajoute aucune surface publique générique de heartbeat. La policy reste possédée par le protocole Helius LaserStream WebSocket dans `WsSession` : ```text protocole actif WsProtocolKind::HeliusLaserStream uniquement intervalle nominal 60 s wire WebSocket Ping control frame vide état d'émission Active uniquement succès prochain deadline = now + 60 s reconnect réussi deadline réarmé depuis la nouvelle connexion write timeout/failure WsActorIoOutcome::Failed -> reconnect borné existant close/shutdown branche shutdown prioritaire, timer abandonné SolanaStandard aucun heartbeat provider ``` Aucun champ `heartbeat_*` n'est ajouté à `WsSessionSettings` ou Config en `0.2.8`. La cadence Helius est une policy interne provider-owned, comme décidé à l'audit d'ouverture. Pour rendre les canaris de timer déterministes sans réduire la cadence de production, `tokio` active uniquement la feature de dev/test `test-util`; aucune nouvelle dépendance runtime n'est ajoutée. Canaris locaux `pre.007` préparés : ```text [ ] policy Helius-only + intervalle exact 60 s [ ] premier Ping après 60 s, puis réarmement périodique [ ] SolanaStandard n'émet aucun Ping provider même après plusieurs intervalles [ ] close avant deadline n'émet aucun heartbeat [ ] write failure du Ping produit le même outcome Failed utilisé par le reconnect [ ] reconnect réussi réarme le heartbeat depuis la nouvelle connexion, sans Ping immédiat [ ] shutdown/backoff existant reste interruptible [ ] aucune modification du lifecycle transaction pre.006 ``` Comptages attendus après ajout des canaris : ```text Transport unit 331 Transport public API 40 release completeness 29 doctests compile-fail 4 ``` Le heartbeat Helius reste un mécanisme de maintien de connexion ; il n'introduit aucune promesse de replay WebSocket ni de livraison lossless. ## 16. Checkpoint `pre.007` et préparation `pre.007-fix.001` Checkpoint opérateur `0.2.8-pre.007` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean [x] cargo check --workspace = sans warning [!] cargo clippy --workspace --all-targets unit_tests/ws_session.rs: yield_runtime_steps() termine sans `return;` explicite [!] cargo test -p ksp-onchain-transport-lib 329/331 unit passent ; 2 canaris heartbeat échouent [ ] cargo test --workspace — non retenu tant que le gate Transport n'est pas vert ``` Les quatre autres preuves heartbeat importantes passent déjà : policy Helius-only/60 s, absence de heartbeat standard, erreur d'écriture mappée vers le reconnect existant, et réarmement après reconnexion. Le défaut observé est donc borné au harness de test, pas au chemin runtime `ws_session.rs`. Cause exacte des deux canaris : ```text helius_heartbeat_sends_ping_at_sixty_seconds_and_rearms l'horloge pausée est avancée avant d'avoir garanti un premier poll de l'actor ; le sleep_until heartbeat peut donc être armé seulement après l'avance. helius_explicit_close_cancels_heartbeat_before_deadline après close, le serveur fixture termine et le sender d'observation est détruit ; try_recv() peut légitimement rendre Disconnected au lieu de Empty. ``` `fix.001` ne touche pas le runtime heartbeat. Il modifie uniquement le canari : ```text - `yield_runtime_steps()` termine par `return;` explicite ; - après `tokio::time::pause()`, l'actor reçoit plusieurs yields avant la première avance ; - le test close prouve d'abord `Empty` à t=30 s avant close ; - après close, il exige seulement qu'aucun `Ok(())` Ping n'ait été observé, donc `Empty` et `Disconnected` sont tous deux acceptables. ``` La constante 60 s, la frame Ping, le branch actor, le reconnect, Config et `WsSessionSettings` restent byte-for-byte hors du fix. `pre.008` reste bloqué jusqu'au replay vert de `pre.007-fix.001`. ## 17. Replay `pre.007-fix.001` et préparation `pre.007-fix.002` Checkpoint opérateur `0.2.8-pre.7.fix.1` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate [x] cargo check --workspace = vert, sans warning [x] cargo clippy --workspace --all-targets = vert, sans warning [!] cargo test -p ksp-onchain-transport-lib = 330/331 unit ; 1 seul échec heartbeat périodique [ ] cargo test --workspace — bloqué tant que Transport n'est pas vert ``` Le `fix.001` a donc bien corrigé : ```text [x] `clippy::implicit_return` dans yield_runtime_steps [x] close avant deadline : aucun Ping puis Empty/Disconnected acceptés après fermeture [x] policy Helius-only + 60 s [x] absence de heartbeat standard [x] write failure -> reconnect [x] réarmement après reconnect ``` Échec restant : ```text helius_heartbeat_sends_ping_at_sixty_seconds_and_rearms le timer est expiré à t=60 s, mais l'observation traverse encore plusieurs tâches : actor -> écriture socket -> lecture serveur fixture -> ping_tx -> ping_rx. Un `try_recv()` après un nombre fixe faible de yields reste donc une course scheduler. ``` `fix.002` reste test-only et ne modifie pas `src/ws_session.rs`. Il ajoute un helper d'observation bornée qui : ```text - tente `try_recv()` ; - si Empty, cède explicitement au scheduler ; - répète au maximum 256 tours ; - échoue immédiatement si le canal est Disconnected ; - n'appelle ni `advance()`, ni `sleep()`, ni `timeout()` pendant cette attente. ``` Ainsi, le test peut laisser la propagation async finir **sans faire avancer l'horloge Tokio au-delà de t=60 s / t=120 s**. Le canari continue donc de vérifier la vraie cadence nominale, et ne peut pas réussir grâce à un heartbeat ultérieur. Le runtime reste byte-for-byte hors du fix : cadence 60 s, Ping control frame, actor unique, reconnect, shutdown, Config et lifecycle transaction inchangés. `pre.008` reste bloqué jusqu'au replay entièrement vert de `pre.007-fix.002`. ## 18. Replay `pre.007-fix.002` et préparation `pre.007-fix.003` Checkpoint opérateur `0.2.8-pre.7.fix.2` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate [x] cargo check --workspace = vert, sans warning [x] cargo clippy --workspace --all-targets = vert, sans warning [!] cargo test -p ksp-onchain-transport-lib = 330/331 unit ; même unique canari périodique [ ] cargo test --workspace — non retenu tant que Transport n'est pas vert ``` Le `fix.002` démontre que le problème n'est pas un simple manque de tours scheduler : même 256 `yield_now()` bornés ne rendent pas le Ping observable par la fixture TCP locale. La revue du runtime montre en parallèle que : ```text - le deadline heartbeat est construit par `next_helius_heartbeat_deadline()` ; - la branche `sleep_until(heartbeat_deadline)` est bien présente dans le `tokio::select!` de l'actor ; - un succès de `send_helius_heartbeat()` réarme à `now + 60 s` ; - le canari reconnect actor réel continue de prouver un Ping après le réarmement de connexion. ``` Diagnostic affiné : sous horloge Tokio pausée, `yield_now()` garantit une cession au scheduler mais **ne garantit pas à lui seul un tour du driver I/O OS**. Le chemin d'observation TCP : ```text actor -> websocket.send(Ping) -> socket loopback -> reactor I/O -> fixture websocket.next() -> channel ``` peut donc rester invisible au `try_recv()` malgré un timer actor expiré et une écriture déjà engagée. `fix.003` reste test-only côté Rust et conserve `src/ws_session.rs` byte-identical. Le canari périodique : ```text - garde l'horloge virtuelle pausée pour prouver l'absence avant le deadline ; - avance exactement jusqu'au deadline nominal ; - reprend ensuite le temps réel uniquement pendant une fenêtre I/O bornée <= 1 s pour laisser le reactor TCP propager le Ping ; - repause immédiatement l'horloge après observation ; - compense le temps réel consommé avant le contrôle du second intervalle afin de rester strictement avant le second deadline ; - franchit ensuite le second deadline avant une nouvelle fenêtre I/O bornée. ``` Cette fenêtre réelle ne peut pas masquer un heartbeat manquant : elle ne dure qu'une seconde au maximum, très inférieure à l'intervalle provider-owned de 60 s, et n'est ouverte qu'après franchissement du deadline virtuel attendu. Le runtime, Config, `WsSessionSettings`, le lifecycle transaction et la cadence 60 s restent inchangés. `pre.008` reste bloqué jusqu'au replay entièrement vert de `pre.007-fix.003`. ## 19. Replay `pre.007-fix.003` et préparation `pre.007-fix.004` Checkpoint opérateur `0.2.8-pre.7.fix.3` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate [x] cargo check --workspace = vert, sans warning [x] cargo clippy --workspace --all-targets = vert, sans warning [x] cargo test -p ksp-onchain-transport-lib = 331/331 unit + 40 API + 29 completeness + 4 doctests [!] cargo test --workspace = échec unique dans ksp-core-lib/tests/workspace_dependencies.rs ``` Le heartbeat `pre.007` est donc fonctionnellement fermé côté Transport. L'échec workspace est indépendant du runtime : ```text transport_manifest_preserves_ksp_dependency_firewall attendu historique : tokio dev features = ["net", "rt"] manifest courant : tokio dev features = ["io-util", "net", "rt", "test-util"] ``` `pre.007` a légitimement ajouté `test-util` pour `tokio::time::pause/advance` et `io-util` pour les fixtures WebSocket de test. Le canari workspace, resté textuellement figé sur l'ancien ensemble, n'a pas été mis à jour dans le delta d'origine. `fix.004` corrige uniquement cette incohérence de validation : ```text - aucune modification de crates/ksp-onchain-transport-lib/Cargo.toml ; - aucune modification de src/ws_session.rs ni des canaris heartbeat ; - mise à jour du canari dependency-firewall vers l'ensemble dev exact courant ["io-util", "net", "rt", "test-util"] ; - aucune nouvelle dépendance ni feature. ``` `pre.008` reste bloqué jusqu'au replay workspace vert de `pre.007-fix.004`. ## 20. Fermeture `pre.007` et préparation `pre.008` Checkpoint opérateur `0.2.8-pre.7.fix.4` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate [x] cargo check --workspace = vert, sans warning [x] cargo clippy --workspace --all-targets = vert, sans warning [x] cargo test -p ksp-onchain-transport-lib = 331 unit + 40 API + 29 completeness + 4 doctests [x] cargo test --workspace = vert ``` La tranche heartbeat et ses quatre fixes sont donc fermés. `pre.008` ne rouvre ni cadence, ni Config, ni lifecycle transaction nominal. Ré-audit Helius du 2026-08-23 : ```text transactionSubscribe supporté sur endpoints WSS unifiés mainnet/devnet accountInclude/accountExclude/accountRequired 50 000 adresses max chacune transactionUnsubscribe ID distant numérique + bool de réponse blockSubscribe non supporté Helius slotsUpdatesSubscribe non supporté Helius voteSubscribe non supporté Helius ``` Le runtime partagé possède déjà les mécanismes de bornage : `max_message_size_bytes`, `max_frame_size_bytes`, queues de notifications bornées, reconnect fini, remapping remote/local et isolation de logical subscription. Le travail `pre.008` porte donc principalement sur des canaris adversariaux Helius et sur un défaut de diagnostic identifié pendant la revue : les notifications Helius publiques dérivaient encore `Debug` sur leur payload brut. Décisions `pre.008` : ```text 1. HeliusFullTransactionNotification::Debug - transaction brute omise - signature omise - slot/index seulement conservés comme métadonnées sûres 2. HeliusTransactionSignatureNotification::Debug - signature/memo/err/confirmation value omis - états omitted/null/value seulement pour les champs wire optionnels 3. HeliusTransactionNotification::Unknown - payload serde_json::Value toujours accessible via la variante - Debug rend uniquement 4. provider JSON-RPC application error - ERROR_CODE_RPC_APPLICATION_ERROR - rpc_code + method sûrs seulement - message/data arbitraires provider absents du KspError - session physique reste Active et accepte une subscription saine ensuite 5. notification method mismatch - remote ID Helius lié à transactionNotification recevant une autre méthode - seule la logical transaction subscription échoue - cleanup transactionUnsubscribe best-effort - autre subscription saine et session physique restent actives 6. oversized provider payload - max frame/message partagé appliqué avant JSON provider - reconnect borné du même actor - connexion de remplacement reste utilisable 7. capability inverse - Helius garde compile-fail block/slotsUpdates/vote - SolanaStandard ajoute compile-fail transactionSubscribe ``` Aucune nouvelle dépendance, aucun nouveau socket/actor, aucune extension Config, aucune lecture d'environnement Transport. Les garanties `pre.006` de queue overflow transaction + root restent la preuve provider-specific de backpressure ciblée ; `pre.008` ne duplique pas ce test inutilement. Gate attendu après application : ```text Transport unit 335 Transport public API 40 release completeness 30 Transport doctests 5 workspace vert warnings 0 ``` `pre.009` reste bloqué jusqu'au replay intégralement vert de `pre.008`. ## 21. Fermeture `pre.008` et préparation `pre.009` Checkpoint opérateur `0.2.8-pre.8` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate [x] cargo check --workspace = vert, sans warning [x] cargo clippy --workspace --all-targets = vert, sans warning [x] Transport unit = 335/335 [x] Transport public API = 40/40 [x] release completeness = 30/30 [x] Transport doctests = 5/5 [x] cargo test --workspace = vert ``` Verdict : **`pre.008` DONE.** Le réaudit final de conformité Helius révèle une évolution normative depuis `pre.001`. L'overview LaserStream WebSocket courant affirme désormais le support du jeu complet des méthodes standard. Les pages spécifiques permettent de résoudre la divergence sans extrapolation : ```text blockSubscribe page spécifique = explicitement non supporté Helius voteSubscribe page spécifique = explicitement non supporté Helius slotsUpdatesSubscribe page spécifique = unstable, documenté sur endpoints Helius, sans interdiction provider slotsUpdatesUnsubscribe = documenté avec subscriptionId + bool ``` Décision `pre.009` : ```text Helius standard actuel retenu = account/logs/program/root/signature/slot/slotsUpdates Helius extension = transaction Helius absent = block/vote SolanaStandard = 9 familles / 18 opérations inchangées HTTP = 52 current + 14 historical inchangés ``` La tranche ajoute uniquement la façade Helius `slots_updates_subscribe()` en réutilisant `WsSubscriptionKind::SlotsUpdates`, `SolanaSlotUpdate`, le triplet wire standard et le lifecycle actor déjà existant. Elle retire le compile-fail Helius correspondant, conserve ceux de block/vote et garde le compile-fail inverse `SolanaStandard -X-> transactionSubscribe`. Canaris de fermeture prévus : ```text Transport unit 335 Transport public API 41 release completeness 33 Transport doctests 4 workspace vert warnings 0 ``` Les trois nouveaux canaris `release_completeness` verrouillent : ```text 1. HTTP 52/14 + Standard WebSocket 9 familles / 18 méthodes ; 2. Helius = 7 familles standard + transaction, avec block/vote toujours absents ; 3. Config -> Transport, KSP_SECRET_HELIUS_API_KEY et dependency firewall sans reverse dependency. ``` Aucune nouvelle dépendance, aucun nouveau DTO Helius pour `slotsUpdates`, aucun second socket/actor et aucune modification Config ne sont nécessaires. Le point « enhanced accountSubscribe » reste reporté : l'overview parle de filtres avancés, mais la page API spécifique ne publie toujours pas de wire provider-specific supplémentaire assez précis pour une API typed ; KSP ne devine donc aucun champ. `pre.010` reste réservé au smoke Helius live opt-in si une stratégie credential-safe est disponible, à README/USAGE et au `cargo tree` final. ## 22. Fermeture `pre.009` et préparation `pre.010` Checkpoint opérateur `0.2.8-pre.9` reçu le 2026-08-23 : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate [x] cargo check --workspace = vert, sans warning [x] cargo clippy --workspace --all-targets = vert, sans warning [x] Transport unit = 335/335 [x] Transport public API = 41/41 [x] release completeness = 33/33 [x] Transport doctests = 4/4 [x] cargo test --workspace = vert ``` Verdict : **`pre.009` DONE.** Le réaudit Helius courant confirme les endpoints unifiés `wss://mainnet.helius-rpc.com/?api-key=...` et `wss://devnet.helius-rpc.com/?api-key=...`, le heartbeat recommandé par Ping chaque minute, `slotSubscribe` sur ces endpoints et `transactionSubscribe` comme extension Helius. La stratégie de smoke durable est néanmoins contrainte par l’architecture KSP : ```text Transport -X-> Config / environnement Config ne doit pas devenir la destination générale des futurs smokes cross-crates KSP_SECRET_HELIUS_API_KEY appartient exclusivement à Config ``` Décision `pre.010` : **aucun nouveau smoke Helius live committé dans Transport ou Config**. La future surface KSP d’intégration/orchestration dédiée devra porter le scénario `Config helius_devnet -> HeliusLaserStreamWsSession -> slotSubscribe -> notification -> unsubscribe -> close`. `transactionSubscribe` pourra rester un smoke opt-in supplémentaire dépendant des droits provider, jamais un gate réseau stable. Livraison `pre.010` : ```text - README Transport synchronisé avec la surface Helius finale 7 standard + transaction - USAGE Transport documente Config -> façade Helius, heartbeat et limites d’ownership - release completeness verrouille l’absence de lecture secret/env dans le smoke Transport - aucun nouveau test Helius live dans Config - aucune nouvelle dépendance - cargo tree final requis de l’opérateur ``` Gate attendu : ```text Transport unit 335 Transport public API 41 release completeness 34 Transport doctests 4 workspace vert warnings 0 ``` Audit graphe à joindre au checkpoint opérateur : ```bash cargo tree -p ksp-onchain-transport-lib cargo tree -p ksp-onchain-transport-lib --duplicates cargo tree --duplicates ``` `pre.011` reste bloqué jusqu’au gate complet et à l’inspection de ces graphes. ## 23. Fermeture `pre.010` et préparation `pre.011` ### 23.1 Gate opérateur `0.2.8-pre.10` Le checkpoint opérateur du 23 août 2026 est intégralement vert : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py General Rust rule audit: clean Rust export completeness audit: 0 candidate(s) KSP workspace Rust rule audit: clean [x] cargo check --workspace [x] cargo clippy --workspace --all-targets [x] Transport unit = 335/335 [x] Transport public API = 41/41 [x] release completeness = 34/34 [x] Transport doctests = 4/4 [x] cargo test --workspace [x] cargo tree -p ksp-onchain-transport-lib [x] cargo tree -p ksp-onchain-transport-lib --duplicates [x] cargo tree --duplicates ``` Le graphe Transport direct reste centré sur les dépendances déjà acquises (`futures-util`, `reqwest`, `serde`, `serde_json`, `tokio`, `tokio-tungstenite`, Core et Logging). Aucun SDK Helius/gRPC ni dépendance Config inverse n'apparaît. Doublons ciblés Transport observés : ```text syn 2.x / 3.x webpki-roots 0.26.x / 1.x ``` Ils sont transitifs et non bloquants. Le graphe workspace complet expose d'autres duplications transitives provenant principalement de Tauri/GTK, JSON Schema et des stacks crypto Wallet/Solana ; aucune dérive nouvelle propre à `0.2.8` n'est identifiée. Verdict : **`pre.010` DONE.** ### 23.2 Clôture documentaire `pre.011` `pre.011` ne doit ajouter aucune capacité runtime Helius. Sa mission est : ```text workspace.package.version = 0.2.8-pre.11 fermer le plan et la matrice au niveau candidate synchroniser les index documentaires devenus obsolètes créer prompts/014-V0_2_9_START_PROMPT.md conserver ROADMAP/CHANGELOG pour la publication stable rel.001 rejouer le gate workspace final ``` Le prompt `0.2.9` est construit conformément à `docs/rules/PROMPT_STRUCTURE.md` et comparé aux prompts `0.2.5`–`0.2.8`. Il conserve notamment : ```text base stable exacte et archive opérateur autoritaire ordre de lecture interne obligatoire sources externes Yellowstone à réauditer état HTTP/WS/Helius à préserver frontières Config/Transport/provider-neutral questions ouvertes dependencies/proto/licence/lifecycle première mission pre.001 audit/brainstorming/sizing obligatoire prévision souple initiale détaillée et recalibrable workflow version/delta/commit/archive/tag validation Rust/cargo tree/smoke critères de clôture instruction d'ouverture interdisant le client lourd avant gate ``` Le réaudit préparatoire Yellowstone du 23 août 2026 confirme que l'upstream évolue rapidement (`Subscribe`, unary RPCs, replay, filtres enrichis et extensions deshred). Le prompt n'en fait aucun pin : il impose à `0.2.9-pre.001` de réauditer release, proto, crates, MSRV, features et licences avant de choisir les dépendances. Critères `pre.011` : ```text [x] workspace.package.version = 0.2.8-pre.11 [x] aucun code runtime Helius modifié [x] aucune nouvelle dépendance [x] prompt 0.2.9 autonome présent [x] prompt suit les 16 rubriques normatives de PROMPT_STRUCTURE [x] forecast 0.2.9 visible, détaillé et explicitement souple [x] prompt impose audit licence/dependencies avant Yellowstone/Tonic/Prost [x] prompt distingue Yellowstone standard des providers et de deshred/pré-exécution [x] docs/000, plans/000, validation/000, prompts/000 et séquence fonctionnelle synchronisés [x] ROADMAP non marqué stable avant rel.001 [x] CHANGELOG non utilisé comme journal de prerelease [x] cargo fmt --all [x] audit Rust workspace clean [x] cargo check --workspace sans warning [x] cargo clippy --workspace --all-targets sans warning [x] cargo test -p ksp-onchain-transport-lib [x] cargo test --workspace ``` Si ce gate est vert, la prochaine livraison est `0.2.8-rel.001`; aucune `pre.012` n'est nécessaire sauf défaut réel. ## 24. Candidate `pre.011` validée et publication stable `rel.001` Le checkpoint opérateur du 23 août 2026 ferme la candidate : ```text [x] cargo fmt --all [x] python3 scripts/audit_rust_workspace_rules.py General Rust rule audit: clean Rust export completeness audit: 0 candidate(s) KSP workspace Rust rule audit: clean [x] cargo check --workspace [x] cargo clippy --workspace --all-targets [x] Transport unit = 335/335 [x] Transport public API = 41/41 [x] release completeness = 34/34 [x] Transport doctests = 4/4 [x] cargo test --workspace ``` Les `cargo tree` finaux ont déjà été exécutés et enregistrés en `pre.010`; `pre.011` n’a modifié aucune dépendance. Aucun fichier sous `crates/` n’a été modifié par la candidate documentaire. Verdict : **`pre.011` DONE ; aucune `pre.012` nécessaire.** `0.2.8-rel.001` publie ensuite la surface candidate sans nouvelle capacité runtime : ```text workspace.package.version = 0.2.8 Helius WS = Stable HTTP = 52 current + 14 historical Solana Standard WS = 9 familles / 18 opérations Helius = 7 familles standard + transaction block/vote Helius = absents heartbeat = Ping control frame / 60 s / Helius only Config = V2 helius_laserstream + secret redacted smoke live provider = reporté vers intégration/orchestration prochain prompt = prompts/014-V0_2_9_START_PROMPT.md ``` La publication stable ne change ni source Rust, ni Config runtime, ni dépendance. `ROADMAP.md`, `CHANGELOG.md`, les index et l’inventaire composants sont synchronisés uniquement au niveau stable.