# Contrats communs de décodage et de matérialisation ## Objet La version `0.4.0` introduit l’infrastructure backend-agnostique utilisée par les futurs décodeurs Solana Core, SPL et protocolaires. Elle ne cherche pas encore à décoder maximalement un protocole précis. Le flux commun est : ```text core instruction contextualisée -> dispatch déterministe -> observation décodée versionnée -> persistance atomique decode + couverture + ledger -> matérialisation optionnelle explicitement autorisée -> persistance atomique mat + ledger ``` ## Input contextualisé `CoreInstructionReplayInput` reste l’unique contrat d’entrée commun. Son contrat passe à la version `2` dans `0.4.1-pre.014`. Il contient la signature, le slot, le statut et l’erreur on-chain, le chemin stable de l’instruction, le program ID, les comptes résolus dans leur ordre original, le payload brut déterministe et son hash, toutes les instructions outer ordonnées par index numérique, les inner instructions descendantes, les logs reliés prudemment, les changements de balances et la version du contrat core. `outer_instructions_json` est un tableau stable dont chaque entrée contient `instructionIndex`, `instructionPath`, `programId`, `payloadJson` et `payloadHash`. L’instruction cible est incluse. Cette projection provient des tables core existantes et ne nécessite aucune migration SQL. Le hash de replay est calculé à partir de la sérialisation JSON canonique de cet input. Le skip exige la même étape, le même processor, la même version, la même clé d’entrée, le même hash et un statut ledger réussi. Le passage au contrat `2` modifie légitimement les hashes existants, car les payloads outer participent désormais à la sérialisation. Le pipeline reste en version `1` : la version du contrat core et le nouveau contexte suffisent à invalider les anciens skips. Un force replay est requis après mise à niveau. ## Contrat de décodeur `InstructionDecoder` expose : - une identité stable `name/version` ; - les programmes et surfaces supportés ; - une matrice de couverture déclarée ; - une reconnaissance déterministe ; - un résultat terminal `decoded`, `ignored`, `unsupported` ou `failed` ; - zéro ou plusieurs observations typées uniquement pour `decoded` ; - une preuve, une confiance et des diagnostics structurés. Le dispatch est ordonné par compatibilité exacte, priorité déclarée, surface, code d’entrée et identité stable. Il ne dépend ni du nom de la crate ni de l’ordre d’enregistrement implicite. Avant la lecture du store, le pipeline calcule le périmètre effectif des programmes à partir de l’union des `program_id` déclarés par les décodeurs activés. Lorsque l’opérateur fournit un filtre explicite, ce filtre doit être un sous-ensemble exact de ces programmes ; une valeur incompatible est refusée avant toute sélection SQL. Cette règle empêche un décodeur natif de consommer par défaut des instructions SPL ou protocolaires et évite de rejouer indéfiniment le même lot `unmatched` sans rapport avec les processors choisis. Une instruction inconnue d’un programme reconnu doit être classée sans produire de faux événement. Un `unmatched` reste possible lorsque le program ID est supporté mais que la reconnaissance contextuelle refuse l’input ; il ne modifie pas globalement le lifecycle de l’instruction pour ne pas empêcher un autre décodeur futur de la traiter. ## Transactions on-chain échouées Une transaction échouée reste décodable lorsqu’un input structurel existe. Toute observation conserve : ```text transaction_failed transaction_error observation_committed ``` `observation_committed` doit être faux lorsque la transaction a été annulée. Ces observations peuvent décrire une instruction tentée, un événement loggé avant l’erreur, une classification d’échec, une consommation de compute ou une surface appelée. Elles ne peuvent pas produire automatiquement un trade réussi, une modification de liquidité réussie, un changement confirmé de catalogue ou une candle normale. `EventMaterializer` applique une politique explicite par famille. Les familles `trade`, `liquidity` et `lifecycle` sont refusées avant l’appel au matérialiseur lorsque la source est échouée ou non commitée. Une seconde barrière valide ensuite les familles de sortie et n’autorise, dans ce contexte, que les matérialisations d’audit ou de risque. ## Persistance Les tables actives sont : - `kb_sol_decode_events` ; - `kb_sol_decode_coverage_declarations` ; - `kb_sol_decode_coverage_observations` ; - `kb_sol_mat_events` ; - `kb_sol_ops_processing_ledger`. Aucun schéma PostgreSQL applicatif explicite n’est créé. Les payloads et preuves JSONB utilisent des colonnes suffixées par `_jsonb`. Le décodage persiste dans une transaction unique les observations, la couverture, l’état lifecycle de l’instruction et le ledger. La matérialisation persiste également ses sorties et son ledger dans une transaction unique. Un rollback à la dernière étape doit donc supprimer toutes les écritures précédentes de la même tentative. Toute exécution non skippée remplace atomiquement les sorties du même processor, de la même version et de la même clé d’entrée. Le force replay contourne uniquement le skip version/hash ; il ne supprime jamais les sorties d’un autre processor, d’une autre version ou d’une autre clé. Les versions antérieures restent disponibles pour l’audit. Lorsqu’un force replay contient une liste explicite de signatures, le pipeline retire le filtre de lifecycle pour cette sélection bornée. Le même contournement est autorisé sans signatures uniquement lorsque l’opérateur active explicitement le mode « toutes les signatures » ; la sélection reste alors limitée par les programmes compatibles, les instruction paths, les slots éventuels et la limite. Sans signatures ni cette autorisation explicite, la requête est refusée. Les déclarations de couverture sont synchronisées comme un snapshot du couple processor/version. Le résultat de persistance distingue les lignes réellement insérées, modifiées, supprimées du snapshot ou strictement inchangées ; une déclaration identique est comptée comme `skipped`, pas comme une nouvelle insertion. ## Couverture La couverture compare les entrées déclarées et observées, puis agrège : - reconnaissance ; - décodage ; - matérialisation ; - erreurs ; - inconnues ; - transactions réussies ; - transactions échouées. Une surface ne peut pas être considérée comme clôturée tant que toutes les instructions, événements et discriminants connus, y compris historiques et non liés au trading, ne sont pas classifiés. ## Traçabilité structurée Chaque campagne reçoit un `campaign_id` process-local stable, propagé de la démo jusqu’aux décisions de dispatch, de ledger, de décodage, de matérialisation et de persistance. Des spans imbriqués de campagne, input et processor transmettent aussi ce contexte aux événements émis par les décodeurs et les stores sans modifier leurs contrats. Elle doit permettre de reconstruire précisément les décisions prises sans requête SQL libre. Les événements `debug` utilisent un champ `action` stable et conservent au minimum, selon l’étape : - le nombre de signatures, un échantillon borné et la sélection normalisée ; - les filtres demandés puis les `program_id` et états effectivement appliqués ; - la signature, le slot, l’instruction path, le program ID et la clé d’input ; - le processor et sa version ; - le hash déterministe pertinent ; - la décision de dispatch, y compris lorsque `recognize` n’est pas appelé à cause d’un program ID incompatible ; - le résultat de reconnaissance et de décodage ; - le contrôle du ledger, le motif exact du skip ou son contournement par force replay ; - le début et la validation de la persistance PostgreSQL ; - le statut terminal et les compteurs par processor. Les targets stables sont : ```text kb_app_demo.demo_decode_replay kb_app_demo.frontend.demo_decode_replay kb_pipeline.decode_replay kb_store_pg.decode_pipeline kb_decoder_solana_core ``` Les signatures, slots, paths et program IDs sont des identifiants publics on-chain. Les listes complètes de signatures ne sont pas répétées à chaque couche : les événements de campagne conservent un compteur et un petit échantillon, tandis que les événements par input conservent la signature exacte. Les traces ne doivent pas enregistrer de DSN, secret, clé privée ni payload brut complet. Le payload d’instruction est représenté par son hash déterministe. ## Orchestration `kb_pipeline::execute_decode_replay` fournit : - sélection bornée par signatures, états, slots, program IDs et instruction paths ; - zéro, un ou plusieurs décodeurs compatibles selon la politique choisie ; - concurrence bornée ; - arrêt coopératif ; - skip version/hash ; - force replay ; - résumé par processor et statut ; - matérialisation optionnelle après succès de la persistance decode. La fenêtre Tauri `demo_decode_replay` ne contient aucune logique SQL libre. Elle construit une requête typée, appelle le pipeline et affiche les diagnostics read-only. ## Projections natives actives `kb_materializer_lifecycle::LifecycleMaterializer` est enregistré dans `demo_decode_replay` à partir de `0.4.1-pre.003`. Son applicabilité est filtrée par surface, entrée et paramètres avant toute consultation du ledger ou application de politique. `pre.017` étend ses familles acceptées à `Lifecycle`, `Admin` et `Audit`, mais uniquement pour des observations exactes qui produisent réellement une mutation lifecycle. `pre.018` ajoute l’initialisation/la fermeture des rapports Slashing sous `slashing_violation_report`, sans transformer le rapport en pénalité de stake. Il applique toujours `SuccessfulCommittedOnly`. Les sorties stables actives sont : - `address_lookup_table::0` pour les cinq mutations ALT ; - `program_loader:::0` pour les mutations loader stables ; - `feature_gate:revoke_pending_activation:0` pour la révocation Feature Gate ; - `durable_nonce_account::0` pour initialize, advance, authorize, upgrade et withdraw du System Program ; - `zk_proof_context::0` pour l’initialisation d’un contexte ZK ElGamal demandée par une vérification réussie et pour sa fermeture. `authorize_nonce_account` reste décodé en famille `Admin`, mais sa mutation d’autorité durable est promue en sortie `Lifecycle`. Les vérifications ZK ElGamal restent des événements `Audit`; elles ne sont acceptées par le matérialiseur que lorsque `contextStateRequested = true`. Une preuve sans contexte n’est jamais proposée au matérialiseur. Le retrait d’un nonce account conserve une sémantique conditionnelle. Le runtime détruit le compte lorsque la totalité du solde est retirée ; l’instruction seule ne suffit pas à reconstruire de manière certaine l’état final transactionnel du compte. La projection décrit donc l’opération commitée, conserve le montant demandé et indique explicitement que l’état final n’est pas capturé. Les deltas SOL ne sont pas dupliqués. La création d’un contexte ZK conserve le compte cible, son autorité et le type de preuve, sans matérialiser les octets de preuve. La fermeture conserve le compte, la destination des lamports et l’autorité, puis décrit le reset vers le System Program. L’ancien ZK Token Proof Program n’est jamais matérialisé : son runtime actuel est un stub sans effet et sa sémantique historique n’est pas attribuable à une transaction sans preuve de version. Le hash d’entrée matérialiseur reste dérivé de l’observation décodée complète. Le ledger conserve séparément les processors `solana_native_lifecycle`, `solana_native_admin` et `solana_native_compliance_audit`, leur version, la clé d’input et le hash. Une transaction échouée ou une observation non commitée est refusée avant toute sortie mutable. `pre.020` répartit les responsabilités : create/allocate System restent dans lifecycle ; assignations System, Config `store` et changements d’autorité Loader appartiennent à `kb_materializer_admin` ; writes/copies de bytecode Loader appartiennent à `kb_materializer_compliance_audit`. Les transferts SOL ne sont pas rematérialisés, car les balance changes core en sont la source canonique. ## Projections natives restantes La couverture maximale d’un décodeur n’implique pas une matérialisation systématique. Une projection n’est ajoutée que lorsqu’elle possède une identité stable, un état cible explicite, une politique d’idempotence et suffisamment de contexte pour ne pas reconstruire une mutation fictive. Les prochaines projections sont réparties par propriétaire : - `kb_materializer_lifecycle` : créations et allocations System, sans dupliquer les deltas SOL ; - `kb_materializer_admin` : assignations System, écritures Config opaques commitées et changements d’autorité Loader ; - `kb_materializer_compliance_audit` : écritures et copies de bytecode Loader, avec hash et préfixe borné sans payload complet ; - `kb_materializer_staking` : intentions/transitions Stake et Vote commitées. `pre.021` active des projections instructionnelles pour comptes Stake/Vote, autorités, lockup, vote state, retraits et rewards ; un snapshot final exige toujours l’état antérieur/suivant du compte, les crédits cumulés et les sysvars ; - un matérialiseur transactionnel Compute Budget : profil fusionnant toutes les instructions Compute Budget du message. Les surfaces suivantes restent volontairement en decode/audit : - précompiles de signature, qui décrivent une vérification runtime sans état métier durable ; - preuves ZK sans compte de contexte, qui n’ont pas de mutation persistante à projeter ; - ancien ZK Token Proof Program, dont le runtime Agave `v4.1.1` est un stub sans effet. Chaque ajout futur doit indiquer le matérialiseur propriétaire, la source d’état et la politique de transaction, au lieu d’étendre automatiquement `solana_native_lifecycle`.