v0.2.11-pre.008

This commit is contained in:
2026-08-26 10:23:42 +02:00
parent 5aeff5ca14
commit 87d7314bf4
13 changed files with 972 additions and 63 deletions

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md -->
<!-- version: 13 -->
<!-- version: 14 -->
# Plan `0.2.11` — Off-chain price transport SOL/USD multi-provider
**Statut courant : `0.2.11-pre.007` complète les huit adapters `market_price` avec Birdeye Standard et matérialise le registry provider-neutral. Birdeye exige `X-API-KEY`, utilise `/defi/price` sur Solana/WSOL, modélise 1 RPS par compte, 30 000 compute units de quota informatif et 3 compute units par requête SOL/USD. Le registry expose uniquement descriptor + état générique en ordre déterministe ; le dispatch/refresh reste réservé à `pre.008`. Le gate opérateur ciblé de `pre.006` est PASS ; `cargo test --workspace` n'a pas été rejoué sur l'état exact `pre.006`.**
**Statut courant : `0.2.11-pre.008` matérialise le service générique de refresh SOL/USD au-dessus des huit adapters V1. `MarketPriceService` possède le dispatch provider, le registry runtime et les transitions d'availability ; les consumers utilisent uniquement les provider ids opaques, `registry()`, `refresh`, `refresh_many` et `refresh_all`. Les limites/cooldowns restent non bloquants et aucun fallback, consensus ou scheduler provider n'est introduit. `MarketPriceProviderSetup` reste la frontière provider-specific destinée à la composition/Config de `pre.009`. Le gate opérateur complet de `pre.007` est PASS, y compris `cargo test --workspace`.**
## 1. Base et autorité
@@ -436,7 +436,7 @@ misconfigured
disabled
```
`MarketPriceProviderAvailability` et `MarketPriceProviderState` matérialisent cette projection générique dès `pre.002`. `pre.007` ajoute `MarketPriceProviderRegistry` et `MarketPriceProviderRegistryEntry` : le registry est borné, trié de façon déterministe par `provider_id`, rejette les doublons et n'expose que `descriptor + state`. Son module ne contient aucun dispatch par nom de provider. Le registry reste une projection générique sans méthode de dispatch ni transition réseau ; le futur service `pre.008` possédera les mutations runtime et republiera les états mis à jour.
`MarketPriceProviderAvailability` et `MarketPriceProviderState` matérialisent cette projection générique dès `pre.002`. `pre.007` ajoute `MarketPriceProviderRegistry` et `MarketPriceProviderRegistryEntry` : le registry est borné, trié de façon déterministe par `provider_id`, rejette les doublons et n'expose que `descriptor + state`. Son module ne contient aucun dispatch par nom de provider. `pre.008` ajoute `MarketPriceService`, propriétaire du dispatch réel et des mutations d'availability, tout en conservant le registry comme projection générique détachable. `MarketPriceProviderSetup` contient uniquement la sélection provider-specific nécessaire à la composition initiale ; elle ne traverse pas les opérations runtime destinées à la HID.
`MarketPriceProviderAvailability` expose aussi génériquement l'éligibilité immédiate au refresh et un `retry_at` lorsqu'il est réellement connu. `AuthenticationUnavailable`, `QuotaUnavailable`, `Misconfigured`, `Disabled`, `CoolingDown`, `TemporarilyUnavailable` et `Ready` restent distincts sans parsing provider dans les consumers.
@@ -516,7 +516,7 @@ Il n'existe pas d'URL provider arbitraire dans la Config V1. Cela évite SSRF, r
Les primitives `http_*` sont **crate-private** : elles ne créent pas un client HTTP générique public contournant les capacités métier. Les adapters `market_price_*`, puis de futures familles telles que `swap_quote_*`, les consomment derrière leur propre contrat.
Le limiter `pre.003` est non bloquant. Une cadence fixe est matérialisée par un token bucket lissé ; lorsqu'aucun burst n'est documenté, la capacité locale initiale reste volontairement `1`. Lorsqu'un burst est documenté, sa capacité est indépendante du nombre moyen de requêtes de la fenêtre et peut donc lui être supérieure. Une limite dynamique n'invente aucune cadence locale et apprend seulement des réponses provider, notamment `429`. Un `Retry-After` serveur peut prolonger le cooldown mais ne peut pas dépasser une borne défensive d'une heure. Aucune primitive HTTP commune ne dort en attendant la disponibilité : elle expose un délai de defer que l'orchestrateur `pre.008` pourra projeter provider par provider.
Le limiter `pre.003` est non bloquant. Une cadence fixe est matérialisée par un token bucket lissé ; lorsqu'aucun burst n'est documenté, la capacité locale initiale reste volontairement `1`. Lorsqu'un burst est documenté, sa capacité est indépendante du nombre moyen de requêtes de la fenêtre et peut donc lui être supérieure. Une limite dynamique n'invente aucune cadence locale et apprend seulement des réponses provider, notamment `429`. Un `Retry-After` serveur peut prolonger le cooldown mais ne peut pas dépasser une borne défensive d'une heure. `pre.008` projette ces délais dans `MarketPriceProviderAvailability` et ne dort jamais : `refresh`, `refresh_many` et `refresh_all` retournent immédiatement un état non éligible lorsqu'un prochain instant admissible est encore dans le futur.
## 15. Config provider-capability-aware
@@ -740,15 +740,15 @@ Implémentation des adapters Jupiter Price V3 et DexScreener avec DTOs wire priv
### `pre.007` — Birdeye, registry et availability
**Statut : réalisé, gate Cargo opérateur à exécuter.**
**Statut : réalisé, gate opérateur complet PASS.**
Implémentation de Birdeye Standard avec `X-API-KEY`, endpoint Price Single Solana/WSOL, prix exact et timestamp provider réel. Le descriptor modélise 1 RPS par compte, 30 000 compute units de quota informatif et 3 compute units par refresh SOL/USD. `MarketPriceProviderRegistry` finalise l'inventaire runtime provider-neutral des huit adapters sous forme d'entrées `descriptor + state`, avec ordre déterministe et doublons rejetés. Les transitions runtime et le dispatch restent réservés à `pre.008`. Aucun dispatch de refresh, Config ou smoke live n'est avancé.
### `pre.008` — Refresh individuel et multiple
**Statut : planifié.**
**Statut : réalisé, gate Cargo opérateur à exécuter.**
Service de refresh single/multiple, orchestration rate-limit-aware et tests cross-provider sans scheduling provider dans les consumers.
`MarketPriceService` possède les huit adapters configurés, le registry runtime et le dispatch provider-specific interne. La construction accepte `MarketPriceProviderSetup` pour la future couche de composition, puis l'usage devient intégralement provider-agnostic via `registry()`, `refresh(provider_id)`, `refresh_many(...)` et `refresh_all()`. Les échecs provider sont classés en availability générique ; un résultat en erreur n'interrompt pas les autres providers d'un batch. Les états non éligibles sont projetés sans requête réseau, les cooldowns expirés redeviennent tentables, et aucune opération ne dort pour attendre un rate limit. Aucun fallback, consensus, agrégation, Config ou scheduling périodique n'est avancé.
### `pre.009` — Config Off-chain Transport

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md -->
<!-- version: 12 -->
<!-- version: 13 -->
# Validation `0.2.11` — Off-chain price transport SOL/USD
@@ -234,30 +234,55 @@ Les fixtures de `pre.006` couvrent Jupiter keyless/Free, redaction de la clé, p
## 3.10 Gate `0.2.11-pre.007`
| Critère | Statut | Preuve |
|------------------------------------------------------------------|---------|---------------------------------------|
| version workspace `0.2.11-pre.7` | PASS | `Cargo.toml` racine |
| Birdeye Standard SOL/USD adapter public | PASS | `market_price_birdeye.rs` |
| Birdeye exige `X-API-KEY` lorsqu'activé | PASS | settings + header sensible |
| Birdeye fixe `x-chain = solana` et le mint WSOL | PASS | request builder |
| Birdeye Price Single `value` parsé exactement | PASS | `RawValue` -> `MarketPriceDecimal` |
| Birdeye `updateUnixTime` conservé comme timestamp provider | PASS | parser provider |
| Birdeye Standard 1 RPS compte modélisé | PASS | descriptor provider |
| quota Birdeye 30 000 compute units informatif | PASS | `MarketPriceProviderLongTermQuota` |
| coût Price Single 3 compute units informatif | PASS | `MarketPriceProviderRequestCost` |
| registry provider-neutral matérialisé | PASS | `MarketPriceProviderRegistry` |
| huit providers V1 présents dans le canari de complétude registry | PASS | test `market_price_registry` |
| registry trié par provider id et doublons rejetés | PASS | constructeur public générique + tests |
| entry publique limitée à descriptor + état générique | PASS | `MarketPriceProviderRegistryEntry` |
| availability expose éligibilité et `retry_at` génériques | PASS | `MarketPriceProviderAvailability` |
| registry sans mutation ou dispatch réseau | PASS | projection générique publique |
| registry ne contient aucun dispatch/name provider | PASS | dependency boundary |
| refresh single/multiple, Config et smoke live non avancés | PASS | scope réservé à `pre.008+` |
| audit Rust sandbox | PASS | clean, 0 candidate export |
| audit Markdown sandbox | PASS | clean, 121 tables / 112 fichiers |
| gate Cargo | PLANNED | à exécuter par l'opérateur |
| Critère | Statut | Preuve |
|------------------------------------------------------------------|--------|---------------------------------------|
| version workspace `0.2.11-pre.7` | PASS | `Cargo.toml` racine |
| Birdeye Standard SOL/USD adapter public | PASS | `market_price_birdeye.rs` |
| Birdeye exige `X-API-KEY` lorsqu'activé | PASS | settings + header sensible |
| Birdeye fixe `x-chain = solana` et le mint WSOL | PASS | request builder |
| Birdeye Price Single `value` parsé exactement | PASS | `RawValue` -> `MarketPriceDecimal` |
| Birdeye `updateUnixTime` conservé comme timestamp provider | PASS | parser provider |
| Birdeye Standard 1 RPS compte modélisé | PASS | descriptor provider |
| quota Birdeye 30 000 compute units informatif | PASS | `MarketPriceProviderLongTermQuota` |
| coût Price Single 3 compute units informatif | PASS | `MarketPriceProviderRequestCost` |
| registry provider-neutral matérialisé | PASS | `MarketPriceProviderRegistry` |
| huit providers V1 présents dans le canari de complétude registry | PASS | test `market_price_registry` |
| registry trié par provider id et doublons rejetés | PASS | constructeur public générique + tests |
| entry publique limitée à descriptor + état générique | PASS | `MarketPriceProviderRegistryEntry` |
| availability expose éligibilité et `retry_at` génériques | PASS | `MarketPriceProviderAvailability` |
| registry sans mutation ou dispatch réseau | PASS | projection générique publique |
| registry ne contient aucun dispatch/name provider | PASS | dependency boundary |
| refresh single/multiple, Config et smoke live non avancés | PASS | scope réservé à `pre.008+` |
| audit Rust sandbox | PASS | clean, 0 candidate export |
| audit Markdown sandbox | PASS | clean, 121 tables / 112 fichiers |
| gate Cargo | PASS | gate opérateur complet du 2026-08-26 |
Les tests déterministes de `pre.007` couvrent l'auth Birdeye et sa redaction, le descriptor 1 RPS / 30 000 CU / 3 CU, le parsing exact du prix et du timestamp, les réponses invalides, ainsi que le registry complet des huit providers, son ordre déterministe, le rejet de doublons et les projections d'availability. Aucun refresh réseau générique ni smoke live n'est revendiqué dans cette tranche.
Les tests déterministes de `pre.007` couvrent l'auth Birdeye et sa redaction, le descriptor 1 RPS / 30 000 CU / 3 CU, le parsing exact du prix et du timestamp, les réponses invalides, ainsi que le registry complet des huit providers, son ordre déterministe, le rejet de doublons et les projections d'availability. Le gate opérateur du 2026-08-26 est intégralement PASS : audits Rust/Markdown, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, tests ciblés Off-chain et `cargo test --workspace`. Aucun smoke live provider n'est revendiqué.
## 3.11 Gate `0.2.11-pre.008`
| Critère | Statut | Preuve |
|-------------------------------------------------------------------------|---------|--------------------------------------------|
| version workspace `0.2.11-pre.8` | PASS | `Cargo.toml` racine |
| `MarketPriceService` provider-agnostic public | PASS | `market_price_service.rs` |
| setup provider-specific limité à la composition | PASS | `MarketPriceProviderSetup` |
| registry détachable sans dispatch provider | PASS | `MarketPriceService::registry` |
| refresh individuel par `MarketPriceProviderId` | PASS | `MarketPriceService::refresh` |
| refresh multiple par liste d'identifiants opaques | PASS | `MarketPriceService::refresh_many` |
| refresh all en ordre provider déterministe | PASS | `MarketPriceService::refresh_all` |
| doublons / provider inconnu rejetés avant dispatch | PASS | tests `market_price_service` |
| provider non éligible projeté sans requête réseau | PASS | eligibility + test huit providers disabled |
| `429` / defer local projetés en `CoolingDown` | PASS | classification générique |
| `401/403` keyed projetés en `AuthenticationUnavailable` | PASS | classification auth-aware |
| transport temporaire projeté sans rendre le service global inutilisable | PASS | outcome provider-neutral |
| aucune attente/sleep de rate limit dans le service | PASS | dependency boundary |
| aucun fallback, consensus ou agrégation multi-provider | PASS | scope + dependency boundary |
| Config, scheduling périodique et smoke live non avancés | PASS | scope réservé à `pre.009+` |
| audit Rust sandbox | PASS | clean, 0 candidate export |
| audit Markdown sandbox | PASS | clean, 122 tables / 113 fichiers |
| gate Cargo | PLANNED | à exécuter par l'opérateur |
Les tests déterministes de `pre.008` vérifient le registry de service sur les huit setups V1, `refresh_all` sans réseau lorsque tous les providers sont désactivés, le rejet pré-dispatch des doublons/providers inconnus, les classes auth/rate-limit/temporaire/local misconfiguration et l'éligibilité temporelle des cooldowns. Les méthodes multiples restent séquentielles et déterministes dans V1 ; elles ne dorment jamais pour attendre un provider et ne font aucun fallback/consensus.
## 4. Matrice provider prévue
@@ -278,21 +303,21 @@ Avant la stable, les conditions d'usage et la persistance des offres gratuites d
## 5. Contrat public prévu
| Invariant | Statut |
|------------------------------------------------------------------------|---------|
| surface publique limitée à SOL/USD dans V1 | PASS |
| identifiants provider opaques pour les consumers | PASS |
| provenance provider toujours observable | PASS |
| sémantique de prix observable sans prétendre à une équivalence | PASS |
| timestamp requête/réception KSP présents | PASS |
| timestamp provider optionnel seulement lorsqu'il est réellement fourni | PASS |
| absence de prix distincte de zéro | PASS |
| `f64` non utilisé comme canon public | PASS |
| aucune structure wire provider exportée | PASS |
| aucune URL/header/asset id provider requis côté app | PASS |
| registry provider et état runtime possédés par Off-chain Transport | PASS |
| refresh provider par identifiant générique | PLANNED |
| refresh multiple sans connaissance provider côté consumer | PLANNED |
| Invariant | Statut |
|------------------------------------------------------------------------|--------|
| surface publique limitée à SOL/USD dans V1 | PASS |
| identifiants provider opaques pour les consumers | PASS |
| provenance provider toujours observable | PASS |
| sémantique de prix observable sans prétendre à une équivalence | PASS |
| timestamp requête/réception KSP présents | PASS |
| timestamp provider optionnel seulement lorsqu'il est réellement fourni | PASS |
| absence de prix distincte de zéro | PASS |
| `f64` non utilisé comme canon public | PASS |
| aucune structure wire provider exportée | PASS |
| aucune URL/header/asset id provider requis côté app | PASS |
| registry provider et état runtime possédés par Off-chain Transport | PASS |
| refresh provider par identifiant générique | PASS |
| refresh multiple sans connaissance provider côté consumer | PASS |
## 6. Rate limiting et availability
@@ -304,10 +329,10 @@ Avant la stable, les conditions d'usage et la persistance des offres gratuites d
| `429` classé | PASS |
| `Retry-After` honoré lorsqu'exploitable | PASS |
| cooldown expose prochain instant admissible | PASS |
| refresh all ne lance que les providers éligibles | PLANNED |
| provider en cooldown ne bloque pas les autres | PLANNED |
| provider down ne rend pas le service global inutilisable | PLANNED |
| auth manquante distinguée d'un transport down | PLANNED |
| refresh all ne lance que les providers éligibles | PASS |
| provider en cooldown ne bloque pas les autres | PASS |
| provider down ne rend pas le service global inutilisable | PASS |
| auth rejetée distinguée d'un transport down | PASS |
| quota indisponible distingué d'un transport down | PLANNED |
| quota mensuel local non présenté comme compteur authoritative | PASS |