Files
2026-08-25 21:00:19 +02:00

8.3 KiB

Delta 0.2.11-pre.003 — HTTP REST commun et rate limiting

1. Base requise

Cette tranche s'applique exclusivement après :

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 :

0.2.11-pre.2.fix.3

La version Cargo de sortie est :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

Fixed
Dynamic
Unlimited

Une policy fixe utilise un token bucket lissé :

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 :

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 :

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

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

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

aucun

12. Fichiers volontairement inchangés

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

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 :

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 :

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.