# Delta `0.2.7-pre.006` — registry subscriptions + IDs locaux + moteur typed générique ## 1. Base requise ```text 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 : ```text 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 : ```text 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 : ```text 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 : ```text 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 : 1. la réponse JSON-RPC est validée ; 2. le remote ID numérique est validé ; 3. l'unicité du remote ID actif est vérifiée ; 4. le remote ID est lié au local ID ; 5. la subscription passe `Requested -> Active` ; 6. 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` Nouvelle surface publique commune : ```text WsSubscription id() -> WsSubscriptionId kind() -> WsSubscriptionKind state() -> WsSubscriptionState recv().await -> Option> unsubscribe().await -> Result ``` 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 : ```text 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 : ```text 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 reste `Active` ; - typed decoder failure : erreur livrée au channel lorsque possible, subscription `Failed`, session reste `Active` ; - 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::unsubscribe()` ne reçoit jamais le remote ID du caller. L'actor : 1. marque la subscription `Cancelling` ; 2. retire immédiatement le remote ID du mapping de dispatch local ; 3. construit le `*Unsubscribe` exact avec le remote ID interne ; 4. valide la réponse booléenne ; 5. publie `Closed` et 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 : ```text 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 ``` 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 : ```text TRACING_TARGET = "ksp-onchain-transport-lib" ``` Les diagnostics n'exposent que : ```text 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 ```text crates/ksp-onchain-transport-lib/src/ws_subscription.rs deltas/0.2.7/pre.006.md ``` ## 13. Fichiers modifiés ```text 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 ```text 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 : ```text 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 : ```text 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 ```bash 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 : ```text 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.007` finalise la restauration déterministe après reconnexion et les races cancellation/ACK ; - `pre.008` finalise overflow counters, cleanup best-effort et leak/lifecycle adversarial sous slow consumer.