288 lines
8.6 KiB
Markdown
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.
|