# Delta `0.2.11-pre.003` — HTTP REST commun et rate limiting ## 1. Base requise Cette tranche s'applique exclusivement après : ```text v0.2.10 + 0.2.11-pre.001 + 0.2.11-pre.001-fix.001 + 0.2.11-pre.002 + 0.2.11-pre.002-fix.001 + 0.2.11-pre.002-fix.002 + 0.2.11-pre.002-fix.003 ``` La version Cargo attendue à l'entrée est : ```text 0.2.11-pre.2.fix.3 ``` La version Cargo de sortie est : ```text 0.2.11-pre.3 ``` ## 2. Gate d'entrée acquis L'opérateur a validé `0.2.11-pre.002-fix.003` le `2026-08-25` avec : ```text cargo fmt --all exécuté python3 scripts/audit_rust_workspace_rules.py clean python3 scripts/audit_markdown_tables.py clean, 116 tables / 105 files cargo check --workspace PASS cargo clippy --workspace --all-targets PASS cargo test -p ksp-offchain-transport-lib PASS, 10 unit + 1 boundary + 2 public API ``` `cargo test --workspace` n'a pas été fourni pour cet état exact et n'est pas déclaré PASS ici. ## 3. Objectif Matérialiser les primitives HTTP REST communes de `ksp-offchain-transport-lib` et le rate limiting local sans avancer aucun provider. La tranche doit préparer directement `pre.004+` tout en conservant les frontières : ```text http_* = mécanique transport crate-wide et crate-private market_price_* = première capacité métier, SOL/USD uniquement aucun client HTTP générique exporté aux consumers aucun SDK provider aucun Config -> Off-chain ajouté avant pre.009 aucun refresh registry/service avant pre.007/pre.008 ``` ## 4. HTTP REST commun `http_client.rs` matérialise un `reqwest::Client` réutilisable par les adapters internes. La construction impose explicitement : ```text rustls redirects désactivés Referer automatique désactivé proxy système désactivé retries implicites reqwest désactivés User-Agent KSP explicite connect timeout borné request timeout borné ``` `reqwest` reste une dépendance directe de la crate et aucun SDK provider n'est ajouté. La requête GET interne : ```text accepte uniquement HTTPS pour les adapters runtime refuse credentials dans l'URL encode les query pairs via Url permet des headers sensibles marqués sensitive redacte URL et headers dans Debug ``` Un constructeur HTTP non HTTPS existe uniquement sous `cfg(test)` pour les serveurs loopback déterministes. ## 5. Bornes de réponse et JSON Les valeurs initiales sont : ```text connect timeout défaut = 5 s connect timeout hard max = 30 s request timeout défaut = 10 s request timeout hard max = 120 s body défaut = 1 MiB body hard max = 4 MiB ``` Le body est borné pendant sa lecture par chunks. La présence ou l'absence de `Content-Length` ne permet donc pas de contourner la limite. Après lecture, la syntaxe JSON est validée avec `serde::de::IgnoredAny`. Le document brut borné reste disponible pour le futur adapter typed, ce qui évite de faire passer un nombre provider par une représentation générique `f64` avant le parsing `MarketPriceDecimal`. ## 6. Classification HTTP Les codes crate-wide ajoutés sous le domaine `offchain_transport` distinguent : ```text http_settings_invalid http_client_build_failed http_request_invalid http_connection_failed http_timeout http_request_failed http_access_denied http_rate_limited http_temporary_failure http_response_too_large http_invalid_json http_rate_limit_invalid ``` La classification de statut est : ```text 401/403 -> access denied 429 -> rate limited 408 et 5xx -> temporary failure autre non-2xx -> request failed 2xx -> body borné puis JSON syntaxiquement validé ``` Les erreurs `reqwest` sont converties après `without_url()`. Aucun body remote n'est recopié dans `KspError`. `Retry-After` est actuellement exploité sous sa forme `delta-seconds`, bornée défensivement à une heure. Une forme HTTP-date non parseable reste ignorée plutôt que devinée. ## 7. Admission et cooldown `http_admission.rs` fournit un limiter non bloquant. Les policies internes sont : ```text Fixed Dynamic Unlimited ``` Une policy fixe utilise un token bucket lissé : ```text requests/window burst explicite uniquement lorsqu'il est documenté burst absent -> capacité locale conservatrice de 1 ``` Une policy dynamique n'invente aucune cadence locale. Elle applique seulement les cooldowns appris du provider. `try_admit()` ne dort jamais : il retourne immédiatement `Ready` ou `Deferred(duration)`. Cette propriété prépare le `refresh multiple/all` de `pre.008`, qui devra continuer avec les autres providers au lieu d'attendre un provider en cooldown. Un `429` peut étendre le cooldown avec `Retry-After`; la durée provider est bornée à une heure et comparée au fallback local. ## 8. Tests ajoutés Les nouveaux tests unitaires couvrent : ```text settings HTTP par défaut et bornes pathologiques policy fixe invalide burst absent lissé conservativement burst documenté consommé atomiquement refill déterministe du token bucket cooldown provider et borne Retry-After Debug request sans URL/credential GET JSON valide redirect refusé body chunked dépassant la limite 429 + Retry-After sans propagation du body canari JSON invalide après statut 2xx ``` Les canaries d'intégration vérifient aussi : ```text reqwest rustls présent Config toujours absent SDK providers toujours absents tracing direct toujours absent modules http_* présents HttpRestClient non exporté publiquement redirect/no-proxy/no-retry/without_url explicites dans le client codes d'erreur HTTP dans le domaine offchain_transport ``` ## 9. Fichiers ajoutés ```text crates/ksp-offchain-transport-lib/src/http_admission.rs crates/ksp-offchain-transport-lib/src/http_client.rs crates/ksp-offchain-transport-lib/src/http_settings.rs crates/ksp-offchain-transport-lib/unit_tests/http_admission.rs crates/ksp-offchain-transport-lib/unit_tests/http_client.rs crates/ksp-offchain-transport-lib/unit_tests/http_settings.rs deltas/0.2.11/pre.003.md ``` ## 10. Fichiers modifiés ```text Cargo.toml crates/ksp-offchain-transport-lib/Cargo.toml crates/ksp-offchain-transport-lib/src/error.rs crates/ksp-offchain-transport-lib/src/lib.rs crates/ksp-offchain-transport-lib/tests/dependency_boundary.rs crates/ksp-offchain-transport-lib/tests/public_api.rs docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md ``` ## 11. Fichiers supprimés ```text aucun ``` ## 12. Fichiers volontairement inchangés ```text CHANGELOG.md ROADMAP.md README.md .env.example config/** crates/ksp-config-lib/** crates/ksp-onchain-transport-lib/** prompts/** ``` `ROADMAP.md` et `CHANGELOG.md` restent hors de la tranche conformément à leur ownership de release. ## 13. Validations exécutées 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 python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.11 clean contrôle lignes Rust > 160 sur ksp-offchain-transport-lib PASS inspection diff contre 0.2.11-pre.002-fix.003 aucun provider adapter ajouté aucune dépendance ksp-config-lib ajoutée aucune dépendance tracing directe ajoutée ``` ## 14. Validations non exécutées dans le sandbox Le sandbox de génération ne fournit pas `cargo`, `rustc` ou `rustfmt`. Après application, exécuter : ```bash cargo fmt --all python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.11 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-offchain-transport-lib cargo test --workspace ``` ## 15. Décisions et points différés Aucune question ne bloque `pre.004`. Restent volontairement différés : ```text mapping MarketPriceProviderRateLimit -> HttpAdmissionPolicy par provider CoinGecko/CoinMarketCap/CoinPaprika wire DTOs et endpoints credentials provider effectifs registry et availability runtime refresh single/multiple Config std.offchain_transport Retry-After HTTP-date si un provider retenu l'exige réellement POST/écritures HTTP pour futures familles off-chain ``` `pre.004` reste propriétaire du premier lot d'adapters : CoinGecko, CoinMarketCap et CoinPaprika.