67 KiB
Plan 0.2.8 — Helius LaserStream WebSocket
Statut :
0.2.8-pre.008est validé intégralement par l’opérateur : fmt/audit/check/Clippy, 335 unit Transport, 40 API, 30 completeness, 5 doctests et workspace complet verts.0.2.8-pre.009réconcilie la documentation Helius courante, ajouteslotsUpdatesà la façade Helius et ferme les canaris de compliance HTTP/WS/Config/dependencies.
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 :
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 :
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 — préparé
workspace.package.version courant = 0.2.8-pre.8
commit attendu = v0.2.8-pre.008
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.
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>/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 PREPARED — compliance Helius WebSocket + réconciliation documentaire `slotsUpdates`
+ 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.NNNreprésente une tranche de travail planifiée ; sesfix.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
fixreste 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.011n'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 :
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.001reste audit + brainstorming + sizing ;pre.002-fix.001corrige 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.mdreste global ; les détails vivent dans plan/validation/deltas ;CHANGELOG.mdreste 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 :
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é :
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 :
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
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 :
Account
Block
Logs
Program
Root
Signature
Slot
SlotsUpdates
Vote
Partition :
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 :
SolanaStandard -> "solana_standard"
WsEndpointSettings fournit déjà :
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
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 :
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
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
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
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 :
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
slotsUpdatesSubscribeavec son unsubscribe ; - la page spécifique
slotsUpdatesSubscribela documente comme unstable, avec endpoints mainnet/devnet, request, notification et subscription ID, sans mention « Helius does not support » ; - la page spécifique
slotsUpdatesUnsubscribedocumente l'ID numérique et la réponse booléenne ; - à l'inverse, les pages spécifiques
blockSubscribeetvoteSubscribegardent 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.
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 :
vote Option<bool>
failed Option<bool>
signature Option<string>
accountInclude Option<string[]> max 50_000
accountExclude Option<string[]> max 50_000
accountRequired Option<string[]> max 50_000
tokenAccounts none | balanceChanged | all
Options :
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 :
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é :
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 :
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 :
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 :
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<T>
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 :
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 :
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 :
block
vote
L'objectif est qu'un appel du genre :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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é
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 :
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 :
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
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
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
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<P> : 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
WsSessionré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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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é :
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> / 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>/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 :
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 :
WsSubscriptionKind::HeliusTransaction
subscribe method transactionSubscribe
unsubscribe method transactionUnsubscribe
notification method transactionNotification
HeliusLaserStreamWsSession::transaction_subscribe(request)
-> WsSubscription<HeliusTransactionNotification>
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 :
[ ] 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 :
[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 :
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 :
[ ] 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 :
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 :
[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 :
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 :
- `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 :
[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é :
[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 :
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 :
- 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 :
[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 :
- 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 :
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 :
- 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 :
[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 :
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 :
- 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 :
[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 :
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 :
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 <omitted>
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 :
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 :
[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 :
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 :
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 :
Transport unit 335
Transport public API 41
release completeness 33
Transport doctests 4
workspace vert
warnings 0
Les trois nouveaux canaris release_completeness verrouillent :
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.