Feature #1454
open[EPIC] Backup OKX : parité fonctionnelle Kraken → OKX, bascule (failover) et masquage des comptes
0%
Description
### Problème
Tout le PSCA de Qwarks repose aujourd'hui sur un **unique exchange, Kraken** : compte maître, sous-comptes d'orientation (Dynamique / Équilibré / TOP 5 / Bitcoin) et compte de liquidité. OKX n'est utilisé que pour conserver les crypto-actifs appartenant à Qwarks (frais de gestion et commissions de performance), en ségrégation des actifs clients.
Un incident majeur sur Kraken interrompt donc **l'intégralité de la gestion**, sans dispositif de reprise. La note technique interne *« Mise en place d'un backup complet des sous-comptes PSCA sur OKX »* (V1, réf. Annexe 6 — Schéma des flux financiers Qwarks du 23 mars 2026) demande un dispositif de backup à **parité fonctionnelle complète Kraken → OKX**, activable en cas d'incident. Exigence relevant du périmètre PSCA/MiCA (art. 75, 77, 81, 82) et de la résilience opérationnelle **DORA**.
### Contexte (technique)
Étude de faisabilité menée par lecture directe du code de `QwarksConnectApi`, `QwarksConnectWeb` et `QwarksConnectMaintenance` (document `OKX_BACKUP_STUDY.md`). Constats structurants :
- **Ce n'est pas un copier-coller de l'intégration Kraken.** Il n'existe **aucune abstraction d'exchange** aujourd'hui : `KrakenClient` est un type Go **concret** (~1450 lignes) injecté directement dans ~5 services cœur (`WalletCommand`, `TokenQuery`, `KrakenQuery`, `WalletStateCalculator`, serveur token). Faire coexister deux exchanges avec bascule impose d'introduire une interface + des adaptateurs et de recâbler tous les points d'injection. **Ce refactor — qui touche les flux financiers et la valorisation — est le vrai coût et le vrai risque**, davantage que les appels API OKX.
- **Mais la frontière exchange est étroite** : tout l'aval (attribution des ordres, valorisation, frais/HWM, reporting) travaille déjà sur des **tables normalisées** (`sales_order`, `token_price`, `kraken_ledger`, `wallet_state`). L'exchange n'intervient qu'à **l'ingestion**, et seules **8 méthodes** du client sont réellement utilisées dans le cœur. La logique financière n'a donc **pas** à être dupliquée.
- **OKX n'est pas un Kraken bis** : auth à 3 champs (clé + secret + **passphrase**), HMAC-SHA256 (timestamp+méthode+path+body) vs HMAC-SHA512+nonce ; nommage des actifs différent (`BTC` vs `XXBT`, `EUR` vs `ZEUR`) ; pagination par **curseur** vs offset ; sémantique ordres/ledger différente ; staking/rewards et paires synthétiques potentiellement non mappables 1:1. Surtout : **la cotation OKX est souvent en USDT/USDC alors que toute la valorisation QC est en EUR**.
- **Aucun mécanisme de masquage de comptes n'existe** aujourd'hui, à aucun niveau (ni API, ni BFF, ni Web).
- **Secrets** : la note impose AWS Secrets Manager / KMS (prod `eu-west-3`) ; il n'y en a **aucun** dans le code. `service-configuration.*` et `okx_keys.yaml` sont bien gitignorés aujourd'hui, mais des **clés API Kraken en clair subsistent dans l'historique Git** de `QwarksConnectApi` (ajoutées dans `4e62f32`, fichier supprimé dans `90a28a3` — la suppression ne purge pas l'historique). Voir *Points ouverts*.
### Comportement proposé
Architecture retenue (détail complet dans `OKX_BACKUP_STUDY.md`) :
- **Port `ExchangeClient`** (8 méthodes) avec deux adaptateurs : `KrakenClient` (existant, devient le 1er adaptateur, sans changement de comportement) et `OKXClient` (nouveau). Chaque adaptateur **normalise vers l'espace de tokens QC, en EUR**.
- **Factory par compte** : client immuable lié à ses credentials → supprime la mutation `SetAPIKey`, aujourd'hui non thread-safe.
- **Exchange actif stocké en base** (table `exchange_failover_state`, append-only) : bascule **instantanée, auditable, réversible, sans redéploiement** — conforme DORA. Pas de flag en configuration : un incident est le pire moment pour déployer. Endpoint admin `POST /exchange/failover` avec double validation + `reason` / `incident_ref`, et UI de confirmation reprenant le pattern `DialogSafetyRefreshKraken`.
- **Provenance en base** : colonne `exchange` sur `exchange_ledger` (ex-`kraken_ledger`), `sales_order` et `token_price`, avec unicité `(exchange, external_id)` → **le non-double-comptage devient un invariant SQL**, à la bascule comme au retour vers Kraken.
- **Masquage** : attribut `is_backup` filtré **côté serveur dans la BFF** (`wallets_details`, `stats`, `orders`, `transaction`). Le masquage côté Web n'est que de la défense en profondeur cosmétique — jamais la seule barrière.
- **Modèle wallets recommandé** : OKX alimente les **mêmes** wallets clients (source alternative), et non un univers parallèle de wallets miroirs. À confirmer (**O1**) — ce choix simplifie le masquage et la réversibilité, et supprime des classes de risque entières.
### Découpage et estimation
**40 à 72 jours-homme** (1 dev Go + Next expérimenté, ~8 h/j), soit **≈ 8 à 14 semaines** en solo. Détail dans `OKX_BACKUP_ESTIMATION_CLIENT.csv`.
- **Phase 0 — Cadrage** (2-4 j) : cartographie OKX, structure des sous-comptes, liste d'actifs éligibles, devise de cotation, spécification de la table de mapping actifs/paires. Réponses aux questions O1-O7.
- **Phase 1 — Abstraction exchange** (6-10 j) : extraction du port `ExchangeClient` + factory par compte, `KrakenClient` en 1er adaptateur, suppression de `SetAPIKey`, recâblage des 5 points d'injection et du cron. Refactor **pur**, verrouillé par les tests d'intégration existants.
- **Phase 2 — Adaptateur OKX** (8-15 j) : auth/signature HMAC-SHA256 + headers `OK-ACCESS-*`, gestion des sous-comptes, mapping des endpoints (`orders-history`, `account/bills`, `deposit-history`, `market/history-candles`, `market/ticker`, `public/instruments`, `earn/staking`), pagination par curseur, rate-limit.
- **Phase 3 — Secrets** (3-5 j) : `SecretsProvider` (fichier/env en dev, AWS Secrets Manager `eu-west-3` + KMS en prod), moindre privilège, rotation ; migration des clés Kraken sur le même mécanisme.
- **Phase 4 — Base de données** (3-5 j) : migrations goose — `kraken_ledger` → `exchange_ledger` + colonne `exchange` (backfill `kraken`), unicité `(exchange, ledger_id)`, colonnes `exchange` sur `sales_order` / `token_price`, table `exchange_failover_state`, colonnes `exchange` + `is_backup` sur `wallet`.
- **Phase 5 — Bascule + réversibilité** (4-7 j) : service `FailoverState`, endpoint admin, lecture de l'exchange actif par le cron/sync, garde-fous anti double-comptage (un seul exchange actif par scope, pas de fenêtre active chevauchante, dedup source-aware).
- **Phase 6 — Masquage des comptes** (3-5 j) : filtrage serveur dans la BFF + masquage Web.
- **Phase 7 — Parité valorisation** (5-10 j) : table de mapping opérationnelle, alignement de l'heure de référence minuit (bougie `1Dutc` OKX → stamp 23:30 UTC), pont USDT/USDC → EUR, job de contrôle pré-bascule qui **échoue** si un token détenu n'a pas de route de prix EUR, diff de parité des prix vs Kraken.
- **Phase 8 — DORA** (2-4 j) : runbook de bascule, entrée au registre des prestataires TIC, procédure de rollback, drill de reprise scripté.
- **Phase 9 — Tests et livraison** (5-9 j) : tests de contrat des adaptateurs (Kraken + OKX sur la même suite), harnais de parité rejouant un jeu d'ordres connu dans les deux adaptateurs et diffant les `sales_order` / `wallet_state` résultants, re-run intégration complet.
**Démarrables sans attendre les accès OKX** : phases 1, 3 et 4 — le chantier peut donc commencer utilement dès maintenant.
**Long poles** (dépendants de l'API/sandbox OKX et de la liste d'actifs éligibles) : phases 2, 7 et 9.
### Risques principaux
- **R1 — Incohérence de valorisation (cotation USDT vs EUR, fuseau minuit).** Portée : faux états sur **tout le livre**, matériel et difficile à détecter. Parade : table de mapping + route EUR par token, job pré-bascule bloquant, diff de parité vs Kraken.
- **R2 — Couplage du namespace tokens (`BTC` ≠ `XXBT`).** Tokens dupliqués → holdings éclatés. Parade : `ResolveToken` OKX mappe vers le token canonique existant + contrôle anti-duplication.
- **R3 — Double-comptage / perte d'historique à la bascule ET au retour.** Parade : index unique `(exchange, external_id)`, un seul exchange actif par scope, rapport de réconciliation post-bascule.
- **R4 — Rayon de blast du refactor du client concret** (mauvais compte ingéré si un site d'injection est oublié). Parade : phase 1 sans changement de comportement, le compilateur trouve tous les sites.
- **R5 — Clés Kraken en clair dans l'historique Git.** Portée : fonds réels (trading / retrait non autorisé). Parade : **rotation des clés Kraken**, migration AWS Secrets Manager, purge/scrubbing de l'historique.
- **R6 — Fuite de masquage** (compte OKX visible par un client) → incident confidentialité/conformité. Parade : filtrage serveur + test « un compte backup n'apparaît JAMAIS dans une réponse client ».
- **R7 — Non thread-safe** si deux exchanges tournent en parallèle (corruption de la clé active). Parade : factory → clients immuables.
- **R8 — Staking/rewards & reporting des frais OKX** possiblement indisponibles par sous-compte → sous-déclaration silencieuse. Parade : vérifier les endpoints `earn` tôt (O3), sinon mode dégradé défini avec la conformité.
- **R9 — Collision de préfixe de numéro de compte** (défaut → 0) → comptes mal orientés.
- **R10 — Exposition réglementaire DORA/MiCA.** Parade : runbook + registre append-only + drills de reprise.
### Points ouverts à trancher (Marc / Alexandre) — bloquants pour chiffrer le haut de fourchette
- **O1 — Modèle wallets** : OKX alimente les **mêmes** wallets QC (recommandé) ou des wallets miroirs séparés ?
- **O2 — Auth sous-comptes** : une clé maître OKX avec routage, ou un jeu clé+secret+passphrase par sous-compte ?
- **O3 — Staking/earn et frais OKX** disponibles par sous-compte via API ?
- **O4 — Devise de cotation des comptes d'orientation OKX** : EUR, ou USDT/USDC ? *(plus gros facteur de variation du chiffrage)*
- **O5 — Liste d'actifs éligibles OKX** confirmée vs jeu de tokens QC actuel.
- **O6 — Continuité HWM / commission de performance** au travers d'une bascule en cours de période ?
- **O7 — Granularité du failover** : global (incident exchange) ou par orientation ? (v1 = global)
- **O8 — Sécurité, à traiter sans attendre le reste** : rotation des clés Kraken présentes en clair dans l'historique Git + purge de l'historique. À suivre dans un ticket SecOps dédié.
> Tant que **O1 / O2 / O4** ne sont pas tranchées, le haut de fourchette (phases 2 et 7) reste un plafond souple : monde USDT + clé par sous-compte + wallets séparés → haut de fourchette ; monde EUR + clé maître + single-set → bas de fourchette.
### Hors périmètre développement
Le **déclenchement** de la bascule (quand et comment l'activer, information client, réversibilité opérationnelle) est une décision opérationnelle et de conformité, à cadrer avec Marc et la conformité — pas à trancher côté développement.
## Acceptance criteria
- [ ] Un port `ExchangeClient` existe, avec `KrakenClient` comme adaptateur ; **aucun changement de comportement** sur le flux Kraken (tests d'intégration existants verts).
- [ ] `SetAPIKey` est supprimé : chaque compte obtient un client immuable via une factory.
- [ ] Un adaptateur `OKXClient` couvre les 8 méthodes du port (ordres, ledger, dépôts, prix minuit, prix live, résolution de paires, rewards) et passe **la même suite de tests de contrat** que Kraken.
- [ ] Les sous-comptes OKX (maître, Dynamique, Équilibré, TOP 5, Bitcoin, liquidité) sont connectés à QC.
- [ ] Les ordres OKX sont alloués aux comptes clients (numéro à 5 chiffres) selon la même logique que Kraken.
- [ ] Les frais de gestion et commissions de performance OKX sont récupérés et alloués.
- [ ] Les prix OKX à minuit alimentent les `wallet_state`, **en EUR**, sur la même heure de référence que Kraken.
- [ ] Toute donnée ingérée porte son `exchange` d'origine, avec unicité `(exchange, external_id)` en base.
- [ ] Un job de contrôle pré-bascule **échoue** si un token détenu n'a pas de route de prix EUR.
- [ ] La bascule s'effectue via un endpoint admin (double validation + `reason`/`incident_ref`), **sans redéploiement**, et est tracée dans une table append-only.
- [ ] Le retour vers Kraken est possible **sans double comptage ni perte d'historique** ; un rapport de réconciliation post-bascule le démontre.
- [ ] En fonctionnement normal, un compte `is_backup` **n'apparaît jamais** dans une réponse client — filtrage vérifié **côté serveur (BFF)**, avec test dédié.
- [ ] Aucune clé n'est stockée en clair : les secrets Kraken et OKX passent par AWS Secrets Manager / KMS (`eu-west-3`), en moindre privilège, avec rotation.
- [ ] Un harnais de parité rejoue un jeu d'ordres connu dans les deux adaptateurs et diffe les `sales_order` / `wallet_state` résultants sans écart.
- [ ] Le runbook de bascule, l'entrée au registre DORA et un drill de reprise scripté (préprod : bascule OKX → run cron → comparaison des états → retour Kraken) sont livrés.
## Classification
- feature
## Complexity
- 9/10 — Le chiffrage (40-72 j-h, ≈ 8-14 semaines solo) tient moins aux appels API OKX qu'à l'introduction d'une abstraction d'exchange dans un code où `KrakenClient` est un type concret injecté dans les 5 services qui portent les flux financiers et la valorisation ; s'y ajoutent une migration de schéma avec backfill, un pont de valorisation USDT/USDC → EUR, un mécanisme de bascule réversible sans double-comptage, un chantier secrets, et une exigence de conformité DORA/MiCA — le tout sur des données financières réelles où une erreur silencieuse fausse tout le livre.