Files
khadhroony-solana-project/deltas/0.2.7/pre.006.md
2026-08-22 19:56:20 +02:00

288 lines
8.6 KiB
Markdown

<!-- file: deltas/0.2.7/pre.006.md -->
<!-- version: 1 -->
# 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<T>`
Nouvelle surface publique commune :
```text
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 :
```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<T>::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<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 :
```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.