# Delta `v0.2.1-pre.004` ## Base Base attendue : ```text v0.2.1-pre.003-fix.001 ``` Cette base a été validée localement par le user avec `cargo fmt`, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, les tests ciblés Transport et `cargo test --workspace`. Version Cargo cible : ```text 0.2.1-pre.4 ``` ## Objectif Matérialiser la résilience runtime prévue par le plan `008` autour du client/pool HTTP déjà introduit : - token bucket RPS/burst par rôle ; - limite de concurrence par semaphore ; - cooldown après rate-limit provider ; - admission async avec deadline commune ; - fallback vers les pairs et tiers moins prioritaires lorsqu'un candidat est temporairement indisponible ; - états passifs `Degraded` / `RateLimited` et snapshots sûrs ; - retry/backoff transport borné ; - interdiction centralisée d'un resend après dispatch ambigu pour les opérations `NeverAfterDispatch`. Cette tranche ne réalise toujours pas de POST JSON-RPC ni de méthode Solana typée ; le raccordement réseau appartient à `pre.005`. ## Modifications ### Workspace et dépendances - `workspace.package.version` passe de `0.2.1-pre.3.fix.1` à `0.2.1-pre.4` ; - aucune nouvelle dépendance tierce n'est introduite ; - `ksp-onchain-transport-lib` consomme désormais `tokio.workspace = true` avec la feature locale `sync`, nécessaire au semaphore et à `Notify` ; - les features runtime/time Tokio restent possédées par la déclaration workspace existante ; - le firewall Transport -> Config/Store/Program et l'absence de `tracing` direct restent inchangés. ### Admission RPS / burst Chaque rôle endpoint possède son état runtime indépendant. Lorsque `requests_per_second` est configuré : - un token bucket est créé au niveau endpoint/rôle ; - `burst_capacity` fixe la capacité maximale ; - si le burst n'est pas explicite, la capacité dérive de la valeur RPS, soit une seconde de capacité ; - les tokens se reforment proportionnellement au temps écoulé ; - une admission sans token disponible expose l'instant de prochaine admissibilité au pool plutôt que de bloquer un thread. Sans RPS, le rôle n'est pas limité par le token bucket KSP. ### Concurrence `max_concurrent_requests` est matérialisé par un `tokio::sync::Semaphore` par rôle. Le nouveau `HttpRequestPermit` : - réserve la capacité de concurrence ; - conserve la sélection endpoint/rôle/request-kind ; - transporte la deadline commune ; - libère automatiquement la capacité à son drop ; - réveille les waiters lorsque de la capacité redevient disponible. Un candidat saturé n'empêche pas d'essayer les autres candidats du même tier puis les tiers moins prioritaires. ### Cooldown et rate-limit `HttpRequestPermit::record_rate_limited()` : - incrémente les compteurs failure/rate-limit ; - marque le rôle dégradé ; - applique le cooldown configuré par `pause_after_rate_limit` ; - utilise un fallback runtime de 1 seconde lorsque ce cooldown n'est pas configuré ; - accepte un délai provider déjà résolu et le borne à 60 secondes avant de pouvoir prolonger le cooldown local ; - notifie le pool de la nouvelle disponibilité temporelle. Le parsing concret du header HTTP `Retry-After` reste au futur exécuteur HTTP de `pre.005`; `pre.004` stabilise le contrat en `Duration`. ### Admission async et deadline commune Nouvelles surfaces publiques : ```text HttpRequestPermit HttpTransportPool::acquire_for_method(...) HttpTransportPool::acquire_for_request_kind(...) HttpTransportPool::acquire_for_request_kind_with_timeout(...) ``` Algorithme runtime : 1. filtrer endpoint/rôle/capability comme en `pre.003` ; 2. ordonner les candidats par priorité ; 3. appliquer le round-robin dans chaque tier ; 4. tenter l'admission cooldown + concurrence + token bucket ; 5. essayer les pairs puis les tiers inférieurs lorsqu'un candidat est temporairement bloqué ; 6. si tous les candidats sont temporairement bloqués, attendre notification ou prochain instant de refill/cooldown ; 7. ne jamais dépasser la deadline commune. Par défaut, la deadline commune utilise le plus petit `request_timeout` des endpoints structurellement compatibles. Une surcharge permet à une couche supérieure d'imposer un budget plus strict. ### Retry / backoff / no-resend Nouveaux contrats publics : ```text HttpRetryCause HttpDispatchState HttpRetryDecision evaluate_transport_retry(...) ``` La policy centralisée : - respecte `HttpRetrySettings::max_retries` ; - applique un backoff exponentiel borné entre `initial_backoff` et `max_backoff` ; - autorise seulement les causes transport classées retryables (`Connection`, `Timeout`, `RateLimited`, `TemporaryHttp`) ; - exclut `Request`, `RpcApplication` et `InvalidResponse` du retry automatique ; - respecte `TransportRetryClass::NotApplicable` ; - interdit tout retry d'une méthode `NeverAfterDispatch` lorsque l'état est `DispatchedAmbiguous` ; - autorise encore une tentative si le transport sait explicitement que la requête n'a pas été dispatchée ; - peut prolonger le backoff d'un rate-limit par un délai provider borné à 60 secondes. Aucune logique de retry métier/exécution Solana n'est introduite. ### Santé passive et snapshots Les rôles endpoint exposent maintenant de manière sûre : - `availability` ; - RPS/burst configurés ; - concurrence maximale et nombre en vol ; - cooldown restant ; - compteurs success/failure/rate-limit. Transitions : ```text Available --failure--> Degraded Available/Degraded --rate-limit--> RateLimited RateLimited --cooldown écoulé--> Degraded Degraded --success--> Available ``` Les snapshots n'exposent toujours aucune URL ni credential provider. ### ROADMAP et plan `ROADMAP.md` reçoit uniquement la mise à jour synthétique normale de l'état de `0.2.1` : la résilience runtime devient acquise et les prochains jalons majeurs restent les 4 canaris, Config standard et la clôture. Aucun historique de prerelease/fix n'y est ajouté. Le plan `008` enregistre `pre.004` comme réalisé et décrit l'état précis dans sa section de suivi. ## Tests ajoutés La suite Transport passe de **53 à 72 tests déclarés**. Les nouveaux tests couvrent notamment : - backoff exponentiel et borne maximale ; - budget `max_retries` ; - no-resend après dispatch ambigu ; - retry permis lorsqu'un non-dispatch est prouvé ; - RPC application errors et réponses invalides non retryées ; - extension/cap du délai provider ; - token bucket burst/refill déterministe ; - burst implicite dérivé du RPS ; - libération de semaphore ; - cooldown/rate-limit ; - fallback vers un tier inférieur sur saturation concurrence, RPS ou cooldown ; - réveil après libération de concurrence ; - timeout d'admission borné ; - transition passive degraded -> available ; - snapshot public de résilience ; - admission async depuis l'API publique sans fuite de l'URL ; - consommation publique de la policy de retry. ## Hors périmètre conservé Restent à `pre.005` : - exécution HTTP POST JSON-RPC réelle ; - mapping concret des erreurs `reqwest`/HTTP vers `HttpRetryCause` ; - parsing de `Retry-After` HTTP ; - méthodes typées `getHealth`, `getVersion`, `getGenesisHash`, `getBalance` ; - fixtures RPC de ces méthodes. Restent à `pre.006+` : - document/schema Config standard Transport ; - adapter Config -> settings Transport ; - smoke tests réseau opt-in et documentation finale. ## Validation Non exécutée dans le sandbox de génération : `cargo` et `rustc` n'y sont pas installés. Après application : ```bash cargo fmt --all cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-onchain-transport-lib cargo test --workspace ``` Le graphe de dépendances n'ajoute aucune crate nouvelle, mais la feature locale `tokio/sync` devient directement utilisée par Transport. Pour auditer le feature-set effectif de cette tranche : ```bash cargo tree -p ksp-onchain-transport-lib -e features cargo tree -p ksp-onchain-transport-lib -e normal ``` ## Suite Tranche suivante prévue : ```text 0.2.1-pre.005 ``` Périmètre : exécuteur HTTP JSON-RPC réel + quatre méthodes canari typées `getHealth`, `getVersion`, `getGenesisHash`, `getBalance`, avec fixtures déterministes et raccordement des timeouts/429/erreurs transport à la résilience de `pre.004`.