Files
khadhroony-solana-project/deltas/0.2.11/pre.008.md
2026-08-26 10:23:42 +02:00

243 lines
7.5 KiB
Markdown

<!-- file: deltas/0.2.11/pre.008.md -->
<!-- version: 1 -->
# Delta `0.2.11-pre.008` — Refresh individuel + multiple/all
## 1. Base requise
Cette tranche s'applique exclusivement après `0.2.11-pre.007` accepté par l'opérateur.
Version Cargo attendue à l'entrée :
```text
0.2.11-pre.7
```
Version Cargo de sortie :
```text
0.2.11-pre.8
```
Le gate opérateur du `2026-08-26` est entièrement PASS : `cargo fmt --all`, audit Rust clean avec 0 candidate export, audit Markdown clean à 121 tables / 112 fichiers, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, `cargo test -p ksp-offchain-transport-lib` avec 49 unitaires + 5 boundary + 6 public API, puis `cargo test --workspace` complet PASS avec uniquement les smokes/diagnostics explicitement ignorés.
## 2. Objet strict de la tranche
`pre.008` matérialise l'orchestrateur générique de la famille `market_price` :
```text
MarketPriceService
MarketPriceProviderSetup
MarketPriceRefreshOutcome
refresh(provider_id)
refresh_many(provider_ids)
refresh_all()
transitions runtime d'availability
```
La tranche n'ajoute pas :
```text
Config Off-chain Transport
lecture directe d'environnement
scheduler périodique
fallback provider
consensus / moyenne / agrégation de prix
smoke live provider
nouvelle famille off-chain
```
## 3. Frontière composition / runtime
`MarketPriceProviderSetup` est l'unique enum provider-specific introduit par l'orchestrateur. Il consomme les settings déjà publics des huit adapters V1 et est destiné à la couche de composition, notamment au futur adapter Config de `pre.009`.
Une fois le service construit, les opérations runtime ne prennent plus aucun type provider-specific :
```text
registry()
refresh(&MarketPriceProviderId)
refresh_many(&[MarketPriceProviderId])
refresh_all()
```
La future HID peut donc rester indépendante de CoinGecko, Birdeye, Kraken, Jupiter ou de tout autre provider concret.
## 4. Registry runtime et availability
`MarketPriceService` possède les adapters concrets et une copie mutable interne du `MarketPriceProviderRegistry`.
Le registry public reste une projection détachable `descriptor + state`. Deux helpers crate-private permettent au service de remplacer l'availability sans modifier l'identité du provider.
`MarketPriceProviderAvailability` gagne :
```text
is_refresh_eligible_at(now)
```
Cette méthode autorise :
```text
Ready
CoolingDown dont retry_at est expiré
TemporarilyUnavailable sans retry_at
TemporarilyUnavailable dont retry_at est expiré
```
et bloque :
```text
AuthenticationUnavailable
Disabled
Misconfigured
QuotaUnavailable
CoolingDown non expiré
TemporarilyUnavailable avec retry_at futur
```
Aucun timer/sleep n'est lancé pour rendre un provider éligible : l'état est simplement réévalué lors du prochain appel explicite.
## 5. Classification générique des erreurs
Le service absorbe les erreurs provider/HTTP après dispatch et retourne un `MarketPriceRefreshOutcome` générique.
Les classes principales sont :
```text
provider disabled -> Disabled
401/403 sur mode avec API key -> AuthenticationUnavailable
401/403 sur mode keyless -> TemporarilyUnavailable
admission locale deferred -> CoolingDown
HTTP 429 -> CoolingDown
HTTP 408/5xx -> TemporarilyUnavailable
client/settings/request localement invalides -> Misconfigured
autre erreur provider/transport -> TemporarilyUnavailable
```
Un `Retry-After` exploitable ou un délai local de defer est converti en `retry_at` absolu. Aucun message/body provider n'est projeté au consumer ; le code d'erreur est seulement journalisé via `ksp-logging-lib` avec le provider id validé.
`QuotaUnavailable` reste une catégorie publique mais n'est pas inventée automatiquement lorsque le provider ne fournit pas une preuve exploitable de quota épuisé.
## 6. Refresh individuel
`MarketPriceService::refresh` :
```text
résout le provider par identifiant opaque
projette immédiatement les états non éligibles
n'effectue aucun sleep
appelle uniquement l'adapter propriétaire du provider
met l'état à Ready après succès
classe un échec dans l'availability générique
retourne toujours une outcome provider-neutral pour un provider configuré
```
Un provider inconnu retourne `ERROR_CODE_MARKET_PRICE_PROVIDER_NOT_FOUND` avant tout réseau.
## 7. Refresh multiple / all
`refresh_many` conserve l'ordre fourni par le caller et valide la liste complète avant dispatch :
```text
maximum 64 providers
doublons interdits
provider inconnu interdit
```
Chaque provider configuré retourne son outcome propre. Une erreur réseau/provider normalisée ne court-circuite donc pas les providers suivants.
`refresh_all` utilise l'ordre stable des provider ids du service. Les providers non éligibles sont inclus comme projections d'état mais ne déclenchent pas de requête réseau.
V1 reste volontairement séquentielle et déterministe. Cette décision ne constitue pas un scheduler : aucun provider n'est attendu par sleep et aucun fallback ou consensus n'est exécuté.
## 8. Façade publique
Ajouts publics :
```text
MarketPriceProviderSetup
MarketPriceRefreshOutcome
MarketPriceService
ERROR_CODE_MARKET_PRICE_PROVIDER_NOT_FOUND
ERROR_CODE_MARKET_PRICE_REFRESH_INVALID
MarketPriceProviderAvailability::is_refresh_eligible_at
```
`MarketPriceRefreshOutcome` expose uniquement :
```text
provider_id()
state()
observation()
refreshed()
```
Le consumer n'a pas accès au dispatcher interne, aux clients HTTP, aux limiters ou aux DTOs wire.
## 9. Tests déterministes ajoutés
Les nouveaux canaries couvrent :
```text
construction du service avec les huit setups V1 désactivés
registry de service trié et provider-neutral
refresh_all de huit providers désactivés sans réseau
ordre déterministe des outcomes
doublon dans refresh_many rejeté avant dispatch
provider inconnu rejeté avant dispatch
401/403 keyed vs keyless
429 -> CoolingDown
5xx + retry_after -> TemporarilyUnavailable daté
erreur locale de request -> Misconfigured
éligibilité avant/après retry_at
absence de sleep / fallback / consensus dans le service
façade publique générique depuis la crate root
```
## 10. Documentation réconciliée
Mise à jour de :
```text
docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md
docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md
```
Le gate complet de `pre.007` fourni par l'opérateur est enregistré comme PASS.
## 11. Fichiers ajoutés
```text
crates/ksp-offchain-transport-lib/src/market_price_service.rs
crates/ksp-offchain-transport-lib/unit_tests/market_price_service.rs
deltas/0.2.11/pre.008.md
```
## 12. Fichiers modifiés
```text
Cargo.toml
crates/ksp-offchain-transport-lib/src/error.rs
crates/ksp-offchain-transport-lib/src/lib.rs
crates/ksp-offchain-transport-lib/src/market_price_provider.rs
crates/ksp-offchain-transport-lib/src/market_price_registry.rs
crates/ksp-offchain-transport-lib/tests/dependency_boundary.rs
crates/ksp-offchain-transport-lib/tests/public_api.rs
crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs
docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md
docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md
```
## 13. Validation attendue
```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
```
`pre.009` ne doit commencer qu'après un gate propre de `pre.008` ou un delta fix explicite.