# Plan `0.5.3` — audit et normalisation de `ks-store` ## 1. Statut du document Ce document constitue le livrable de plan/audit de `0.5.3-pre.001`. La numérotation documentaire et des archives conserve trois chiffres (`pre.001`), tandis que la version SemVer Cargo correspondante est obligatoirement `0.5.3-pre.1` : les identifiants prerelease numériques SemVer ne peuvent pas comporter de zéro initial. `0.5.3-pre.001` ne modifie volontairement aucune table, migration SQL, requête de production ou API Rust de stockage. Les décisions ci-dessous doivent être validées avant la première migration SQL de `0.5.3`. La base auditée est la clôture `0.5.2` fournie dans l'archive finale du workspace. ## 2. Périmètre et décisions de cadrage `0.5.3` reste un chantier de stockage. Il couvre : - la normalisation du namespace `kb_sol_* -> k_sol_*` ; - le gel structurel des tables stabilisées ; - les temporalités blockchain et de persistance ; - la provenance, l'idempotence et le replay ; - la normalisation des migrations, contraintes, index et requêtes ; - la suppression des fuites PostgreSQL hors de `ks-store` ; - la mise en place d'une façade de stockage backend-agnostique ; - la remise à plat du contrat de configuration du backend sans casser les frontières `KS_*` / `KS_SECRET_*` ; - la définition des règles additives pour les futurs faits canoniques Solana. Sont explicitement hors périmètre : - la réconciliation générale des scénarios/exécuteurs de `0.5.4` ; - la migration générale des fixtures wallet de `0.5.4` ; - la régression de fixture Token-2022 reportée à `0.5.4` ; - l'infrastructure Anchor de `0.6.x` ; - l'implémentation des DEX de `0.7.x+` ; - une nouvelle refonte `.kswallet` ; - le renommage du repository/workspace. ### 2.1 Décisions structurantes de `pre.001` Les décisions suivantes sont retenues pour les tranches suivantes : 1. **les tables Solana actives passent toutes sous `k_sol_*`** ; aucune table Bot n'est incluse dans cette reconstruction ; 2. **les 13 tables Core/raw/decode/materialization actuelles constituent la baseline conservée** : aucune de ces 13 tables n'est fusionnée par principe ; `k_sol_core_instructions` conserve les instructions top-level et `k_sol_core_inner_instructions` conserve les CPI. La distinction physique est considérée comme une optimisation et une représentation fidèle des deux sources canoniques Solana, tandis que le contrat de lecture peut fournir une projection logique unifiée aux décodeurs ; des contrats N1/N2/N3 génériques supplémentaires restent possibles si l'audit de readiness en démontre la nécessité avant gel ; 3. **les CPI reçoivent les mêmes capacités de sélection, lifecycle/replay et provenance de processor que les top-level** avant gel, afin qu'elles puissent devenir des inputs de niveau 3 autonomes ; 4. **`block_time` devient une donnée SQL de premier rang** sur la transaction canonique raw et sur l'ancre Core transactionnelle ; il reste nullable et n'est jamais synthétisé ; 5. **les tables filles ne dupliquent pas `block_time`** lorsqu'il est résoluble efficacement par la transaction Core ; 6. **`kb_sol_mat_events` reste le journal générique versionné des sorties de matérialisation**, mais son nom durable devient `k_sol_mat_outputs`, cohérent avec son rôle réel ; les projections métier spécialisées seront additives ; 7. **aucune table DEX/trading n'est créée en `0.5.3`** : aucun chantier DEX n'a encore fourni les invariants nécessaires à un contrat SQL gelable ; lorsque ces matérialisations arriveront, les tables devront être génériques par fait/concept et non par protocole ; 8. **aucune projection metadata N4 n'est créée en `0.5.3`**, mais deux cibles conceptuelles sont déjà retenues : asset/token metadata commune Metaplex + Token-2022 et program metadata distincte pour SPM ; elles seront ajoutées lorsque leurs premiers contrats queryables seront suffisamment audités, puis resteront plus évolutives que les fondations N1-N3 ; 9. **les bases Devnet/Mainnet/test actuelles sont considérées destructibles pour `0.5.3`** : la cible est une reconstruction propre, pas une compatibilité in-place avec le schéma `0.5.2` ; l'ancien schéma doit être détecté/refusé plutôt que silencieusement mélangé au nouveau ; 10. **les ressources SQL PostgreSQL sont rangées sous `ks-store/migrations/postgres/`** et chaque fichier contient une seule instruction SQL ; les fonctions privées PostgreSQL utilisent `include_str!` pour exécuter la ressource correspondante ; 11. **tables, contraintes/keys, index et opérations de maintenance (`DROP`, `TRUNCATE`) sont séparés** en ressources/fonctions explicites et assemblés par un orchestrateur d'initialisation PostgreSQL transactionnel ; 12. **le DDL dupliqué en chaînes Rust disparaît** : le fichier SQL inclus est l'unique source du texte SQL exécuté ; 13. **`ks-store` expose une façade `Store` et des contrats fonctionnels backend-agnostiques** ; `PostgresStore`, `PgPool`, les types `PostgresReplay*` et les diagnostics `Postgres*` cessent d'être publics ; 14. **`ks-store` ne dépend pas de `ks-config`** : la frontière applicative transporte une configuration résolue, mais l'interprétation du backend et la création de la connexion sont internes à `ks-store` ; 15. **le bloc de configuration backend devient sélectionné et opaque jusqu'à `ks-store`**, afin que `ks-config` n'impose plus simultanément les paramètres de backends non sélectionnés ; 16. **`ks-store` expose un résumé runtime sans secrets** destiné notamment à `kb-app-demo-desktop` : backend actif, état de connexion, options publiques retenues, auto-initialisation, version/état du schéma et tables créées/vérifiées au démarrage ; 17. **les surfaces historiques `demo_sql_*` du desktop sont renommées structurellement en `demo_store_*` dès `pre.002`** ; la refonte fonctionnelle finale des tableaux/compteurs reste planifiée plus tard et aucune logique SQL/PostgreSQL ne doit revenir dans l'application ; 18. **toutes les opérations PostgreSQL portent un target de logging sous `ks-store.pg`** ; le SQL brut peut être journalisé au niveau `trace`, sans valeurs bindées ni secrets, tandis que les niveaux normaux journalisent l'identité d'opération, la durée et le résultat ; 19. **le contrat de page publique abandonne les gros offsets comme mécanisme durable** au profit d'une pagination stable/cursorisée pour les lectures qui en ont besoin ; aucune requête UI ne doit pouvoir demander 100 000 lignes en un appel ; 20. **les index non uniques sont des objets physiques évolutifs**, exclus du gel logique des tables ; les PK, FK, uniques, checks, types, nullabilités et sémantiques de colonnes sont gelés ; 21. **les anciens traits publics sans implémentation réelle sont supprimés ou remplacés** avant gel de l'API de crate ; 22. **le plan de prereleases est une trajectoire, pas une borne numérique rigide** : `pre.007` est seulement le premier candidat de clôture ; tout manque découvert ajoute une ou plusieurs prereleases et décale la clôture sans forcer une fermeture artificielle ; 23. **le vocabulaire public et nouveau de stockage utilise `top-level` plutôt que `outer`** : les instructions dites historiquement `outer` sont les instructions top-level de `transaction.message.instructions`; il n'existe pas de troisième famille physique `outer` à ajouter en plus de top-level et CPI/inner ; 24. **la hiérarchie CPI est un invariant Core à préserver avant gel** : `stack_height` doit être transporté explicitement lorsque disponible et `parent_instruction_path` doit désigner le parent CPI immédiat lorsque la pile permet de le reconstruire, pas seulement l'instruction top-level racine ; 25. **l'architecture de persistance distingue quatre niveaux** : N1 raw/acquisition, N2 Core canonique, N3 decode/materialization générique et N4 projections spécialisées/queryables ; les contrats N1/N2/N3 sont fortement gelés après `0.5.3`, sauf urgence ou omission structurelle majeure, tandis que N4 reste volontairement plus évolutif ; 26. **les replays inter-niveaux sont des contrats de premier rang** : N1 -> N2, N2 -> N3 et N3 -> N4 doivent pouvoir être rejoués indépendamment, de façon idempotente, versionnée et avec invalidation descendante contrôlée ; 27. **`k_sol_mat_outputs` est conservée impérativement comme journal N3 générique** même lorsque des projections N4 existent ; une projection spécialisée n'est jamais un remplacement de la sortie matérialisée canonique/versionnée ; 28. **les metadata auront à terme deux projections N4 conceptuelles distinctes** : une projection canonique d'asset/token alimentable par Metaplex Token Metadata et Token-2022, et une projection de metadata de programme pour Solana Program Metadata (SPM) ; elles ne sont pas créées en `0.5.3`, mais leur séparation conceptuelle est retenue ; 29. **le desktop ne doit plus exposer le backend concret** : les surfaces portent la nomenclature `demo_store_*` dès `pre.002` et seront refondues fonctionnellement vers des résultats tabulaires génériques, `demo_config` consommera un résumé store sanitisé sans champs PostgreSQL/SQLite concrets, et le splash vérifiera des modèles logiques avec compteurs d'objets sans afficher les noms physiques des tables/index ; 30. **la façade `Store` ouverte au démarrage est destinée à être conservée dans l'état applicatif** plutôt que recréée par chaque commande desktop ; `ks-store` possède ainsi la connexion, l'initialisation et les diagnostics runtime, tandis que l'application ne conserve qu'une capacité backend-agnostique ; 31. **le gel N1-N3 est conditionné par un canari de compatibilité avec les futurs mécanismes de décodage**, Anchor/IDL étant le premier cas concret : l'ajout d'un décodeur futur doit pouvoir se faire sans modification structurelle normale de N1/N2/N3. `0.5.3` n'implémente pas Anchor, mais doit vérifier que les niveaux gelés conservent les bytes d'instruction, comptes ordonnés et leurs attributs, hiérarchie CPI, logs, return data lorsqu'elle existe, temporalité, provenance et observations/états de comptes nécessaires à un redécodage ultérieur ; toute omission générique découverte par cet audit doit être corrigée avant gel ; 32. **les 13 tables existantes constituent la baseline auditée, pas un quota de tables finales** : leur séparation top-level/CPI est conservée, mais le nombre final peut augmenter en `0.5.3` si le canari de readiness démontre qu'un fait Solana générique indispensable manque dans N1/N2/N3. Aucune nouvelle table ne doit être ajoutée pour Anchor en tant que technologie ; seules des structures canoniques Solana indépendantes du mécanisme de décodage sont admissibles. ## 3. Sources actives auditées Le plan a été construit à partir des sources actives suivantes : - `README.md`, `ROADMAP.md`, `CHANGELOG.md`, `RULES.md` ; - l'ensemble de `docs/rules/` ; - `docs/architecture/ARCHITECTURE.md` ; - `docs/architecture/STORAGE_ARCHITECTURE.md` ; - `docs/architecture/PIPELINE_ARCHITECTURE.md` ; - `docs/architecture/CRATE_MAP.md` ; - `docs/architecture/SURFACE_CRATE_MATRIX.md` ; - `ks-store/README.md`, `USAGE.md`, `TODO.md`, `CHANGELOG.md` ; - tout `ks-store/src/` ; - les migrations `ks-store/migrations/0001` à `0004` ; - les scripts SQL de `ks-store/maintenance/` ; - les DTO, entités, repositories et requêtes PostgreSQL ; - `ks-pipeline` pour le backfill, Core extraction et decode replay ; - `ks-pipeline-demo-scenarios` pour les consommateurs de store ; - `kb-app-demo-desktop` pour les diagnostics/replay SQL ; - `config/store.config.json`, son exemple et ses schémas JSON ; - les guides et scénarios actifs PostgreSQL/Mainnet/Devnet ; - l'audit historique `0.5.0-pre.003` uniquement pour les constats qui n'étaient pas encore formalisés dans l'architecture active. ## 4. Inventaire exhaustif du schéma actif `0.5.2` Le schéma actif comporte **13 tables de baseline**. Ces 13 tables sont conservées et la séparation top-level/CPI est maintenue physiquement ; le nombre final de tables N1/N2/N3 n'est fixé qu'après le canari future-decoder, qui peut justifier l'ajout d'un contrat Solana générique manquant avant gel. | Table `0.5.2` | Domaine | Rôle actuel | Identité principale | Décision `0.5.3` | |---------------------------------------|-----------------|-------------------------------------------|---------------------------------------------------------------------|----------------------------------------------------------| | `kb_sol_raw_transactions` | raw | document canonique source-indépendant | `id`, signature unique | corriger puis geler sous `k_sol_raw_transactions` | | `kb_sol_obs_transaction_observations` | acquisition | provenance d'acquisition | `id`, `observation_key` unique | renommer puis geler | | `kb_sol_core_transactions` | Core | ancre transactionnelle normalisée | `id`, signature unique | corriger puis geler | | `kb_sol_core_account_keys` | Core | clés résolues de transaction | `id`, `(signature, account_index)` unique | renommer puis geler | | `kb_sol_core_instructions` | Core | instructions top-level + lifecycle | `id`, `(signature, instruction_path)` unique | corriger puis geler séparément | | `kb_sol_core_inner_instructions` | Core | instructions CPI contextuelles | `id`, `(signature, instruction_path)` unique | compléter lifecycle/replay puis geler séparément | | `kb_sol_core_logs` | Core | logs ordonnés et liens fiables | `id`, `(signature, log_index)` unique | renommer puis geler | | `kb_sol_core_balance_changes` | Core | variations natives/token génériques | `id`, `(signature, balance_change_index)` unique | renommer puis geler | | `kb_sol_ops_processing_ledger` | opérations | état courant d'idempotence/reprocessing | `(stage, processor_name, processor_version, input_key)` unique | renommer puis geler | | `kb_sol_decode_events` | decode | observations décodées versionnées | `(processor_name, processor_version, input_key, event_key)` unique | renommer puis geler | | `kb_sol_decode_coverage_declarations` | decode | couverture déclarée d'un décodeur/version | identité fonctionnelle composite | renommer puis geler | | `kb_sol_decode_coverage_observations` | decode | couverture réellement observée | `(processor_name, processor_version, input_key)` unique | renommer puis geler | | `kb_sol_mat_events` | materialization | journal générique des sorties versionnées | `(processor_name, processor_version, input_key, output_key)` unique | renommer en `k_sol_mat_outputs` puis geler comme journal | ### 4.1 Migrations actives Les quatre fichiers historiques actifs de `0.5.2` sont spécifiques PostgreSQL : | Migration `0.5.2` | Tables principales | Décision `0.5.3` | |-----------------------------------------|--------------------------------------------------------|-----------------------------------------------------------------------| | `0001_canonical_transaction_store.sql` | raw + observations d'acquisition | remplacée par les ressources atomiques PostgreSQL du nouveau baseline | | `0002_core_store.sql` | Core transaction/account/instruction/inner/log/balance | remplacée par les ressources atomiques PostgreSQL du nouveau baseline | | `0003_processing_ledger.sql` | ledger | remplacée par les ressources atomiques PostgreSQL du nouveau baseline | | `0004_decode_materialization_store.sql` | decode/coverage/materialization | remplacée par les ressources atomiques PostgreSQL du nouveau baseline | Ces fichiers ne sont pas conservés comme chaîne de compatibilité active : les bases Devnet/Mainnet/test doivent être supprimées/recréées avant validation de la nouvelle architecture. Leur historique reste disponible dans Git et les archives de versions précédentes. Constat important : le runtime reproduit aujourd'hui leur DDL sous forme de chaînes Rust dans `ks-store/src/postgres/migrations.rs`. Cette duplication est supprimée. En cible, chaque instruction SQL PostgreSQL existe dans **un seul fichier** sous `ks-store/migrations/postgres/` et la fonction Rust correspondante l'embarque par `include_str!`. ### 4.2 Maintenance SQL active Les scripts `maintenance/drop_raw_core_store.sql` et `maintenance/reset_derived_store_keep_raw.sql` sont également PostgreSQL-spécifiques et utilisent les noms `kb_sol_*`. La cible les remplace par des ressources unitaires rangées avec le backend PostgreSQL, par exemple : ```text ks-store/migrations/postgres/ schema/ tables/ constraints/ indexes/ maintenance/ truncate/ drop/ ``` Chaque fichier contient **une seule instruction SQL**. Les noms exacts seront normalisés pendant `pre.003`, mais la convention fonctionnelle visée est explicite, par exemple : ```text create_table_if_not_exists_k_sol_raw_transactions.sql add_constraint_pk_k_sol_raw_transactions.sql create_index_if_not_exists_ix_k_sol_raw_transactions_slot.sql truncate_table_k_sol_raw_transactions.sql drop_table_if_exists_k_sol_raw_transactions.sql ``` Les fonctions privées PostgreSQL correspondantes sont séparées et utilisent `include_str!`. L'initialiseur général appelle les opérations nécessaires dans un ordre déterministe et dans une transaction lorsque PostgreSQL le permet. ## 5. Matrice table -> DTO -> repository/query -> consommateurs | Table cible | DTO/entités actuels principaux | Contrat/repository | Requêtes | Consommateurs externes | |--------------------------------------|----------------------------------------------------------------------------------|-----------------------------------------------|----------------------------------------------------------------------|--------------------------------------------------------------| | `k_sol_raw_transactions` | `RawTransactionInsert`, `RawTransactionRow`, `RawPayloadLifecycleMark` | `RawTransactionStore`, `CoreExtractionStore` | `raw_queries`, `core_extraction_queries`, `replay_candidate_queries` | `ks-pipeline::backfill`, `ks-pipeline::core_extraction` | | `k_sol_obs_transaction_observations` | `TransactionObservationInsert`, `TransactionObservationRow` | `RawTransactionStore` | `raw_queries` | `ks-pipeline::backfill` | | `k_sol_core_transactions` | `CoreTransactionInsert`, `CoreTransactionRow` | `CoreTransactionStore`, `CoreExtractionStore` | `core_queries`, `core_extraction_queries`, replay | `ks-pipeline::core_extraction`, decode context indirect | | `k_sol_core_account_keys` | `CoreAccountKeyInsert`, `CoreAccountKeyRow` | `CoreTransactionStore` | `core_queries`, replay | `ks-pipeline::core_extraction`, decode replay context | | `k_sol_core_instructions` | actuellement `CoreInstructionInsert` + `CoreInnerInstructionInsert` et deux rows | `CoreTransactionStore`, `DecodePipelineStore` | `core_queries`, `decode_pipeline_queries`, replay | `ks-pipeline::core_extraction`, `ks-pipeline::decode_replay` | | `k_sol_core_logs` | `CoreLogInsert`, `CoreLogRow` | `CoreTransactionStore` | `core_queries`, replay | Core extraction/decode context | | `k_sol_core_balance_changes` | `CoreBalanceChangeInsert`, `CoreBalanceChangeRow` | `CoreTransactionStore` | `core_queries`, replay | Core extraction/decode context, recherche mint/owner/account | | `k_sol_ops_processing_ledger` | `ProcessingLedgerIdentity`, `ProcessingLedgerStatus`, `ProcessingLedgerRow` | `CoreExtractionStore`, `DecodePipelineStore` | core/decode/replay | Core extraction et decode/materialization | | `k_sol_decode_events` | `DecodeObservationInsert` | `DecodePipelineStore` | `decode_pipeline_queries` | `ks-pipeline::decode_replay` | | `k_sol_decode_coverage_declarations` | `DecodeCoverageDeclarationInsert` | `DecodePipelineStore` | `decode_pipeline_queries` | `ks-pipeline::decode_replay` | | `k_sol_decode_coverage_observations` | `DecodeCoverageObservationInsert`, `DecodeCoverageSummaryRow` | `DecodePipelineStore` | `decode_pipeline_queries` | pipeline + desktop diagnostics | | `k_sol_mat_outputs` | `MaterializedOutputInsert`, `MaterializedOutputQueryRow` | `DecodePipelineStore` | `decode_pipeline_queries` | pipeline, scénarios Devnet, desktop | ### 5.1 Contrats publics hérités sans implémentation Quatre traits publics actuels n'ont aucune implémentation PostgreSQL et représentent des contrats historiques incomplets : - `ProgramObservationStore` ; - `DecodedEventStore` ; - `MaterializedEventStore` ; - `ProcessingLedgerStore`. Leurs DTO historiques (`DecodedEventInsert`, `MaterializedEventInsert`, `ProcessingLedgerMark` et rows associés) sont plus pauvres que les contrats réellement utilisés par `DecodePipelineStore` et le ledger actuel. **Décision :** ne pas les figer. Ils sont supprimés si aucun consommateur n'est découvert au moment du delta d'API, ou remplacés par un contrat réellement implémenté. Un canari d'API externe vérifiera qu'aucune surface morte n'est exportée au crate-root. ## 6. Audit temporel ### 6.1 Concepts temporels retenus Les concepts suivants restent distincts : - `slot` : ordre blockchain, non convertible en temps civil par hypothèse ; - `block_time` : timestamp Unix fourni par Solana, nullable ; - `detected_at` : première détection locale éventuelle ; - `received_at` : réception du payload source ; - `normalized_at` : fin de normalisation canonique ; - `persisted_at` : insertion de l'observation d'acquisition ; - `created_at` : première création de la ligne logique dans la table concernée ; - `updated_at` : dernière mutation/reprocessing de cette ligne logique ; - temps de matérialisation : `created_at`/`updated_at` du journal `k_sol_mat_outputs`, jamais confondu avec `block_time`. ### 6.2 Matrice temporelle cible | Table cible | `slot` | `block_time` | acquisition | insertion/update | Justification | |--------------------------------------|------------|-------------------------|---------------------------------------------------------------|---------------------------------------------------------|------------------------------------------------------------------------| | `k_sol_raw_transactions` | requis | **nullable, à ajouter** | via table observations | `created_at`, `updated_at` | ancre canonique et requêtes temporelles raw sans parser JSONB | | `k_sol_obs_transaction_observations` | nullable | non | `detected_at`, `received_at`, `normalized_at`, `persisted_at` | `persisted_at` | table de transport/acquisition, pas de duplication du temps blockchain | | `k_sol_core_transactions` | requis | **nullable, à ajouter** | non | `created_at`, `updated_at` | ancre temporelle normalisée des faits Core et des projections dérivées | | `k_sol_core_account_keys` | requis | via transaction | non | `created_at` | duplication de `block_time` inutile | | `k_sol_core_instructions` | requis | via transaction | non | `created_at`, `updated_at` | lifecycle mutable, temps blockchain résolu par parent | | `k_sol_core_logs` | requis | via transaction | non | `created_at` | même ancre | | `k_sol_core_balance_changes` | requis | via transaction | non | `created_at` | même ancre | | `k_sol_ops_processing_ledger` | non imposé | non | non | `started_at`, `finished_at`, `created_at`, `updated_at` | temps de traitement uniquement | | `k_sol_decode_events` | requis | via transaction | non | `created_at`, `updated_at` | éviter duplication ; résolution par signature/Core | | `k_sol_decode_coverage_declarations` | non | non | non | `created_at`, `updated_at` | contrat de processeur, pas un fait blockchain | | `k_sol_decode_coverage_observations` | requis | via transaction | non | `created_at`, `updated_at` | observation de traitement | | `k_sol_mat_outputs` | requis | via transaction | non | `created_at`, `updated_at` | journal de matérialisation, pas source du temps on-chain | ### 6.3 Source de vérité de `block_time` `ks-lib::MdCanonicalTransaction.block_time: Option` est la source canonique. Règles : - `RawTransactionInsert::from_canonical()` doit transporter explicitement cette valeur ; - `CoreTransactionInsert` doit recevoir la même valeur pendant Core extraction ; - `None` reste `NULL` ; - aucune interpolation à partir du slot, du temps d'acquisition ou de `NOW()` ; - lorsqu'une transaction Core possède `raw_transaction_id`, un test vérifie l'égalité des deux `block_time` ; - le replay DTO `MdCoreInstructionReplayInput` doit évoluer vers un contrat incluant `block_time: Option` afin que les matérialiseurs futurs puissent produire des séries temporelles sans requête secondaire. ## 7. Audit structurel table par table ### 7.1 `kb_sol_raw_transactions` -> `k_sol_raw_transactions` **Propriétaire :** `ks-store`, domaine canonique Solana raw. **Rôle durable :** conserver l'identité et le document source-indépendant canonique d'une transaction, ainsi que son état de rétention et sa progression globale. **Correction avant gel :** - ajouter `block_time BIGINT NULL` ; - transporter la valeur depuis `MdCanonicalTransaction` ; - documenter `created_at` comme première insertion en base et `updated_at` comme dernière mutation de lifecycle/reprocessing ; - conserver `signature` unique comme clé d'idempotence ; - conserver le hash/version canonique ; - ne pas ajouter provider/endpoint : cette provenance appartient à `k_sol_obs_transaction_observations`. **État :** `à corriger`, puis `stable/frozen`. ### 7.2 `kb_sol_obs_transaction_observations` -> `k_sol_obs_transaction_observations` **Rôle durable :** journaliser une observation d'acquisition sans conserver inutilement le payload provider-spécifique. Le contrat temporel est déjà le plus riche du schéma. `observation_key` fournit l'idempotence ; `raw_transaction_id` relie éventuellement la transaction canonique ; provider/protocole/méthode/origin/session/filter et hash de payload fournissent la provenance d'acquisition. **Correction logique requise :** aucune colonne identifiée. Le renommage du namespace, des contraintes/index et la documentation des sémantiques suffisent. **État :** `stable/frozen` après migration de namespace. ### 7.3 `kb_sol_core_transactions` -> `k_sol_core_transactions` **Rôle durable :** ancre normalisée minimale d'une transaction Core : identité, slot, temps on-chain, état d'exécution et lineage raw. **Correction avant gel :** - ajouter `block_time BIGINT NULL` ; - conserver `failed` + `err_json` ; - conserver `raw_transaction_id` nullable pour lineage ; - ne pas élargir cette table avec toutes les métriques de runtime uniquement parce qu'elles existent dans le JSON canonique. Les frais, compute units, cost units, rewards ou futures métriques peuvent être représentés par de nouvelles projections Core reliées à `transaction_id` si un besoin métier/query stable le justifie. Cette séparation évite de remodeler l'ancre gelée. **État :** `à corriger`, puis `stable/frozen`. ### 7.4 `kb_sol_core_account_keys` -> `k_sol_core_account_keys` Le contrat `(signature, account_index)` est stable et couvre les clés statiques et chargées. Les flags signer/writable et l'executable nullable sont conservés. `slot` est une dénormalisation volontaire facilitant les requêtes ; sa cohérence avec la transaction parent doit être testée. **État :** `stable/frozen` après renommage. ### 7.5 `kb_sol_core_instructions` + `kb_sol_core_inner_instructions` La séparation physique est finalement **conservée**. Elle représente deux sources canoniques différentes : - les instructions du message transactionnel top-level ; - les instructions internes/CPI produites pendant l'exécution et rapportées dans les metadata. Dans un pipeline destiné à convertir un volume important de blockchain `raw -> Core`, cette séparation garde des tables et index plus petits pour les recherches top-level, évite une colonne parent nullable sur toutes les lignes et permet d'optimiser séparément les volumes CPI. Une table unique simplifierait certaines requêtes, mais ne fournit pas un gain de performance intrinsèque ; les lectures globales peuvent être exposées par une projection logique `UNION ALL` backend-interne ou par deux flux fusionnés derrière le repository générique. La faiblesse actuelle n'est donc **pas** l'existence de deux tables. Elle est l'asymétrie de contrat : - seules les top-level possèdent aujourd'hui `processing_state`, processor et lifecycle ; - seules les top-level sont sélectionnées directement comme inputs de decode ; - les CPI sont principalement chargées comme contexte ; - `stack_height`, déjà disponible dans le modèle canonique, n'est pas une colonne Core dédiée. Avant gel : - `k_sol_core_instructions` reste la table top-level ; - `k_sol_core_inner_instructions` reste la table CPI ; - les deux tables doivent pouvoir participer au niveau 3 avec un lifecycle/replay équivalent ; - `stack_height` doit être audité comme colonne Core nullable sur les deux contrats, car il existe déjà dans `MdCanonicalCompiledInstruction` et peut être utile à la reconstruction de l'invocation ; - `parent_instruction_path` reste spécifique aux CPI et doit pointer vers le parent CPI immédiat lorsque l'ordre + `stack_height` permettent de reconstruire la pile ; pour une CPI directement appelée par la top-level, il pointe vers cette top-level ; - l'identité unique reste `(signature, instruction_path)` dans chaque table ; - un DTO/read-model générique peut exposer `instruction_kind`, `instruction_path`, `parent_instruction_path: Option<_>` et les champs communs sans imposer une table physique unique. Le repository public doit permettre : - sélection top-level seule ; - sélection CPI seule ; - sélection logique de toutes les instructions ; - pagination/ordre déterministe communs lorsque les deux catégories sont demandées. Le choix de deux tables physiques est interne au backend. Un futur backend peut utiliser une représentation différente tant qu'il respecte le même contrat fonctionnel. Le terme historique `outer` ne crée aucune troisième catégorie : dans les nouveaux contrats de `ks-store`, il est remplacé par `top-level`. La readiness des futurs décodeurs impose en plus que `payload_json`/le modèle Core transportent sans perte la donnée binaire canonique de l'instruction et que `accounts_json` préserve l'ordre exact des comptes résolus et les informations nécessaires pour retrouver leurs attributs signer/writable. Cet audit doit être fait sur les top-level et les CPI, sans ajouter de champ nommé d'après Anchor. **État :** les deux tables sont `à corriger`, puis `stable/frozen` séparément. ### 7.6 `kb_sol_core_logs` -> `k_sol_core_logs` Le log reste une observation ordonnée de transaction. `instruction_path` et `program_id` sont nullable car le lien ne doit être renseigné que lorsqu'il est déterminé de manière fiable. Les logs sont aussi une donnée de décodage générique et ne doivent pas être traités comme de simples diagnostics jetables. Le canari future-decoder doit vérifier qu'ils restent suffisamment fidèles et ordonnés pour permettre notamment le décodage ultérieur d'événements produits dans les logs. Aucune colonne `block_time` n'est dupliquée. **État :** `stable/frozen` après renommage et validation de fidélité. ### 7.7 `kb_sol_core_balance_changes` -> `k_sol_core_balance_changes` Le rôle reste une représentation Core générique des variations natives/token, pas une table métier de balances courantes ni de trades. Les montants JSON permettent de conserver les représentations exactes actuelles sans imposer prématurément un schéma de trading. Les futurs facts token/portfolio utiliseront des tables additives avec types numériques explicitement choisis. Correction physique identifiée : index partiel manquant sur `owner`, car les requêtes replay actuelles l'agrègent déjà. **État logique :** `stable/frozen` après renommage. L'index `owner` n'est pas une modification du contrat logique. ### 7.8 `kb_sol_ops_processing_ledger` -> `k_sol_ops_processing_ledger` **Rôle durable :** état courant d'idempotence d'un couple stage/processor/version/input, pas journal immuable de tous les essais historiques. La contrainte unique actuelle est conservée. `input_hash` est mutable lors d'un nouveau traitement de la même identité ; `attempt_count` et les timestamps décrivent l'état agrégé courant. Si un audit exhaustif des tentatives devient nécessaire, il devra utiliser une nouvelle table append-only `processing_attempts` ou équivalent, reliée au ledger, sans remodeler ce contrat. Correction physique identifiée : un index par `(stage, input_key, updated_at, id)` est justifié par la requête de dernier état utilisée dans les candidats de replay. **État :** `stable/frozen` après renommage et documentation. ### 7.9 `kb_sol_decode_events` -> `k_sol_decode_events` La table possède déjà : processor/version, input key/hash, event key, signature/slot/path/program, protocol/surface/event, preuve, payload, état de transaction et commitment logique. Elle est une projection versionnée remplaçable pour un même processor/version/input/event, pas un historique immutable de toutes les exécutions. `block_time` est résolu par `signature -> k_sol_core_transactions` et n'est pas dupliqué. **État :** `stable/frozen` après renommage. ### 7.10 `kb_sol_decode_coverage_declarations` -> `k_sol_decode_coverage_declarations` La table décrit le contrat machine-readable d'un décodeur/version. Elle n'est pas temporelle blockchain. L'identité fonctionnelle composite existante est conservée. **État :** `stable/frozen` après renommage. ### 7.11 `kb_sol_decode_coverage_observations` -> `k_sol_decode_coverage_observations` La table décrit la couverture réellement observée pour un processor/version/input et conserve hash, signature, slot, path, program, classification, compteurs et état transactionnel. `block_time` est résolu via la transaction Core. **État :** `stable/frozen` après renommage. ### 7.12 `kb_sol_mat_events` -> `k_sol_mat_outputs` Le contenu actuel est un **journal générique versionné de sorties de matérialisation**, et non une table métier unique capable de servir toutes les futures requêtes. Il doit être conservé pour : - audit de sortie ; - replay ; - diagnostics ; - validation des matérialiseurs ; - transition vers des projections canoniques spécialisées. Le nom `mat_outputs` est retenu avant gel car il correspond au DTO `MaterializedOutputInsert` et évite de qualifier abusivement toute projection de « event ». La provenance source reste la composition : - matérialiseur : `processor_name`, `processor_version`, `input_key`, `input_hash`, `output_key` ; - décodeur source : `source_decoder_name`, `source_decoder_version`, `source_decode_input_key`, `source_event_key` ; - transaction : `signature`, `slot` ; - famille canonique : `materialized_family`. Pas de `block_time` dupliqué : il est joignable par signature. **État :** `à corriger` pour le nom/contrat de requête, puis `stable/frozen` comme journal générique. Les futures projections métier ne remplacent pas ce journal ; elles lui sont parallèles. ## 8. Matrice de gel structurel | Table `0.5.2` | État avant travaux | Transformation | État final attendu | |---------------------------------------|--------------------|-----------------------------------------------------------|------------------------------------| | `kb_sol_raw_transactions` | à corriger | `block_time` + namespace | `k_sol_raw_transactions` frozen | | `kb_sol_obs_transaction_observations` | stable | namespace | frozen | | `kb_sol_core_transactions` | à corriger | `block_time` + namespace | frozen | | `kb_sol_core_account_keys` | stable | namespace | frozen | | `kb_sol_core_instructions` | à corriger | namespace + `stack_height`/lifecycle/replay cohérents | table top-level frozen | | `kb_sol_core_inner_instructions` | à corriger | namespace + `stack_height` + lifecycle/replay équivalents | table CPI frozen | | `kb_sol_core_logs` | stable | namespace | frozen | | `kb_sol_core_balance_changes` | stable | namespace + index physique owner | frozen | | `kb_sol_ops_processing_ledger` | stable | namespace + index physique replay | frozen | | `kb_sol_decode_events` | stable | namespace | frozen | | `kb_sol_decode_coverage_declarations` | stable | namespace | frozen | | `kb_sol_decode_coverage_observations` | stable | namespace | frozen | | `kb_sol_mat_events` | à corriger | renommage conceptuel `mat_outputs` | `k_sol_mat_outputs` frozen journal | Aucune table n'est déclarée frozen dans le code ou la documentation finale avant que les migrations, DTO, queries, tests PostgreSQL et snapshot de contrat ne concordent. ## 9. Politique d'évolution après gel Le gel logique comprend : - nom de table ; - nom et sémantique des colonnes ; - type SQL logique ; - nullabilité ; - PK ; - FK ; - contraintes uniques ; - checks métier ; - unité et source de vérité. Ne font pas partie du gel logique : - index non uniques ; - statistiques PostgreSQL ; - paramètres de vacuum/analyze ; - taille du pool ; - plans de requêtes ; - options physiques de backend ne changeant pas le contrat de données. Après `0.5.3`, un nouveau besoin fonctionnel doit créer une nouvelle table ou projection reliée au contrat gelé. Toute exception au gel doit être considérée comme une migration de contrat majeure explicitement documentée, et non comme une correction opportuniste. ## 10. Provenance ### 10.1 Chaîne canonique La résolution de provenance cible est : ```text observation d'acquisition -> raw transaction canonique -> transaction/instruction Core -> observation décodée -> sortie générique de matérialisation -> projection métier additive éventuelle ``` ### 10.2 Répartition de la provenance | Information | Propriétaire canonique | |-----------------------------------------------------------|--------------------------------------------------------| | provider/endpoint/protocole/méthode d'acquisition | `k_sol_obs_transaction_observations` | | signature/slot/block time/document/hash/version canonique | `k_sol_raw_transactions` | | relation normalisée de transaction | `k_sol_core_transactions` | | program/path/payload/context | `k_sol_core_instructions` + tables Core liées | | decoder/version/input hash/event/proof | `k_sol_decode_events` | | couverture déclarée/observée | tables coverage | | matérialiseur/version/input/output | `k_sol_mat_outputs` | | source decoder d'une matérialisation | `k_sol_mat_outputs` via identité composite du décodeur | | état courant d'un traitement | `k_sol_ops_processing_ledger` | Aucun provider n'est ajouté aux tables decode/mat : il reste résoluble par la transaction et les observations d'acquisition. ## 11. Idempotence, replacement et replay ### 11.1 Identités actuelles à préserver | Domaine | Identité | |----------------------|-----------------------------------------------------------------------| | raw | signature unique | | acquisition | `observation_key` unique | | Core transaction | signature unique | | Core account | signature + account index | | instruction unifiée | signature + instruction path | | log | signature + log index | | balance change | signature + balance change index | | ledger | stage + processor + processor version + input key | | decode event | processor + version + input key + event key | | coverage declaration | processor + version + program + surface + kind + code + discriminator | | coverage observation | processor + version + input key | | materialized output | processor + version + input key + output key | ### 11.2 Garanties actuelles Les persistance Core/decode/materialization utilisent des transactions PostgreSQL et des remplacements déterministes. Le ledger stocke `input_hash` et `processor_version`, ce qui permet de déterminer si un résultat est courant pour une identité précise. ### 11.3 Lacunes de replay identifiées Deux invalidations descendantes doivent être explicitement corrigées : 1. **replay Core forcé** : le remplacement du graphe Core peut rendre des observations decode/materialization existantes obsolètes ; 2. **replay decode forcé** : le remplacement des observations d'un décodeur peut laisser temporairement des sorties de matérialisation calculées sur l'ancienne observation. Politique cible : - le remplacement d'un étage invalide dans **la même transaction** les projections descendantes qui dépendent directement de l'identité remplacée ; - le ledger descendant n'est considéré courant que si son `input_hash` correspond au nouvel input ; - une instruction retirée par un replay Core ne doit pas conserver une projection materialized présentée comme courante ; - le lifecycle de l'instruction doit pouvoir revenir de `materialized` vers l'état cohérent avec le replay lorsque les sorties sont invalidées ; - les sorties d'anciennes versions de processors ne sont pas confondues avec les sorties de la version active ; - aucune cascade n'est basée sur un parsing fragile de noms de Program IDs/protocoles. Les détails d'historisation complète des tentatives ne sont pas ajoutés au ledger courant : une future table d'audit append-only restera possible. ## 12. Audit des index par rapport aux requêtes réelles ### 12.1 Index déjà correctement justifiés - raw signature unique ; - raw slot ; - raw processing state ; - hash canonique partiel ; - observation signature/slot/provider+method/received/status ; - account key ; - instruction program ; - log program/path ; - balance mint/account ; - decode signature/path, program/surface/event, famille/commit ; - coverage program/processor ; - ledger input hash. ### 12.2 Index manquants ou mal alignés identifiés Les index suivants sont des candidats **justifiés par des requêtes déjà présentes**, à confirmer avec `EXPLAIN (ANALYZE, BUFFERS)` sur une base représentative : - `k_sol_core_balance_changes(owner)` partiel : les résumés replay interrogent déjà `owner` ; - `k_sol_ops_processing_ledger(stage, input_key, updated_at DESC, id DESC)` : la requête de candidat cherche le dernier ledger par input ; - `k_sol_mat_outputs(processor_name, materialized_family, slot DESC, id DESC)` : la liste matérialisée filtre et trie déjà ainsi ; - `k_sol_raw_transactions(processing_state, slot, signature)` : sélection Core extraction ; - sur `k_sol_core_instructions`, index de replay autour de `(processing_state, slot, signature, instruction_path)` et de `(program_id, slot, signature, instruction_path)` ; - sur `k_sol_core_inner_instructions`, index équivalents pour lifecycle/replay CPI ; - index `(signature, parent_instruction_path)` sur `k_sol_core_inner_instructions` pour reconstruire efficacement un sous-arbre CPI. ### 12.3 Index non retenus en `0.5.3` - pas d'index JSONB générique sur tous les payloads ; - pas d'index protocole/DEX spéculatif ; - pas de trigram sur toutes les signatures/adresses par défaut ; - pas d'index sur des champs de futures projections non encore produites. Les recherches `contains` restent des surfaces de diagnostic bornées ; si elles deviennent volumineuses, un index spécialisé sera ajouté comme optimisation physique sans casser le gel logique. ## 13. Pagination et limites ### 13.1 Constat - `PageRequest` expose actuellement `limit + offset` avec maximum 1 000 ; - la liste de matérialisations est limitée à 500 ; - les filtres `PostgresReplay*` autorisent jusqu'à **100 000** lignes ; - les grands offsets deviennent coûteux et sont instables face à des insertions concurrentes. ### 13.2 Cible - aucune surface UI/diagnostic publique ne retourne 100 000 lignes en une requête ; - les lectures interactives utilisent un maximum commun de 500 lignes ; - les campagnes de traitement utilisent des batches explicitement bornés et répétés, distincts d'une API UI ; - les listes paginées utilisent une continuation stable fondée sur une clé d'ordre canonique, par exemple `(slot, signature, instruction_path)` pour les instructions ; - un éventuel token de continuation reste opaque pour le consommateur, afin de ne pas exposer un identifiant PostgreSQL comme contrat backend-agnostique ; - l'ordre comprend toujours une clé de départage unique. `PageRequest.offset` n'est pas retenu comme contrat frozen de long terme. ## 14. `u64` et PostgreSQL `BIGINT` ### 14.1 Slots Les DTO Solana restent en `u64`. PostgreSQL conserve `BIGINT` pour les slots avec : - contrainte SQL `slot >= 0` ; - conversion checked `i64::try_from(u64)` à l'entrée ; - conversion checked `u64::try_from(i64)` à la sortie ; - erreur structurée si la valeur excède `i64::MAX` ; - tests aux bornes `0`, `i64::MAX`, `i64::MAX + 1`. Ce choix est accepté pour le slot : la limite `i64::MAX` est physiquement hors d'atteinte de la durée de vie réaliste de Solana, tout en permettant un mapping PostgreSQL simple et indexable. ### 14.2 Valeurs monétaires futures Cette décision **ne doit pas être copiée automatiquement** aux montants SPL ou aux métriques financières. Un montant canonique `u64` susceptible d'utiliser toute sa plage doit utiliser un type SQL capable de conserver l'intégralité de la valeur, par exemple `NUMERIC(20,0)`, ou une représentation exacte explicitement documentée. Aucun prix, volume ou montant financier durable ne doit être stocké en `FLOAT`/`DOUBLE PRECISION` par défaut. ## 15. Stratégie `kb_sol_* -> k_sol_*` ### 15.1 Principe La migration ne fait pas un simple remplacement textuel. Elle doit couvrir : - tables ; - contraintes PK/FK/check ; - index uniques et non uniques ; - séquences `BIGSERIAL` ; - constantes Rust ; - requêtes ; - diagnostics ; - fixtures ; - maintenance SQL ; - tests ; - documentation active ; - chaînes visibles du desktop. ### 15.2 Base vide Une base vide est la **cible normale de `0.5.3`**. L'auto-initialisation PostgreSQL exécute l'orchestrateur des ressources SQL unitaires dans un ordre déterministe : tables, contraintes, index, puis vérification du snapshot logique. Le résultat final ne doit contenir aucun objet actif `kb_sol_*`. ### 15.3 Bases `0.5.2` Devnet/Mainnet/test La décision de session est de **supprimer et recréer** les bases actuelles. `0.5.3` n'investit donc pas dans une migration in-place des données historiques. Le comportement attendu est : 1. si la base est vide, initialiser le nouveau schéma ; 2. si le nouveau schéma `k_sol_*` est déjà conforme, vérifier puis continuer ; 3. si des objets historiques `kb_sol_*` sont détectés, refuser l'auto-initialisation avec un diagnostic non destructif demandant une reconstruction explicite ; 4. ne jamais mélanger silencieusement ancien et nouveau namespace. Les opérations `DROP`/`TRUNCATE` restent disponibles comme maintenance backend-interne explicite ; elles ne sont pas déclenchées automatiquement par `Store::open()` sur une base non vide. ### 15.4 Conséquence sur les tests Le test de migration `0.5.2 -> 0.5.3` n'est plus requis. Il est remplacé par : - init d'une base vide ; - réouverture idempotente d'une base déjà initialisée ; - refus propre d'un schéma historique `kb_sol_*` ; - reconstruction explicite via les opérations de maintenance retenues ; - snapshot exact du schéma final. Cette stratégie est acceptable parce que les bases actuelles ne constituent pas encore des contrats de production à préserver. ## 16. Source de vérité des migrations PostgreSQL ### 16.1 Problème actuel Le DDL existe aujourd'hui simultanément : - dans `ks-store/migrations/*.sql` ; - dans `ks-store/src/postgres/migrations.rs` sous forme de chaînes Rust. Les fichiers sont en outre PostgreSQL-spécifiques alors que leur emplacement ne l'indique pas. ### 16.2 Arborescence cible Les ressources deviennent explicitement backend-private : ```text ks-store/migrations/postgres/ schema/ tables/ constraints/ indexes/ maintenance/ truncate/ drop/ ``` Un futur backend aura son propre arbre (`migrations/sqlite/`, `migrations/mysql/`, etc.) sans modifier les consommateurs. ### 16.3 Une instruction SQL par fichier Chaque ressource contient une seule instruction SQL. Le code Rust ne reconstruit pas le SQL et ne concatène pas du DDL. Exemple de principe : ```rust fn create_pg_table_if_not_exists_k_sol_raw_transactions_sql() -> &'static str { return include_str!("../../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_raw_transactions.sql"); } ``` La fonction d'exécution correspondante reste privée au backend et appelle la ressource. Le nom exact des fonctions sera ajusté aux règles de taille/naming du workspace pendant l'implémentation, mais le principe **1 ressource -> 1 fonction -> 1 opération** est retenu. ### 16.4 Orchestration Un initialiseur PostgreSQL privé : - détient l'ordre d'exécution ; - ouvre la transaction d'initialisation ; - appelle les fonctions de tables/keys/index ; - produit un rapport d'initialisation sans secrets ; - vérifie le contrat final ; - ne laisse pas de schéma partiellement initialisé après un échec transactionnel. Les opérations `DROP` et `TRUNCATE` sont séparées de l'auto-init et ne sont jamais appelées implicitement sur une base existante. ### 16.5 Historique futur Puisque `0.5.3` reconstruit le baseline avant gel, les anciens `0001` à `0004` n'ont plus à rester dans la chaîne active. Après gel, les futures évolutions seront additives et devront disposer d'un mécanisme backend-interne de version/schema state permettant de distinguer le baseline `0.5.3` des extensions ultérieures. Le choix précis de la table d'état interne est réalisé avant clôture sans l'exposer dans l'API publique. ## 17. Frontière backend-agnostique ### 17.1 État actuel `ks-store` respecte déjà plusieurs bonnes frontières : - les modules `contracts` et `postgres` sont privés ; - `ks-pipeline` orchestre déjà via `RawTransactionStore`, `CoreExtractionStore` et `DecodePipelineStore` ; - aucune autre crate ne dépend directement de `sqlx` ; - aucun `sqlx::PgPool` n'est utilisé hors de `ks-store`. Les fuites actuelles sont au crate-root de `ks-store` et dans les consommateurs de setup/diagnostic : - `PostgresStore` ; - `PostgresStoreOptions` ; - `PostgresBackendDiagnostics` ; - `PostgresTableDiagnostics` / `PostgresTableStatistics` ; - `PostgresReplay*` ; - `PostgresStore::pool()` ; - `PostgresStore::from_pool()` ; - fonctions/constants explicitement PostgreSQL exposées. Les principaux consommateurs sont `ks-pipeline-demo-scenarios` et `kb-app-demo-desktop`. `ks-pipeline` lui-même n'est pas à réécrire autour de PostgreSQL. ### 17.2 Façade cible Le crate-root expose : - `Store` : handle clonable backend-agnostique ; - `StoreOpenOptions` : configuration d'ouverture sans `Debug` révélant des secrets ; - `StoreBackendDescriptor` / `StoreBackendDiagnostics` génériques ; - `StoreTableDiagnostics` / `StoreTableStatistics` génériques ; - `ReplayTransactionFilter`, `ReplayProgramFilter`, `ReplayEntityFilter` et rows génériques ; - les repositories fonctionnels actuels réellement implémentés ; - les DTO/models de persistance réellement nécessaires. `Store` délègue en interne à un backend privé, par trait-object ou composition équivalente. Le choix exact doit éviter de dupliquer un `match backend` dans chaque consumer. Un enum de dispatch est acceptable **à l'intérieur de `ks-store`**, pas dans les crates consommatrices. ### 17.3 Interdictions finales Hors `ks-store`, les canaris doivent interdire : - `sqlx::` ; - `PgPool` ; - `PostgresStore` ; - `PostgresStoreOptions` ; - `PostgresReplay` ; - `postgres::` dans un chemin `ks_store` ; - accès à un pool ou à une query SQL concrète. ## 18. Configuration et possession de la connexion ### 18.1 Constat actuel `DatabaseConfig` dans `ks-config` contient simultanément : - `backend` ; - une section `postgres` ; - une section `sqlite`. La validation exige actuellement que les deux sections soient valides, même lorsque l'une n'est pas sélectionnée. Les consommateurs construisent ensuite directement `PostgresStoreOptions`. ### 18.2 Contrat cible Le document store doit représenter : ```text enabled backend: backend_options: ``` Principes : - `ks-config` charge, compose, applique les overrides et conserve le bloc sélectionné sans en interpréter la connexion ; - le backend code est un texte non vide, pas un enum public devant être modifié à chaque nouveau moteur ; - `ks-store::Store::open()` reconnaît le backend et valide ses options ; - un backend inconnu produit une erreur structurée ; - un backend connu mais incomplet produit une erreur structurée sans reproduire de secret ; - PostgreSQL reste le seul backend opérationnel exigé en `0.5.3` ; - la présence d'un exemple SQLite ne signifie pas qu'il est implémenté ; - le passage futur à MySQL/SQLite/RocksDB/Oracle ne change pas l'orchestration `ks-pipeline`. ### 18.3 Sécurité - `StoreOpenOptions` ne dérive pas `Debug` si son contenu peut révéler un secret ; - le backend interne PostgreSQL ne dérive pas un `Debug` contenant l'URL brute ; - le descriptor public expose uniquement un `masked_connection_descriptor` générique, pas `masked_dsn` comme nom de contrat global ; - les erreurs de connexion ne réinjectent jamais l'URL/DSN ou le password ; - `KS_SECRET_POSTGRES_*` reste la convention actuelle pour les URLs sensibles PostgreSQL ; - aucune fixture versionnée n'embarque de credential réel. ### 18.4 Résumé runtime public sans secrets `ks-store` doit fournir aux consommateurs, notamment `kb-app-demo-desktop`, un résumé générique permettant d'afficher l'état réel sans connaître PostgreSQL. Le contrat visé couvre au minimum : - store activé/désactivé ; - code backend actif ; - descriptor de connexion masqué ou label public ; - état de connexion/health ; - auto-initialisation activée ou non ; - version/identité du contrat de schéma ; - état d'initialisation ; - vérification par **modèle logique** (`Raw`, `Core`, `Processing/Decode`, `Materialization` ou équivalent final) ; - compteurs d'objets attendus, présents, créés et vérifiés, notamment tables et index lorsque le backend possède ces notions ; - éventuelles actions de migration/init exécutées, identifiées sans SQL sensible ni paramètres secrets. Le résumé public **n'expose pas les noms physiques des tables, index ou contraintes**. Ces détails restent backend-internes et peuvent être tracés sous le target canonique `ks-store` avec les champs structurés `backend="postgres"`, `domain="ks-store.pg"` et `action="migration"`. Un backend non relationnel peut produire le même résumé logique avec ses propres objets physiques. Les noms exacts (`StoreRuntimeSummary`, `StoreInitializationSummary`, `StoreModelVerificationSummary`, etc.) seront figés lors de `pre.002`. Le desktop consomme uniquement ces DTO génériques. Les panneaux portent la nomenclature `demo_store_*` dès `pre.002`; leur présentation tabulaire finale sera refondue en `pre.005`. `demo_config` ne doit plus lire directement la configuration PostgreSQL/SQLite. Le même résumé logique alimente les informations store du splash. ### 18.5 Logging PostgreSQL Le target Rust reste l’unique `crate::TRACING_TARGET` de la crate, soit `ks-store`, conformément aux règles du workspace. Le backend PostgreSQL est distingué par des champs structurés stables : `backend = "postgres"`, `domain = "ks-store.pg"` et `action = ...`. Règles : - toute exécution SQL PostgreSQL est traçable sous `target: crate::TRACING_TARGET` avec `backend = "postgres"` et `domain = "ks-store.pg"` ; - `info`/`debug` journalisent l'identité d'opération, le temps d'exécution, le nombre de lignes et l'issue ; - le texte SQL brut peut être journalisé à `trace` pour le diagnostic ; - les valeurs bindées ne sont pas journalisées par défaut ; - DSN, password, tokens et secrets ne sont jamais inclus ; - l'abstraction publique `Store` peut conserver un target `ks-store` générique pour les événements indépendants du backend. ## 19. API générique : matrice cible | DTO/model fonctionnel | Contrat public | Implémentation `0.5.3` | |---------------------------------------|--------------------------------------------|---------------------------------------------------| | raw transaction / observation | `RawTransactionStore` | adapter PostgreSQL privé | | Core transaction/graph | `CoreTransactionStore` | adapter PostgreSQL privé | | sélection/persistence Core extraction | `CoreExtractionStore` | adapter PostgreSQL privé | | lecture logique top-level/CPI | read-model/repository Core générique | `UNION ALL` ou flux fusionnés internes PostgreSQL | | decode/coverage/materialization | `DecodePipelineStore` | adapter PostgreSQL privé | | santé/init/diagnostics | `StoreHealthStore` + résumés génériques | adapter PostgreSQL privé | | candidats replay | repository/operations génériques de replay | requêtes PostgreSQL privées | | ouverture/connexion | `Store::open(StoreOpenOptions)` | factory interne PostgreSQL | Le consumer ne reçoit jamais un type de connexion concret. ### 19.1 Architecture de flux à préserver Le stockage doit rester compatible avec la séparation future : ```text Worker 1 / acquisition -> observations + raw canonique (niveau 1) Worker 2 / Core extraction -> Core exhaustif et source-indépendant (niveau 2) Dispatcher / decode + materialization -> observations décodées + `k_sol_mat_outputs` et autres journaux génériques (niveau 3) Projection / specialization -> modèles queryables par domaine et fait canonique (niveau 4) ``` Conséquences : - le niveau 1 privilégie fidélité, provenance d'acquisition et replay ; - le niveau 2 doit permettre de reconstruire/inspecter efficacement toute transaction sans dépendre du provider initial ; - les instructions top-level et CPI appartiennent toutes deux au Core, même si elles restent dans deux tables physiques ; - le niveau 3 peut traiter top-level ou CPI comme inputs autonomes via l'API logique de `ks-store` et conserve les sorties génériques/versionnées nécessaires à l'audit et au replay ; - `k_sol_mat_outputs` est une fondation N3 durable et n'est jamais supprimée au profit d'une projection spécialisée ; - le niveau 4 contient les projections spécialisées/queryables (metadata, futurs faits de trading, staking spécialisé, compliance, etc.) et reste plus évolutif car de nouveaux Program IDs peuvent révéler de nouveaux invariants ; - les projections métier ne doivent pas contaminer les tables raw/Core avec des colonnes spécifiques à un DEX, une metadata ou un domaine applicatif. ### 19.2 Politique de gel par niveau Après `0.5.3`, les niveaux N1, N2 et N3 forment le socle de persistance généraliste et sont **fortement gelés** : colonnes, types, nullabilités, clés et sémantiques ne changent plus dans le cycle normal. Une évolution n'est acceptable qu'en cas d'urgence, de corruption possible ou d'omission structurelle majeure découverte après clôture, et doit alors être traitée comme une migration exceptionnelle explicitement documentée. Le niveau N4 n'est pas soumis au même degré de rigidité. Une projection spécialisée peut nécessiter des colonnes, contraintes ou tables supplémentaires lorsque l'arrivée d'un nouveau décodeur/materializer révèle un invariant jusque-là inconnu. L'évolution N4 doit néanmoins rester contrôlée : préférer l'additif, versionner les projecteurs, préserver la provenance N3 et maintenir un chemin de replay permettant de reconstruire la projection. Le gel logique N1-N3 n'interdit pas l'ajout d'index physiques justifiés, conformément à la politique d'index de ce plan. ### 19.3 Contrats de replay inter-niveaux Les chemins suivants sont obligatoires et indépendants : ```text N1 raw/acquisition -> N2 Core N2 Core -> N3 decode/materialization générique N3 outputs génériques -> N4 projections spécialisées ``` Chaque transition doit disposer d'une identité d'input, d'un processor/projector et de sa version, d'un hash d'entrée lorsque pertinent, d'un état courant et d'un comportement explicite de replacement. Un replay forcé d'un niveau invalide seulement les descendants qui dépendent du résultat remplacé. Le replay N3 -> N4 doit permettre de reconstruire une projection spécialisée après évolution de son modèle **sans redécoder toute la blockchain** lorsque le journal N3 contient déjà l'information canonique nécessaire. Si N3 est lui-même insuffisant ou obsolète, le replay repart de N2 ; de même, N2 peut être reconstruit depuis N1. ### 19.4 Canari de readiness des futurs décodeurs Le gel N1-N3 n'est autorisé que si un nouveau mécanisme de décodage peut être ajouté sans remodeler normalement ces niveaux. Anchor/IDL est le premier canari concret connu, mais la règle est volontairement générique : **N1/N2 doivent conserver les faits Solana, N3 la provenance de décodage/materialization ; aucun niveau ne doit dépendre d'une technologie de decoder particulière.** L'audit `0.5.3` doit vérifier au minimum cinq axes : 1. **instruction readiness** — program id, bytes/data bruts de l'instruction, comptes dans leur ordre exact et résolution de leurs propriétés signer/writable ; 2. **CPI readiness** — mêmes données pour les inner instructions, ordre déterministe, `stack_height`, chemin et parent CPI immédiat ; 3. **event readiness** — logs ordonnés/fidèles, données CPI et `returnData` Solana lorsqu'elle est réellement disponible dans l'entrée canonique ; aucune `returnData` ne doit être synthétisée ; 4. **account readiness** — capacité à conserver/rejouer une observation ou un état de compte Solana avec ses bytes de données et sa provenance lorsqu'une source d'acquisition de comptes est utilisée, afin qu'un futur decoder de layouts/discriminators ne dépende pas d'un schéma connu à l'avance ; 5. **schema provenance N3** — possibilité d'identifier le decoder, sa version et le schéma/IDL/version/hash ayant produit une observation lorsque ce mécanisme est applicable, sans faire de l'IDL une dépendance du modèle Core. La baseline `0.5.2` possède 13 tables actives, mais elle ne contient pas nécessairement tous les contrats génériques nécessaires à ces cinq axes. En particulier, `0.5.3` doit auditer explicitement la conservation de `returnData` et des observations/états de comptes : si ces faits ne sont pas représentés de manière générique et rejouable, une nouvelle table N1/N2 canonique peut être ajoutée **avant gel**. Son nom et son contrat doivent décrire le fait Solana (`account observation`, `account state`, `transaction return data`, etc.), jamais Anchor ou une IDL. Ce canari ne doit pas dériver vers l'implémentation de `0.6.x` : aucun parser Anchor, chargement d'IDL, discriminator Anchor ou table `anchor_*` n'est ajouté en `0.5.3`. Il s'agit uniquement de prouver que les informations génériques nécessaires ne sont pas perdues. Conséquence sur le snapshot frozen : **13 est le nombre de tables existantes auditées au départ, pas une limite finale**. Le snapshot machine-readable de clôture porte le nombre réel de contrats N1/N2/N3 stabilisés après le canari future-decoder. ## 20. Audit des domaines de faits Solana Les surfaces déjà implémentées ou réservées dans `ks-lib` montrent que le store doit rester généraliste. Les domaines observés comprennent notamment : - transaction/Core ; - lifecycle ; - token accounts, mints et extensions SPL/Token-2022 ; - metadata Metaplex, Token-2022 et Solana Program Metadata ; - administration/autorités ; - fees/rewards ; - staking ; - compliance/audit ; - risk ; - governance ; - bridge ; - lending ; - oracle ; - NFT ; - vault ; - pool/liquidity ; - orderbook ; - trades ; - routing ; - perpetuals. Plusieurs de ces matérialiseurs sont encore réservés. Leur présence ne justifie donc pas la création anticipée de tables. Le registre `ks-program-ids` contient également de nombreux Program IDs futurs. Un Program ID enregistré n'est pas une preuve d'implémentation et ne devient jamais automatiquement un préfixe de table. ## 21. Modèles canoniques futurs et niveau de maturité ### 21.1 Modèles de forte stabilité conceptuelle Les concepts suivants sont suffisamment stables pour guider l'API future, mais pas encore pour figer toutes leurs colonnes SQL en `0.5.3` : - identité d'actif/mint ; - état de token account ; - metadata d'actif ; - relation d'autorité ; - état de programme/upgrade authority ; - stake/vote state ; - observation de risque/compliance ; - marché/venue ; - pool de liquidité ; - order book ; - swap/trade execution ; - réserve/liquidité ; - position ; - observation de prix ; - candle/OHLC ; - route multi-hop. ### 21.2 Metadata : N3 générique et deux projections N4 prévues Les travaux `0.4.6`/`0.4.7`/`0.4.8` ont déjà introduit des matérialiseurs metadata, notamment Metaplex Token Metadata, Token-2022 embedded metadata et Solana Program Metadata. Ils produisent aujourd'hui des sorties de famille `Metadata` persistables dans le journal générique de matérialisation. Deux responsabilités doivent rester distinctes : 1. **N3 — journal matérialisé versionné** : `k_sol_mat_outputs` conserve impérativement la sortie exacte du matérialiseur, sa provenance, son processor/version et son payload ; 2. **N4 — projections canoniques queryables** : tables spécialisées destinées aux lectures métier efficaces et reconstructibles depuis N3. La cible conceptuelle retient **deux familles metadata N4**, car les invariants ne sont pas les mêmes : - **asset/token metadata** : projection commune alimentable par Metaplex Token Metadata et Token-2022, destinée notamment aux lectures mint/asset, nom, symbole, URI, autorités, mutabilité, collection/standard et autres champs communs ou extensions normalisées ; - **program metadata** : projection distincte pour Solana Program Metadata (SPM), dont le sujet est un programme et non un asset/token. `0.5.3` ne crée pas encore ces tables. Leur premier contrat sera défini seulement lorsque la tranche de projection spécialisée disposera de données et d'usages suffisamment vérifiés ; contrairement à N1-N3, ces projections N4 **ne seront pas considérées comme immuables au même degré** et pourront évoluer de façon contrôlée si de nouveaux décodeurs révèlent des manques. Leur séparation conceptuelle est néanmoins décidée et `k_sol_mat_outputs` reste la source N3 de replay `N3 -> N4`. Des tables N4 additionnelles (`edition`, `collection state`, `group state`, etc.) restent possibles si leurs invariants justifient réellement une structure séparée. ### 21.3 Pourquoi aucune nouvelle table trading n'est créée maintenant Un schéma de trading gelé avant les DEX créerait des risques de sur-généralisation : - certains marchés sont pools, d'autres carnets ; - certains pools ont deux actifs, d'autres structures peuvent être plus générales ; - CLMM/DLMM introduisent ticks, bins et positions ; - un router peut produire une route composée de plusieurs exécutions ; - la sémantique base/quote n'est pas identique à l'ordre token A/token B ; - les frais et montants nécessitent des unités exactes ; - une candle doit préciser la source, le quote et l'intervalle. Créer ces tables en `0.5.3` sans données DEX réelles contredirait le principe de gel. Le résultat de `0.5.3` est donc une **frontière prête à recevoir ces tables de manière additive**, pas un schéma DEX imaginaire. ## 22. Priorité trading : contrats conceptuels à préserver Lorsque les matérialiseurs DEX arrivent, le modèle devra favoriser : ### 22.1 Marché/paire Un marché ou venue doit avoir une identité canonique indépendante du nom du protocole. L'ordre des actifs et le rôle base/quote ne doivent pas être supposés universels. ### 22.2 Pool AMM/liquidity Un pool porte son identité, ses actifs, réserves et paramètres génériques lorsque ces invariants sont réellement communs. Les courbes/ticks/bins restent dans des tables spécialisées par **concept**, pas par DEX. ### 22.3 Order book Un carnet n'est pas forcé dans le contrat d'un AMM. Si ses invariants sont différents, il possède une table canonique séparée avec niveaux/ordres dans des tables additives appropriées. ### 22.4 Swap/trade Un trade canonique conserve au minimum l'identité de la transaction/instruction, les actifs d'entrée/sortie, les quantités exactes, la venue/pool/market lorsque connue et la provenance programme/version. Les noms `amount_in`, `quoteAmount`, `coin_amount` ou équivalents protocole sont normalisés par le matérialiseur. ### 22.5 Candles/OHLC Une candle n'est créée qu'après définition stable de : - l'identité de marché/paire ; - l'asset quote ; - l'intervalle ; - la source de trades ; - les unités/précisions ; - le traitement des intervalles vides ; - la politique de correction après reorg/replay. ## 23. Matrice de normalisation programme -> fait canonique | Source / Program ID | Observation décodée | Matérialiseur | DTO canonique futur | Table métier cible | |----------------------------------|-----------------------------------------------|--------------------------------------|------------------------------------------------|--------------------------------------| | System / loaders / native | transfert, create, assign, upgrade, authority | lifecycle/admin | lifecycle/authority/program state | tables par fait, pas `system_*` | | SPL Token | mint/account/transfer/burn/authority | token/accounts, lifecycle, admin | token account, mint, token movement, authority | tables token canoniques | | Token-2022 | mêmes faits + extensions | token/accounts, metadata, risk, fees | mêmes modèles + extensions conceptuelles | tables token/metadata/risk additives | | Metaplex Token Metadata | metadata/edition/collection/pNFT | metadata | asset metadata/edition/collection state | tables metadata par invariants | | Stake/Vote | stake/vote actions/state | staking | stake/vote facts | tables staking/vote | | programmes admin/metadata natifs | program metadata/authority | metadata/admin | program metadata/authority | tables programme/autorité | | futur Raydium/Meteora/Orca/Pump | pool/swap/liquidity/position | pool/liquidity/trades | market/pool/swap/reserve/position | tables trading canoniques | | futur orderbook | market/order/fill/level | orderbook/trades | order book/order/fill | tables orderbook/trade | | futur Jupiter/router | route/quote/execution hops | routing/trades | route + hops/executions | tables routing/trade | | oracle | price/update | oracle | price observation | table oracle/price canonique | | governance | proposal/vote/authority | governance/admin | governance facts | tables governance | | compliance/risk | annotation/risk signal | compliance/risk | audit/risk observation | tables audit/risk | La colonne « source » reste une provenance. Elle ne détermine pas le nom de la table cible. ## 24. Interdiction des familles de tables par protocole Un canari documentaire et de code doit empêcher l'introduction automatique de noms actifs : ```text meteora_* raydium_* pump_* orca_* jupiter_* ``` La même règle s'applique à tout autre protocole : une table nommée d'après un programme n'est acceptable que si le **fait lui-même** est réellement spécifique et impossible à exprimer par un concept réutilisable. ## 25. Tests `0.5.3` obligatoires ### 25.1 Contrat de schéma - initialisation sur base vide ; - réouverture idempotente d'une base déjà initialisée ; - refus propre d'une base contenant encore des objets `kb_sol_*` ; - absence finale de tables, contraintes, index et séquences `kb_sol_*` après reconstruction ; - présence des **16 tables du baseline candidat au gel** retenu en `pre.003` ; - snapshot exact des colonnes/types/nullabilités/PK/FK/uniques/checks frozen ; - présence et contrat séparé des tables top-level/CPI ; - test de lecture logique combinée top-level+CPI sans perte, doublon ni ordre instable ; - test `block_time = NULL` lorsque absent ; - test de conservation de `block_time` lorsqu'il existe ; - test d'égalité raw/Core du `block_time` lorsque les deux lignes sont reliées ; - canari vérifiant qu'un fichier SQL PostgreSQL actif contient exactement une instruction du type prévu et qu'il est référencé par l'orchestrateur attendu. ### 25.2 Idempotence/replay - insertion raw répétée ; - observation acquisition répétée ; - Core extraction répétée ; - Core replay forcé avec input hash différent ; - decode replay répété ; - decode replay forcé invalidant les sorties materialized dépendantes ; - materialization répétée ; - replay explicite N1 -> N2 sans duplication ; - replay explicite N2 -> N3 avec remplacement/version de processor ; - replay explicite N3 -> N4 avec version de projector et reconstruction d'une projection spécialisée ; - invalidation descendante bornée : N1 remplace N2/N3/N4 dépendants, N2 remplace N3/N4 dépendants, N3 remplace N4 dépendant sans effacer les niveaux amont ; - suppression des sorties devenues absentes après remplacement ; - rollback transactionnel injecté après chaque étage critique ; - aucun état lifecycle présenté `materialized` ou `projected` si la sortie/projection courante correspondante a été invalidée. ### 25.3 Requêtes/index/pagination - signature exacte ; - range de slots ; - program id top-level et CPI ; - processor/version/state ; - account/address ; - mint ; - owner ; - pagination stable sans doublon/saut sur dataset figé ; - limites refusant 0 et toute valeur au-dessus du maximum de surface ; - borne `u64 -> BIGINT` ; - plans `EXPLAIN` enregistrés pour les index ajoutés lorsque le dataset de test est suffisamment représentatif. ### 25.4 Frontière backend - aucune dépendance `sqlx` hors `ks-store` ; - aucune référence `PostgresStore*` hors `ks-store` ; - aucune référence `PgPool` hors adapter privé ; - exports crate-root uniquement backend-agnostiques ; - unknown backend refusé proprement ; - backend PostgreSQL incomplet refusé proprement ; - `Store` délègue les repositories actuels à PostgreSQL sans changement de comportement. ### 25.5 Sécurité Utiliser un secret sentinelle dans les tests et vérifier qu'il n'apparaît jamais dans : - `Debug` public ; - `Display` d'erreur ; - diagnostics ; - backend descriptor ; - erreur de connexion ; - erreur de validation d'options. ### 25.6 PostgreSQL réel Avec `KS_SECRET_POSTGRES_TEST_URL` local : - connexion/health ; - migrations ; - transaction rollback ; - replay ; - diagnostics ; - introspection du contrat frozen ; - reconstruction propre d'une base de test et refus contrôlé d'un schéma historique `kb_sol_*`. ## 26. Snapshot de contrat frozen Le snapshot `0.5.3` couvre les **16 tables du baseline candidat au gel retenu en `pre.003`**, y compris les trois contrats génériques ajoutés par le canari future-decoder (`account observation`, `account state`, `transaction return data`), ainsi que les tables transverses de processing/decode qui assurent leur idempotence et leur journalisation. Les futures projections N4 disposent de contrats/version propres, mais ne sont pas ajoutées à ce snapshot frozen fondamental. Une fixture machine-readable sera créée avant clôture, par exemple : ```text test-fixtures/store/v0.5.3/frozen_schema_contract.json ``` Elle décrit pour chaque table frozen : - colonnes dans un ordre canonique ; - type logique ; - nullabilité ; - PK ; - FK ; - unique ; - checks métier ; - sémantique/unité ; - source de vérité temporelle/provenance. Les index non uniques sont consignés séparément et ne provoquent pas une violation de gel lorsqu'une optimisation justifiée est ajoutée plus tard. ## 27. Documentation cible à la clôture La dernière prerelease doit réconcilier au minimum : - `docs/architecture/STORAGE_ARCHITECTURE.md` ; - `docs/architecture/PIPELINE_ARCHITECTURE.md` si la frontière visible change ; - `docs/architecture/CRATE_MAP.md` / `SURFACE_CRATE_MATRIX.md` si nécessaire ; - `docs/guides/POSTGRES_STORAGE.md` ; - `ks-store/README.md` ; - `ks-store/USAGE.md` ; - `ks-store/TODO.md` ; - `ks-store/CHANGELOG.md` ; - `ROADMAP.md` ; - rapport final `0.5.3` ; - prompt `0.5.4` ; - archive du présent plan et du prompt `032`. ## 28. Plan de prereleases borné Le découpage suivant est une **trajectoire fonctionnelle**. Il ne force pas la clôture au numéro `pre.007`. Si une tranche révèle un manque structurel, une ou plusieurs prereleases sont insérées et la prerelease de clôture est décalée. ### `0.5.3-pre.001` — audit/plan - inventaire complet ; - matrices de temporalité/provenance/idempotence/index ; - matrice de gel ; - décision de conservation de deux tables Core instructions top-level/CPI avec API logique commune ; - stratégie reconstruction namespace/migrations ; - stratégie façade/configuration backend ; - stratégie de résumé runtime desktop et logging `ks-store.pg` ; - modèle N1 raw -> N2 Core -> N3 materialization générique -> N4 projections spécialisées, avec gel fort N1-N3 et replay inter-niveaux ; - canari future-decoder/Anchor-readiness imposant la conservation générique des bytes d'instruction, comptes ordonnés, CPI, logs, return data et états/observations de comptes avant gel ; - décision metadata N4 : asset/token metadata commune Metaplex + Token-2022, program metadata séparée pour SPM ; - plan des faits canoniques ; - aucun changement SQL de production. ### `0.5.3-pre.002` — façade générique, configuration, diagnostics et sécurité - introduire `Store` et `StoreOpenOptions` backend-agnostiques ; - rendre `PostgresStore`/pool/options privés ; - renommer diagnostics et replay DTO publics sans préfixe backend ; - définir le résumé runtime/init sans secrets consommable par le desktop ; - supprimer les quatre traits/DTO historiques non implémentés ; - remodeler le bloc store config en backend + options sélectionnées/opaques ; - déplacer toute validation/interprétation PostgreSQL dans `ks-store` ; - introduire l’instrumentation PostgreSQL sous le `TRACING_TARGET` canonique avec `backend="postgres"`, `domain="ks-store.pg"` et les canaris backend/secret ; - migrer les helpers de connexion sans changer encore les tables ; - préparer une façade `Store` persistante dans l’état applicatif afin que les commandes desktop ne recréent pas de connexion backend. ### `0.5.3-pre.003` — ressources SQL PostgreSQL et nouveau baseline Core - remplacer l'arborescence active par `migrations/postgres/` ; - une instruction SQL par fichier ; - fonctions privées `include_str!` et orchestrateur d'init transactionnel ; - reconstruire directement les tables sous `k_sol_*` ; - ajouter `block_time` raw/Core ; - conserver séparément top-level/CPI et rendre leurs contrats de lifecycle/replay cohérents ; - transporter/auditer `stack_height` et reconstruire le parent CPI immédiat lorsque l’information canonique le permet ; - exécuter le canari future-decoder sur N1/N2 : fidélité des bytes d'instruction, comptes ordonnés/flags, CPI, logs, `returnData` et observations/états de comptes ; ajouter avant gel uniquement les contrats Solana génériques réellement manquants ; - normaliser les nouveaux contrats sur le terme `top-level` et ne pas introduire de troisième famille `outer` ; - renommer `mat_events -> mat_outputs` ; - remplacer les scripts de maintenance par opérations unitaires drop/truncate ; - supprimer le DDL Rust dupliqué. ### `0.5.3-pre.004` — repositories, replay, idempotence, index et pagination - adapter toutes les queries aux tables finales ; - fournir une lecture logique commune top-level/CPI sans fusion physique ; - corriger l'invalidation descendante et formaliser les replays N1 -> N2, N2 -> N3 et la frontière future N3 -> N4 ; - remplacer les gros offsets/limites par pagination stable ; - ajouter les index justifiés ; - couvrir les bornes BIGINT ; - vérifier atomicité et reprise après erreur ; - instrumenter les requêtes PostgreSQL sous `target: crate::TRACING_TARGET`, `backend="postgres"`, `domain="ks-store.pg"` et `action` explicite. ### `0.5.3-pre.005` — finalisation `demo_store`, consommateurs et validation PostgreSQL réelle - supprimer les derniers `Postgres*` de `ks-pipeline-demo-scenarios` ; - finaliser les surfaces `demo_store_*` de `kb-app-demo-desktop` autour de résultats tabulaires génériques indépendants du backend ; la nomenclature technique a déjà été migrée en `pre.002` ; - refaire `demo_config` afin qu'elle n'expose/interprète plus les champs PostgreSQL/SQLite et consomme uniquement le résumé store sanitisé ; - refaire les informations store du splash autour d'une vérification par modèle logique et de compteurs (tables/index attendus, présents, créés), sans afficher les noms physiques ; - confirmer que `ks-pipeline` reste inchangé dans son rôle d'orchestration ; - tests base vide + réouverture + refus ancien schéma ; - validations PostgreSQL réelles ; - lancer Tauri parce que les diagnostics/surfaces desktop auront changé. ### `0.5.3-pre.006` — gel de contrat et réconciliation technique - produire le snapshot machine-readable frozen avec le nombre réel de tables N1/N2/N3 après canari future-decoder, sans imposer artificiellement la baseline de 13 ; - canari exact du schéma logique ; - audit workspace anti-`kb_sol_` actif ; - audit anti-PostgreSQL hors `ks-store` ; - audit secrets/logging ; - confirmer qu'aucune table trading/DEX ni projection metadata N4 non auditée n'a été introduite ; - ajouter un canari de gel prouvant qu'un futur decoder Anchor/IDL peut exploiter N1/N2/N3 sans changement structurel normal : instructions/CPI lossless, logs, return data, account observations et provenance de schéma ; - figer explicitement les contrats N1/N2/N3 tout en documentant la politique d'évolution plus souple de N4 ; - produire un rapport de validation technique candidat. ### `0.5.3-pre.007` — premier candidat de clôture documentaire - réconcilier README/USAGE/TODO/changelogs/guides/architectures ; - supprimer les TODO terminés ; - mettre à jour le ROADMAP ; - finaliser le rapport de validation ; - archiver ce plan et le prompt `032` sous `olddocs/archivekbot3/` ; - préparer le prompt `0.5.4` scénarios/exécuteurs/fixtures/validations ; - validation standard finale candidate. Si un manque apparaît à `pre.006`, `pre.007` ou pendant les validations finales, **la clôture est décalée** vers `pre.008`, `pre.009`, etc. Le numéro n'est jamais considéré comme plus important que la complétude du contrat. Un `delta-fix-XXX` corrige la prerelease courante sans changer son numéro documentaire et repart à `fix-001` pour chaque prerelease. ## 29. Validation standard par tranche Après chaque delta Rust ou SQL significatif : ```bash cargo fmt --all cargo check --workspace cargo clippy --all-targets python3 scripts/audit_rust_workspace_rules.py cargo test --workspace ``` Quand PostgreSQL réel est requis : ```bash export KS_SECRET_POSTGRES_TEST_URL='...' cargo test -p ks-store ``` Les commandes exactes des tests opt-in existants seront reprises depuis le code de la tranche concernée plutôt que supposées dans ce plan. Le desktop n'est lancé qu'après une modification d'une surface qu'il consomme : ```bash cargo tauri dev -c kb-app-demo-desktop/tauri.conf.json ``` ## 30. Critères de sortie `0.5.3` La version ne peut être clôturée que si : - il n'existe plus d'objet actif `kb_sol_*` ; - les 16 tables du baseline candidat retenu en `pre.003` correspondent exactement au snapshot frozen documenté ; - `block_time` est conservé lorsqu'il existe et reste `NULL` sinon ; - les top-level et CPI restent physiquement séparées mais peuvent être sélectionnées séparément ou comme flux logique commun pour le decode ; - replay et invalidation descendante sont cohérents et transactionnels, avec contrats N1 -> N2, N2 -> N3 et frontière N3 -> N4 documentée/testable ; - les tables N1/N2/N3 sont marquées fortement gelées et la politique d'évolution N4 est explicitement séparée ; - le canari future-decoder confirme que l'ajout d'Anchor/IDL ou d'un autre decoder futur ne nécessite pas de modification structurelle normale de N1/N2/N3, avec couverture explicite des instructions/CPI, logs, `returnData`, observations/états de comptes et provenance de schéma ; - `k_sol_mat_outputs` reste la source N3 générique durable même en présence de futures projections N4 ; - les limites/paginations sont bornées et stables ; - les conversions unsigned/PostgreSQL sont checked ; - les index ajoutés correspondent à des requêtes réelles ; - aucune API publique de `ks-store` n'expose PostgreSQL ou SQLx ; - aucune crate externe n'interprète les paramètres PostgreSQL ; - PostgreSQL reste entièrement fonctionnel comme backend actif, avec ses SQL ressources sous `migrations/postgres/` et ses logs sous le target canonique `ks-store` avec domaine structuré `ks-store.pg` ; - aucun secret n'apparaît dans diagnostics/erreurs/résumés runtime/logs ; - les tables futures sont guidées par des faits canoniques, pas par des Program IDs ; - aucune table DEX spéculative n'a été gelée sans matérialiseur réel ; - les deux projections metadata N4 futures (asset/token Metaplex + Token-2022, program metadata SPM) sont documentées comme extensions additives et rejouables depuis N3 ; - le desktop ne dépend plus d'un diagnostic/config PostgreSQL concret et son splash ne divulgue pas les noms physiques des objets store ; - le plan/prompt sont archivés et `0.5.4` est préparé. ## 31. Points validés / ajustés avant `pre.002` Les retours de revue de `pre.001` fixent désormais les points suivants : 1. Cargo utilise `0.5.3-pre.1`, tandis que la numérotation documentaire/archive reste `0.5.3-pre.001` ; 2. baseline de **13 tables conservées**, avec top-level et CPI physiquement séparées ; le nombre final peut augmenter uniquement si le canari future-decoder démontre un manque Solana générique avant gel ; 3. les deux familles d'instructions doivent avoir des capacités lifecycle/replay équivalentes et une lecture logique commune ; 4. `block_time` reste porté par raw/Core transaction et résolu par relation pour les tables filles ; 5. conservation du journal générique sous le nom `k_sol_mat_outputs` ; aucune table metadata métier n'est ajoutée sans besoin queryable stable ; 6. aucune table trading/DEX n'est créée en `0.5.3` ; les futures tables sont génériques par concept/fait ; 7. les bases Devnet/Mainnet/test `0.5.2` seront supprimées : reconstruction propre, pas migration in-place ; 8. SQL PostgreSQL sous `ks-store/migrations/postgres/`, une instruction par fichier, fonctions privées `include_str!`, tables/keys/index/drop/truncate séparés et orchestrateur d'init ; 9. façade publique `Store` backend-agnostique et PostgreSQL entièrement privé ; 10. configuration backend opaque jusqu'à `ks-store`, sans dépendance `ks-store -> ks-config` ; 11. résumé runtime/init sans secrets exposé à `kb-app-demo-desktop` et renommage structurel immédiat des surfaces `demo_sql_*` vers `demo_store_*` ; 12. logging PostgreSQL sous le target canonique `ks-store` avec `backend="postgres"` et `domain="ks-store.pg"`, sans secrets ni valeurs bindées par défaut ; 13. pagination cursorisée/bornée et distinction gel logique/index physiques ; 14. architecture future préservée et précisée : N1 Worker 1/raw, N2 Worker 2/Core, N3 decode/materialization générique avec `k_sol_mat_outputs`, N4 projections spécialisées/queryables ; 15. les contrats N1/N2/N3 sont fortement gelés après `0.5.3` sauf urgence ou omission structurelle majeure ; les tables/modèles N4 restent plus souples et évolutifs ; 16. replays explicites et indépendants N1 -> N2, N2 -> N3 et N3 -> N4, avec idempotence, version de processor/projector et invalidation descendante ; 17. aucune troisième table `outer` : les instructions historiquement dites outer sont les top-level ; les nouveaux contrats utilisent `top-level` et la table CPI conserve `stack_height` ainsi que le parent CPI immédiat reconstructible ; 18. metadata N4 prévue en deux projections : asset/token metadata commune Metaplex + Token-2022 et program metadata distincte pour SPM, toutes deux reconstructibles depuis N3 ; 19. `demo_store_*` devient la nomenclature technique dès `pre.002`; sa présentation finale évolue vers des résultats tabulaires backend-agnostiques, `demo_config` cesse d'exposer PostgreSQL/SQLite, et le splash vérifie des modèles logiques avec compteurs sans noms de tables/index ; 20. la façade `Store` est destinée à être ouverte une fois et conservée dans `AppState`, les commandes desktop réutilisant cette capacité générique ; 21. `pre.007` est un premier candidat de clôture, jamais une obligation : la clôture est décalée si des manques apparaissent ; 22. Anchor reste hors périmètre fonctionnel de `0.5.3`, mais devient un canari explicite du gel : N1/N2/N3 doivent conserver sans perte les informations génériques nécessaires aux futurs decoders (instructions/CPI, comptes ordonnés et flags, logs, `returnData` réelle, observations/états de comptes, temporalité/provenance) et N3 doit pouvoir tracer le decoder + schéma/IDL/version/hash utilisés ; 23. les 13 tables sont la baseline existante, pas un quota final : si ce canari révèle une omission Solana générique, `0.5.3` peut ajouter avant gel un contrat N1/N2/N3 canonique non nommé d'après Anchor. Ces décisions remplacent les propositions initiales de fusion des instructions et de conservation immuable des migrations `0001` à `0004`. `pre.002` peut démarrer sur cette base ; aucune migration SQL massive n'est encore incluse dans `pre.001`. ## 32. État réalisé de `0.5.3-pre.002` La tranche `0.5.3-pre.002` réalise la première rupture effective de dépendance au backend concret, sans modifier le schéma SQL historique qui reste volontairement réservé à `pre.003`. ### 32.1 Façade et possession de la connexion - `ks-store` expose désormais `Store` et `StoreOpenOptions` comme frontière publique d’ouverture et d’utilisation du stockage ; - `PostgresStore`, `PostgresStoreOptions`, le pool SQLx et les helpers PostgreSQL restent internes à `ks-store` ; - `Store` implémente les contrats fonctionnels déjà consommés par les pipelines et délègue au backend actif sans faire remonter le dispatch dans les crates consommatrices ; - le desktop conserve un `Store` ouvert une seule fois dans `AppState` via `tokio::sync::OnceCell`, puis les commandes réutilisent cette capacité ; - `ks-pipeline-demo-scenarios` utilise également la façade générique et ne construit plus le backend PostgreSQL. ### 32.2 Configuration backend opaque Le contrat runtime de `ks-config` conserve désormais uniquement : ```text enabled backend backend_options ``` `backend_options` reste un objet JSON opaque pour `ks-config`. La sélection, la validation des paramètres PostgreSQL, la création de la connexion et l’auto-initialisation sont interprétées par `ks-store`. `ks-store` ne dépend pas de `ks-config`. PostgreSQL est le seul backend opérationnel de cette tranche. Un backend inconnu ou incomplet est refusé par une erreur structurée qui ne recopie pas ses options sensibles. ### 32.3 Diagnostics et résumé runtime génériques La surface publique fournit des DTO backend-agnostiques pour : - configuration sanitisée ; - backend descriptor masqué ; - health ; - état de migration ; - ressources logiques et statistiques ; - vérification par modèle logique ; - résumé d’initialisation ; - résumé runtime complet. Le résumé d’initialisation regroupe actuellement les ressources du schéma historique sous les modèles logiques `raw`, `core`, `processing`, `decode` et `materialization`. Il expose des compteurs de ressources attendues/disponibles/créées sans exposer les noms physiques. Une liste générique de catégories d’objets backend expose en plus les compteurs physiques sans noms : PostgreSQL fournit déjà la catégorie `table`; `pre.003` ajoutera la catégorie `index` lors de la reconstruction/introspection du nouveau baseline. Un futur backend pourra fournir d’autres catégories sans modifier le contrat du desktop. La constante `STORE_SCHEMA_CONTRACT_VERSION` identifie explicitement cette étape comme un schéma legacy transitoire ; elle ne constitue pas le snapshot frozen final. ### 32.4 Replay et API publique Les DTO publics de diagnostic et de replay ne portent plus le préfixe `Postgres`. Le vocabulaire nouveau utilise `top-level` au lieu de `outer`. Les quatre anciens traits publics sans implémentation PostgreSQL réelle et leurs DTO historiques sont supprimés au lieu d’être figés. La structure SQL et les requêtes restent encore basées sur les tables `kb_sol_*` de `0.5.2` : leur reconstruction sous `k_sol_*`, l’alignement top-level/CPI et le nouveau baseline appartiennent toujours à `pre.003`. ### 32.5 Desktop Les commandes et payloads nécessaires au desktop ne construisent plus un backend concret. `demo_config` reçoit un résumé store sanitisé ; les diagnostics SQL utilisent désormais des ressources logiques génériques et un descriptor de connexion masqué. Le splash utilise le résumé d’initialisation par modèle et des compteurs, sans afficher de noms physiques de tables/index. Les noms historiques de fenêtres/commandes `demo_sql_*` sont supprimés dès `pre.002` et remplacés par `demo_store_*`. `pre.005` conserve uniquement la refonte fonctionnelle finale de l’expérience tabulaire, de `demo_config` et du splash autour du nouveau baseline. ### 32.6 Logging et sécurité Le backend PostgreSQL utilise l’unique target canonique de la crate : ```text target: crate::TRACING_TARGET // "ks-store" backend = "postgres" domain = "ks-store.pg" action = "connection | migration | query | health | replay | ..." ``` Un canari workspace interdit désormais les types/symboles PostgreSQL/SQLx dans les crates consommatrices et vérifie que l’implémentation PostgreSQL utilise `target: crate::TRACING_TARGET` avec les champs structurés `backend="postgres"` et `domain="ks-store.pg"`. Des tests sentinelles vérifient également qu’un backend inconnu, un descriptor masqué et les résumés publics ne recopient pas des options contenant un secret. Le nettoyage fin de l’instrumentation des requêtes (durées, nombres de lignes et suppression systématique de toute valeur bindée des traces normales) reste couplé à la réécriture des repositories de `pre.004`, conformément au plan. ### 32.7 Limites volontaires de la tranche `pre.002` ne : - renomme aucune table `kb_sol_*` ; - ne crée aucun contrat N1/N2/N3 supplémentaire ; - ne restructure pas encore `migrations/postgres/` ; - ne change pas `block_time`, `stack_height`, la hiérarchie CPI ou `k_sol_mat_outputs` ; - ne crée aucune projection N4 metadata/trading ; - n’implémente pas Anchor. Ces travaux restent affectés aux tranches `pre.003+` prévues par le présent plan. ## 33. État réalisé de `0.5.3-pre.003` `pre.003` réalise la reconstruction physique PostgreSQL et ferme les principaux manques révélés par le canari future-decoder. Le résultat est un **baseline candidat au gel**, pas encore le snapshot frozen final de `pre.006`. ### 33.1 Baseline candidat : 16 tables Les 13 tables historiques sont conservées conceptuellement sous le namespace `k_sol_*`, sans fusion des instructions top-level/CPI. Trois contrats Solana génériques sont ajoutés avant gel : 1. `k_sol_obs_account_observations` — observation N1 d'un état de compte avec bytes exacts, hash et provenance d'acquisition ; 2. `k_sol_core_account_states` — état N2 canonique dérivé d'une observation de compte ; 3. `k_sol_core_return_data` — conservation N2 de la `returnData` transactionnelle déjà présente dans le document canonique N1. Le baseline candidat comporte donc : ```text N1 k_sol_raw_transactions k_sol_obs_transaction_observations k_sol_obs_account_observations N2 k_sol_core_transactions k_sol_core_account_keys k_sol_core_instructions k_sol_core_inner_instructions k_sol_core_logs k_sol_core_balance_changes k_sol_core_return_data k_sol_core_account_states N3 / transverse k_sol_ops_processing_ledger k_sol_decode_events k_sol_decode_coverage_declarations k_sol_decode_coverage_observations k_sol_mat_outputs ``` Aucune structure n'est nommée d'après Anchor, une IDL, un DEX ou un protocole. ### 33.2 Temporalité et Core instructionnel `block_time` devient une colonne nullable de premier rang sur `k_sol_raw_transactions` et `k_sol_core_transactions`. La Core extraction vérifie la cohérence entre le document canonique N1 et l'ancre Core lorsqu'ils sont reliés. Les instructions top-level et CPI restent physiquement séparées. Les deux contrats portent désormais `stack_height` lorsque Solana la fournit. La Core extraction reconstruit les chemins CPI imbriqués à partir de la pile ; `parent_instruction_path` pointe alors vers le parent immédiat. Lorsque `stackHeight` est absent, la CPI reste rattachée de manière déterministe à la top-level racine sans inventer un niveau de pile. Les CPI reçoivent dans le schéma les mêmes champs de lifecycle/processor que les top-level. La sélection autonome et le replay opérationnel CPI restent à finaliser dans `pre.004`. ### 33.3 Replay Core v3 `MdCoreInstructionReplayInput` passe à la version 3 et peut transporter : - `block_time` ; - `stack_height` ; - `parent_instruction_path` ; - le contexte ordonné des instructions top-level ; - la `returnData` transactionnelle. Le champ de contexte nouveau est nommé `top_level_instructions_json`. Les usages historiques du terme `outer` dans des décodeurs existants ne sont pas renommés mécaniquement lorsqu'ils décrivent encore une sémantique propre à ces contrats. Le constructeur historique reste disponible pour les tests et sources synthétiques ; les inputs provenant du Core persistant sont enrichis par le store. ### 33.4 Provenance de schéma N3 `k_sol_decode_events` réserve des champs nullable de provenance de schéma : ```text schema_kind schema_id schema_version schema_hash ``` Ils restent vides pour les décodeurs compilés actuels. Un futur mécanisme basé sur un schéma externe/IDL pourra les renseigner sans introduire de colonne Anchor ou modifier N1/N2. ### 33.5 `k_sol_mat_outputs` Le renommage conceptuel est réalisé jusque dans l'API : ```text MaterializedOutputFilter MaterializedOutputQueryRow DecodePipelineStore::list_materialized_outputs ``` `k_sol_mat_outputs` reste le journal N3 générique versionné et la source de replay vers les futures projections N4. ### 33.6 Ressources PostgreSQL atomiques La chaîne active historique `0001` à `0004` est retirée. Les scripts de maintenance monolithiques sont également retirés. La nouvelle arborescence contient : ```text migrations/postgres/ schema/tables/ 16 schema/constraints/ 129 schema/indexes/ 59 maintenance/truncate/16 maintenance/drop/ 16 ``` Soit **236 ressources SQL**. Chaque ressource contient une seule instruction top-level et est embarquée une seule fois par `include_str!`. L'orchestrateur PostgreSQL global : - valide les ressources ; - refuse les objets historiques `kb_sol_*` ; - ouvre une transaction ; - prend un advisory lock ; - applique tables, contraintes et index dans l'ordre ; - commit uniquement lorsque le baseline complet est cohérent. Les initialiseurs partiels raw/Core/decode sont supprimés, y compris des tests. Une initialisation ne peut donc plus valider accidentellement un sous-schéma comme si le baseline complet était prêt. ### 33.7 Diagnostics du schéma Le diagnostic ne dépend plus de `_sqlx_migrations` pour déterminer si le baseline courant est disponible. Il compare les ressources connues réellement présentes. Le résumé backend-agnostique expose : - modèles logiques ; - compte de tables ; - compte d'index ; - état ready/partial/not-initialized ; sans publier les noms physiques. Le backend PostgreSQL attend actuellement **75 index** : 59 index explicites et les 16 index de clés primaires produits par PostgreSQL. ### 33.8 Conclusion du canari future-decoder Le canari a effectivement révélé deux pertes/absences structurelles de la baseline `0.5.2` : - `returnData` était conservée en N1 mais perdue en N2 ; - aucun chemin persistant générique d'observation/état de compte n'était réservé. Ces deux lacunes sont corrigées avant gel. Les autres metadata transactionnelles auditées (`fee`, rewards, compute units, cost units) restent conservées dans le document canonique N1. Elles ne sont pas nécessaires au décodage futur et ne justifient pas de gonfler l'ancre N2 avant gel ; une projection additive reste possible si un besoin queryable stable apparaît. ### 33.9 Travaux volontairement laissés à `pre.004+` `pre.003` ne clôt pas encore : - la lecture logique commune top-level+CPI et le replay CPI autonome ; - l'ingestion/replay opérationnel du nouveau flux account observation -> account state ; - l'invalidation descendante N1 -> N2 -> N3 -> N4 ; - la pagination cursorisée ; - la validation des index par plans réels sur dataset représentatif ; - le snapshot machine-readable frozen ; - la refonte fonctionnelle desktop finale ; - les projections N4 metadata/trading. Ces points restent dans les tranches suivantes et peuvent décaler le premier candidat de clôture au-delà de `pre.007`.