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

7.5 KiB

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 :

0.2.11-pre.7

Version Cargo de sortie :

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 :

MarketPriceService
MarketPriceProviderSetup
MarketPriceRefreshOutcome
refresh(provider_id)
refresh_many(provider_ids)
refresh_all()
transitions runtime d'availability

La tranche n'ajoute pas :

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 :

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 :

is_refresh_eligible_at(now)

Cette méthode autorise :

Ready
CoolingDown dont retry_at est expiré
TemporarilyUnavailable sans retry_at
TemporarilyUnavailable dont retry_at est expiré

et bloque :

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 :

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 :

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 :

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 :

MarketPriceProviderSetup
MarketPriceRefreshOutcome
MarketPriceService
ERROR_CODE_MARKET_PRICE_PROVIDER_NOT_FOUND
ERROR_CODE_MARKET_PRICE_REFRESH_INVALID
MarketPriceProviderAvailability::is_refresh_eligible_at

MarketPriceRefreshOutcome expose uniquement :

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 :

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 :

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

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

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

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.