v0.2.11-pre.009

This commit is contained in:
2026-08-26 11:23:01 +02:00
parent 2fdff17651
commit 69294a153f
27 changed files with 1864 additions and 133 deletions

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md -->
<!-- version: 15 -->
<!-- version: 16 -->
# Plan `0.2.11` — Off-chain price transport SOL/USD multi-provider
**Statut courant : `0.2.11-pre.008-fix.001` corrige le gate de `pre.008` sans modifier son contrat fonctionnel. `MarketPriceService` possède toujours 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`. Le fix supprime un warning Clippy `collapsible_if` et remplace un canari lexical trop large sur le mot `consensus` par des vérifications de surfaces exécutables réellement interdites. `MarketPriceProviderSetup` reste la frontière provider-specific destinée à la composition/Config de `pre.009`.**
**Statut courant : `0.2.11-pre.009` matérialise la frontière Config -> Off-chain Transport. `cfg.std.offchain_transport` et `schema.std.offchain_transport` possèdent désormais un domaine `market_price` avec les huit providers V1, des profils `public_keyless` et `all_free`, les credentials résolus exclusivement depuis une provenance Config classée Secret, et une adresse de paire DexScreener publique lorsqu'elle provient de l'environnement. `ResolvedOffchainTransportConfig` construit `MarketPriceService` sans créer de dépendance inverse depuis Off-chain Transport. Les limites provider restent possédées par la crate runtime et ne deviennent pas des cadences libres de Config.**
## 1. Base et autorité
@@ -520,28 +520,43 @@ Le limiter `pre.003` est non bloquant. Une cadence fixe est matérialisée par u
## 15. Config provider-capability-aware
Config reste l'unique propriétaire des documents/env/secrets.
Off-chain Transport expose ses settings publics ; Config adapte un futur document standard dédié, pressenti :
Config reste l'unique propriétaire des documents, de l'environnement et des secrets. `pre.009` matérialise les identifiants durables :
```text
file_id = std.offchain_transport
file_id document = cfg.std.offchain_transport
file_id schema = schema.std.offchain_transport
fichier = std.offchain_transport.json
schema = std.offchain_transport.schema.json
format_version = 1
```
Les branches de configuration correspondent aux capacités réelles de chaque provider.
Le document reste générique Off-chain Transport et place la capacité actuelle sous `market_price`. Cette structure évite de transformer le document durable en Config dédiée uniquement au prix lorsque d'autres familles telles que `swap_quote` apparaîtront.
| Provider | Champs spécifiques V1 attendus |
|-------------------|------------------------------------------------------------|
| CoinGecko | enabled, access_mode keyless ou demo, api_key optionnelle |
| CoinMarketCap | enabled, access_mode keyless ou basic, api_key optionnelle |
| CoinPaprika | enabled |
| Kraken | enabled |
| Coinbase Exchange | enabled |
| Jupiter | enabled, access_mode keyless ou free, api_key optionnelle |
| Birdeye | enabled, api_key requise si activé |
| DexScreener | enabled, sol_usd_pair_address requise si activé |
Deux profils versionnés servent de canaries :
Le schema final doit conditionner la présence des credentials au mode choisi. Aucun secret n'est versionné. Toute variable d'environnement ajoutée respecte `KSP_SECRET_*` et est inventoriée dans `.env.example`.
```text
public_keyless = providers réellement utilisables sans secret ; Birdeye et DexScreener désactivés
all_free = modes gratuits à clé activés + paire DexScreener explicite
```
Les branches correspondent aux capacités réelles de chaque provider.
| Provider | Champs spécifiques V1 |
|-------------------|---------------------------------------------------------------|
| CoinGecko | enabled, access_mode keyless ou demo, api_key conditionnelle |
| CoinMarketCap | enabled, access_mode keyless ou basic, api_key conditionnelle |
| CoinPaprika | enabled |
| Kraken | enabled |
| Coinbase Exchange | enabled |
| Jupiter | enabled, access_mode keyless ou free, api_key conditionnelle |
| Birdeye | enabled, api_key requise si activé |
| DexScreener | enabled, sol_usd_pair_address requise si activé |
Le schema conditionne les credentials au mode choisi et interdit un faux champ `api_key` sur les modes keyless. L'adapter effectif applique en plus la provenance : une API key provider doit provenir d'une variable classée Secret (`KSP_SECRET_*` ou namespace KSPB secret admis par Config), même si un littéral non vide est syntaxiquement valide JSON. Les erreurs et `Debug` ne recopient jamais le credential.
La paire DexScreener n'est pas un secret. Un littéral validé comme Pubkey Solana est admis ; si la valeur vient de l'environnement, sa provenance doit rester Public, avec le canari versionné `KSP_PUBLIC_DEXSCREENER_SOL_USD_PAIR_ADDRESS`. Pour respecter « requise seulement si activé », `MarketPriceDexScreenerSettings` accepte désormais l'absence de paire lorsque le provider est désactivé et la refuse toujours lorsqu'il est activé.
`.env.example` inventorie les variables nécessaires sans valeur réelle. Les applications desktop empaquetent aussi le nouveau document et son schema parce que `prepare_packaged_runtime` synchronise l'intégralité du registry Config.
Les limites provider ne deviennent pas des valeurs libres permettant à Config/UI d'augmenter la cadence. Une future policy pourra permettre un throttle **plus strict** si un besoin réel apparaît.
@@ -736,7 +751,7 @@ Implémentation des deux adapters exchange keyless avec DTOs wire privés et end
**Statut : réalisé, gate opérateur ciblé PASS ; workspace complet non rejoué.**
Implémentation des adapters Jupiter Price V3 et DexScreener avec DTOs wire privés et endpoints HTTPS officiels fixes. Jupiter distingue keyless à 0,5 requête par seconde et Free key à 1 requête par seconde sur le même `api.jup.ag`, utilise uniquement le mint WSOL et conserve `blockId` comme provenance de récence sans le convertir en timestamp. DexScreener exige un pair address Solana explicite dans ses settings, appelle uniquement `/latest/dex/pairs/solana/{pairId}`, vérifie `chainId = solana`, le pair address retourné et le mint WSOL comme base avant de lire `priceUsd`. Aucune découverte de pool, aucun tri de liquidité, aucun consensus et aucun registry global ne sont avancés.
Implémentation des adapters Jupiter Price V3 et DexScreener avec DTOs wire privés et endpoints HTTPS officiels fixes. Jupiter distingue keyless à 0,5 requête par seconde et Free key à 1 requête par seconde sur le même `api.jup.ag`, utilise uniquement le mint WSOL et conserve `blockId` comme provenance de récence sans le convertir en timestamp. DexScreener exige un pair address Solana explicite lorsqu'il est activé, appelle uniquement `/latest/dex/pairs/solana/{pairId}`, vérifie `chainId = solana`, le pair address retourné et le mint WSOL comme base avant de lire `priceUsd`. Aucune découverte de pool, aucun tri de liquidité, aucun consensus et aucun registry global ne sont avancés.
### `pre.007` — Birdeye, registry et availability
@@ -754,9 +769,9 @@ Implémentation de Birdeye Standard avec `X-API-KEY`, endpoint Price Single Sola
### `pre.009` — Config Off-chain Transport
**Statut : planifié.**
**Statut : réalisé ; gate Cargo opérateur à rejouer.**
Document standard `std.offchain_transport`, schema, exemples, secrets d'environnement nécessaires et adapter Config -> Off-chain Transport conforme aux capacités de chaque provider.
Ajout de `cfg.std.offchain_transport` / `schema.std.offchain_transport`, du document standard V1, de son schema strict, d'un exemple et de la fixture Config. Le domaine `market_price` configure les huit providers sans rendre leurs cadences modifiables. `public_keyless` fonctionne sans secret ; `all_free` référence quatre credentials `KSP_SECRET_*` et une paire DexScreener `KSP_PUBLIC_*`. `ResolvedOffchainTransportConfig` résout l'environnement, contrôle la provenance des credentials/public fields et construit `MarketPriceService` via `MarketPriceProviderSetup`, sans dépendance inverse. Les deux applications desktop embarquent les nouvelles ressources Config. La tranche réconcilie aussi DexScreener pour qu'une paire soit requise uniquement quand le provider est activé.
### `pre.010` — Hardening et complétude technique

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md -->
<!-- version: 14 -->
<!-- version: 15 -->
# Validation `0.2.11` — Off-chain price transport SOL/USD
@@ -261,30 +261,57 @@ Les tests déterministes de `pre.007` couvrent l'auth Birdeye et sa redaction, l
## 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 `pre.008` brut | FAIL | 1 warning Clippy + 1 canari boundary FAIL |
| fix `pre.008-fix.001` statique | PASS | audits Rust/Markdown sandbox |
| gate Cargo du fix | PLANNED | à exécuter par l'opérateur |
| 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 `pre.008` brut | FAIL | 1 warning Clippy + 1 canari boundary FAIL |
| fix `pre.008-fix.001` statique | PASS | audits Rust/Markdown sandbox |
| gate Cargo du fix | PASS | gate opérateur complet du 2026-08-26 |
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. Le gate opérateur initial a toutefois trouvé un warning `clippy::collapsible_if` et un canari boundary trop large (`!service.contains("consensus")`) qui échouait sur la rustdoc décrivant justement l'absence de consensus. `pre.008-fix.001` corrige ces deux points sans modifier le comportement runtime.
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. Le gate opérateur initial a toutefois trouvé un warning `clippy::collapsible_if` et un canari boundary trop large (`!service.contains("consensus")`) qui échouait sur la rustdoc décrivant justement l'absence de consensus. `pre.008-fix.001` corrige ces deux points sans modifier le comportement runtime. Le gate rejoué sur `0.2.11-pre.8.fix.1` est intégralement PASS : audits Rust/Markdown, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, 54 tests unitaires + 6 boundary + 7 public API Off-chain, puis `cargo test --workspace` avec uniquement les smokes/timing explicitement ignorés.
## 3.12 Gate `0.2.11-pre.009`
| Critère | Statut | Preuve |
|----------------------------------------------------------------------|---------|----------------------------------------------------------------|
| version workspace `0.2.11-pre.9` | PASS | `Cargo.toml` racine |
| document `cfg.std.offchain_transport` enregistré | PASS | registry Config + `std.offchain_transport.json` |
| schema `schema.std.offchain_transport` enregistré | PASS | registry Config + schema Draft 2020-12 |
| domaine actuel imbriqué sous `market_price` | PASS | document/schema V1 |
| profils `public_keyless` et `all_free` versionnés | PASS | document standard + exemple |
| huit branches provider Config obligatoires | PASS | schema + fixture |
| credentials conditionnés par mode/provider | PASS | `if/then` du schema |
| credentials effectifs exigent une provenance Config Secret | PASS | adapter `offchain_transport.rs` + tests |
| littéral credential rejeté par l'adapter effectif | PASS | test `literal_or_nonsecret_provider_credentials...` |
| DexScreener pair requis seulement lorsque le provider est activé | PASS | schema + `MarketPriceDexScreenerSettings` |
| Dex pair via environnement exige une provenance Public | PASS | adapter Config + test |
| `.env.example` inventorie quatre secrets et la paire publique | PASS | `.env.example` |
| Config construit `MarketPriceService` via `MarketPriceProviderSetup` | PASS | `ResolvedOffchainTransportConfig` |
| Off-chain Transport ne dépend pas de Config | PASS | dependency ownership + manifest |
| aucune cadence provider libre ajoutée à Config | PASS | schema/document sans rate-limit override |
| Config Debug/safe view ne divulgue pas les API keys | PASS | tests adapter |
| applications desktop embarquent document + schema | PASS | deux `tauri.conf.json` + canaries desktop |
| audit Rust sandbox | PASS | clean, 0 candidate export |
| audit Markdown sandbox | PASS | clean, 123 tables / 115 fichiers |
| `cargo check` / Clippy / tests Config et workspace | PLANNED | gate opérateur requis après application de l'archive `pre.009` |
La tranche n'ajoute aucune URL provider configurable, aucun SDK provider et aucune lecture directe de l'environnement dans `ksp-offchain-transport-lib`. Les secrets restent résolus par `ksp-config-lib`; le service runtime reçoit uniquement les settings typés nécessaires. La modification DexScreener est capability-aware : une paire absente est valide uniquement lorsque l'adapter est désactivé, ce qui permet de conserver son entrée dans l'inventaire générique sans fabriquer de fausse configuration.
## 4. Matrice provider prévue
@@ -374,32 +401,32 @@ Avant la stable, les conditions d'usage et la persistance des offres gratuites d
## 9. DexScreener V1
| Invariant | Statut |
|----------------------------------------------|--------|
| pair address SOL/USD explicite dans settings | PASS |
| `chainId = solana` vérifié | PASS |
| identité SOL de la paire vérifiée | PASS |
| `priceUsd` utilisé comme prix commun | PASS |
| aucune recherche automatique de pool | PASS |
| aucun tri automatique par liquidité | PASS |
| aucune moyenne/consensus entre pools | PASS |
| Invariant | Statut |
|--------------------------------------------------|--------|
| pair address SOL/USD explicite si provider actif | PASS |
| `chainId = solana` vérifié | PASS |
| identité SOL de la paire vérifiée | PASS |
| `priceUsd` utilisé comme prix commun | PASS |
| aucune recherche automatique de pool | PASS |
| aucun tri automatique par liquidité | PASS |
| aucune moyenne/consensus entre pools | PASS |
## 10. Config -> Off-chain Transport
| Invariant | Statut |
|------------------------------------------------------------------|---------|
| Off-chain Transport ne dépend pas de Config | PLANNED |
| document standard offchain dédié | PLANNED |
| branches provider conditionnelles dans schema | PLANNED |
| CoinGecko keyless/Demo représentés | PLANNED |
| CoinMarketCap keyless/Basic représentés | PLANNED |
| Jupiter keyless/Free représentés | PLANNED |
| Birdeye exige credential lorsqu'activé | PLANNED |
| DexScreener exige pair address lorsqu'activé | PLANNED |
| providers sans auth n'exposent pas de faux champ API key | PLANNED |
| secrets via Config uniquement | PLANNED |
| `.env.example` synchronisé pour les secrets réellement ajoutés | PLANNED |
| aucune cadence provider configurable au-dessus de la limite sûre | PLANNED |
| Invariant | Statut |
|------------------------------------------------------------------|--------|
| Off-chain Transport ne dépend pas de Config | PASS |
| document standard offchain dédié | PASS |
| branches provider conditionnelles dans schema | PASS |
| CoinGecko keyless/Demo représentés | PASS |
| CoinMarketCap keyless/Basic représentés | PASS |
| Jupiter keyless/Free représentés | PASS |
| Birdeye exige credential lorsqu'activé | PASS |
| DexScreener exige pair address lorsqu'activé | PASS |
| providers sans auth n'exposent pas de faux champ API key | PASS |
| secrets via Config uniquement | PASS |
| `.env.example` synchronisé pour les secrets réellement ajoutés | PASS |
| aucune cadence provider configurable au-dessus de la limite sûre | PASS |
## 11. Frontière future `ksp-app-solprices-desk`