Files
khadhroony-bot3/docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md
2026-08-12 14:31:48 +02:00

112 KiB
Raw Blame History

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 :

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 :

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<i64> 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<i64> 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 :

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 :

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 :

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 :

enabled
backend: <code>
backend_options: <objet opaque pour ks-config>

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 lunique 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 :

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 :

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 :

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 :

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 linstrumentation 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 linformation 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 :

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 :

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 :

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 douverture et dutilisation 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 :

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 lauto-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é dinitialisation ;
  • résumé runtime complet.

Le résumé dinitialisation 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 dobjets 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 dautres 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_*, lalignement 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é dinitialisation 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 lexpérience tabulaire, de demo_config et du splash autour du nouveau baseline.

32.6 Logging et sécurité

Le backend PostgreSQL utilise lunique target canonique de la crate :

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 limplé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 quun backend inconnu, un descriptor masqué et les résumés publics ne recopient pas des options contenant un secret.

Le nettoyage fin de linstrumentation 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 ;
  • nimplé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 :

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 :

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 :

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 :

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.

34. État réalisé de 0.5.3-pre.004

pre.004 ferme la tranche repositories/replay sans ajouter de table et sans déclarer encore le gel formel N1/N2/N3.

34.1 Replay top-level + CPI

Les instructions top-level et CPI restent dans leurs deux tables physiques. Les repositories construisent désormais une lecture logique commune avec CoreInstructionScope::{TopLevel, Inner}. Le replay contextuel N2 -> N3 fonctionne sur les deux scopes ; le lifecycle est mis à jour dans la table physique correspondant au chemin dinstruction.

Lordre de page durable est :

(slot, signature, scope_rank, instruction_path)

PageRequest abandonne offset et utilise un curseur borné. Les lectures interactives sont plafonnées à 500 lignes.

34.2 Batches de processing

Les campagnes Core extraction, decode et account-state utilisent des limites propres, plafonnées à 1 000 entrées par batch. Elles ne réutilisent pas la limite interactive de 500 et aucune surface publique ne conserve lancien plafond 100 000.

34.3 Invalidation descendante

Le remplacement dun graphe Core invalide, dans la même transaction, les descendants decode/coverage/materialization et les ledgers de la signature avant reconstruction N2.

Le remplacement dun résultat decode invalide les matérialisations appartenant à lidentité exacte (decoder_name, decoder_version, decode_input_key). Les clés de ledger sont comparées par préfixe littéral avec LEFT(...)=prefix, jamais avec LIKE.

Après persistence, le lifecycle instructionnel est recalculé depuis les descendants courants. Une sortie produite par une ancienne version du decoder ne peut donc pas faire apparaître la nouvelle version comme matérialisée.

34.4 Account observation -> account state

Le couple réservé en pre.003 devient opérationnel :

k_sol_obs_account_observations
        -> account_state_normalization ledger
        -> k_sol_core_account_states

La sélection transporte le nom/version du normalizer ; une nouvelle version peut rejouer les observations même si un Core state dune version précédente existe. Le hash dinput assure lidempotence de la version courante. Les valeurs lamports/rent_epoch conservent le domaine u64 complet via NUMERIC(20,0).

34.5 Index et ressources SQL

Aucune table ni contrainte logique nest ajoutée. Quatre index non uniques sont ajoutés pour les nouveaux chemins :

  • ordre de replay des CPI par slot/signature/path ;
  • lookup coverage par signature/path ;
  • descendants materialized outputs par decode input ;
  • descendants materialized outputs par signature/input.

Le corpus passe de 236 à 240 ressources SQL, avec 63 index explicites / 79 index attendus en comptant les 16 clés primaires. Les index restent ajustables avant gel sur la base des plans EXPLAIN.

34.6 Instrumentation PostgreSQL

Les opérations de repository réécrites émettent des événements TRACE sous le target canonique ks-store avec :

backend="postgres"
domain="ks-store.pg"
action
elapsed_ms
rows
outcome

Le statement logging automatique SQLx reste désactivé. Les traces normales ne publient ni SQL brut, ni valeurs bindées, ni secret de connexion.

34.7 Reste avant gel

La validation PostgreSQL réelle des nouveaux chemins, les plans EXPLAIN, la présentation desktop finale et le snapshot machine-readable restent dans les tranches suivantes. pre.004 stabilise les repositories candidats ; il ne déclare pas encore N1/N2/N3 frozen.