8.6 KiB
Delta 0.2.7-pre.006 — registry subscriptions + IDs locaux + moteur typed générique
1. Base requise
0.2.7-pre.005 appliqué
workspace.package.version = 0.2.7-pre.5
Le checkpoint opérateur reçu avant cette tranche est vert : cargo fmt --all, audit Python, cargo check --workspace, cargo clippy --workspace --all-targets, cargo test -p ksp-onchain-transport-lib et cargo test --workspace. Les 269 tests unitaires Transport de pre.005 passent.
2. Objectif
Cette tranche ajoute le registry de subscriptions au même actor que le socket et la pending map JSON-RPC, sans avancer sur reconnect/resubscribe.
Objectifs matérialisés :
1 session physique
-> 0..N subscriptions logiques
-> WsSubscriptionId local stable
-> remote subscription id interne/transient
-> channel de notifications typed et bounded
La création générique de subscription reste crate-private jusqu'aux wrappers standard publics des lots pre.009+.
3. Signal technique
Cette prerelease modifie le runtime Rust, les tests et la documentation. Conformément aux règles KSP :
livraison = 0.2.7-pre.006
workspace.package.version = 0.2.7-pre.6
commit = v0.2.7-pre.006
Aucun tag prerelease.
4. Registry actor-owned
Le même actor possède maintenant :
socket physique
request counter
pending JSON-RPC map
subscription local counter
subscription registry local
remote_subscription_id -> WsSubscriptionId
safe snapshots
Le registry local est ordonné par local ID. Les IDs locaux sont assignés par l'actor et ne dépendent jamais du remote ID retourné par Solana.
Le remote ID reste absent de l'API publique, de Debug, des snapshots et des logs. Seul remote_bound: bool reste projeté par WsSubscriptionSnapshot.
5. Binding subscribe atomique
Le subscribe n'est pas implémenté comme :
execute_json_rpc
puis register local
car une notification pourrait alors arriver entre les deux opérations.
pre.006 ajoute une command actor Subscribe. Le pending request conserve le local ID et le dispatcher typed. Lorsque la réponse *Subscribe est reçue :
- la réponse JSON-RPC est validée ;
- le remote ID numérique est validé ;
- l'unicité du remote ID actif est vérifiée ;
- le remote ID est lié au local ID ;
- la subscription passe
Requested -> Active; - seulement ensuite le handle typed est rendu au caller.
Le prochain message socket ne peut donc pas être dispatché avant la mise à jour du mapping actor-owned.
6. WsSubscription<T>
Nouvelle surface publique commune :
WsSubscription<T>
id() -> WsSubscriptionId
kind() -> WsSubscriptionKind
state() -> WsSubscriptionState
recv().await -> Option<Result<T>>
unsubscribe().await -> Result<bool>
Le receiver de notifications est borné par notification_queue_capacity.
La création WsSession::subscribe_typed(...) reste pub(crate) : elle sera consommée par les wrappers typed standard et ne crée pas de surface raw provider-extension publique.
7. Dispatch notifications
Pour une notification Solana standard :
JSON-RPC notification
-> params.subscription remote u64
-> remote_to_local
-> WsSubscriptionRuntime
-> validation notification method
-> decoder typed
-> bounded typed channel
Le mapping exact famille/méthodes est centralisé sur WsSubscriptionKind pour les neuf familles :
account
block
logs
program
root
signature
slot
slotsUpdates
vote
Chaque famille possède son triplet exact *Subscribe, *Unsubscribe, *Notification.
8. Anomalies isolées
Politique matérialisée dans cette tranche :
- remote ID inconnu/stale : safe drop + diagnostic sûr, session inchangée ;
- notification method incompatible avec la famille enregistrée : subscription
Failed, session resteActive; - typed decoder failure : erreur livrée au channel lorsque possible, subscription
Failed, session resteActive; - queue typed déjà pleine : aucune perte silencieuse considérée normale, subscription terminale ; le compteur/cleanup adversarial complet est finalisé en
pre.008; - malformed JSON / structure JSON-RPC invalide : reste une anomalie de session selon le contrat acquis.
Aucun reconnect n'est déclenché par une erreur RPC applicative ou une erreur typed locale.
9. Unsubscribe
WsSubscription<T>::unsubscribe() ne reçoit jamais le remote ID du caller.
L'actor :
- marque la subscription
Cancelling; - retire immédiatement le remote ID du mapping de dispatch local ;
- construit le
*Unsubscribeexact avec le remote ID interne ; - valide la réponse booléenne ;
- publie
Closedet ferme le channel local.
Le booléen standard Solana est préservé au caller afin de ne pas perdre une variante de réponse pertinente.
Les races unsubscribe/reconnect et le principe « local cancellation wins » sous reconnexion restent le scope explicite de pre.007.
10. Tests déterministes
Ajouts principaux :
subscribe slot -> remote binding -> notification typed -> unsubscribe exact
2 familles -> 2 IDs locaux ordonnés + remote IDs indépendants
unknown remote ID -> notification suivante valide toujours dispatchée
notification method mismatch -> seule la subscription échoue
typed decode error -> erreur receiver + seule la subscription échoue
triplets exacts des 9 familles standard
public API canary WsSubscription<T>
Le serveur reste exclusivement local et déterministe. Aucun réseau Solana réel n'est requis.
11. Logging et sécurité
Toutes les émissions passent exclusivement par ksp-logging-lib avec :
TRACING_TARGET = "ksp-onchain-transport-lib"
Les diagnostics n'exposent que :
session_id
subscription_id local
subscription_kind
endpoint logique
counts/états sûrs
Aucun remote subscription ID n'est journalisé comme identité métier, et aucune URL, credential ou notification brute n'est loggée.
12. Fichiers ajoutés
crates/ksp-onchain-transport-lib/src/ws_subscription.rs
deltas/0.2.7/pre.006.md
13. Fichiers modifiés
Cargo.toml
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/src/ws_lifecycle.rs
crates/ksp-onchain-transport-lib/src/ws_session.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/unit_tests/ws_lifecycle.rs
crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md
docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md
14. Fichiers supprimés
Aucun.
ROADMAP.md et CHANGELOG.md restent inchangés pendant la série prerelease.
15. Validation exécutée dans le sandbox
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
Contrôles statiques supplémentaires :
workspace.package.version = 0.2.7-pre.6
tracing direct = absent
question-mark runtime = absent
remote ID public = absent
lignes Rust > 160 ajoutées = absentes
16. Validation non exécutée dans le sandbox
Cargo/rustfmt ne sont pas disponibles dans le sandbox de génération. Aucun résultat local n'est revendiqué pour :
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
cargo test --workspace
17. Gates opérateur avant commit
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
cargo test --workspace
Si le checkpoint est vert :
commit = v0.2.7-pre.006
La tranche suivante est 0.2.7-pre.007 : reconnect borné, resubscribe déterministe, continuity gap et races unsubscribe/reconnect.
18. Décisions / questions ouvertes
Décisions :
- le registry et le mapping remote/local appartiennent exclusivement à l'actor ;
- le local ID est stable et le remote ID est transient/interne ;
- la création générique typed reste crate-private ;
- le handle typed public possède le receiver et l'unsubscribe ;
- une anomalie typed connue ne doit pas faire tomber la session physique ;
- aucune promesse lossless n'est introduite.
Questions laissées aux tranches suivantes :
pre.007finalise la restauration déterministe après reconnexion et les races cancellation/ACK ;pre.008finalise overflow counters, cleanup best-effort et leak/lifecycle adversarial sous slow consumer.