# Plan `0.3.5` — Interface passive acquisition events ## 1. Statut de la release Base canonique auditée : ```text v0.3.4 workspace.package.version = 0.3.4 ``` Version de travail ouverte par cette tranche : ```text workspace.package.version = 0.3.5-pre.1 label = 0.3.5-pre.001 ``` `0.3.5-pre.001` est un gate de lecture, inventaire, convergence sémantique, threat model, sizing et planification. Il ne modifie aucun contrat public Rust, aucun DTO Transport, aucun modèle Store, aucun runtime et aucune persistence. Le résultat du gate reste volontairement étroit. `pre.001-fix.001` a corrigé deux conclusions trop conservatrices : le lifecycle de slot possède une intersection de **sept** stages communs après normalisation sémantique explicite et `TransactionExecutionEvent` a été rouvert comme candidat actif. `pre.002` matérialise `SlotLifecycleEvent`; `pre.003` ferme ensuite le gate transaction execution et admet une seconde famille passive compacte sans déplacer les DTOs Transport ni les modèles RAW Store. ## 2. Mission La release doit étendre `ksp-interface-lib` seulement lorsqu'un consumer de composition peut recevoir un fait passif KSP-owned sans dépendre du protocole producteur et sans créer un second modèle RAW. Le contrat cible reste : ```text producer Transport -> DTO Transport riche et lossless -> conversion explicite à la composition -> fait Interface passif minimal, seulement si la sémantique commune est prouvée ``` Le type Interface ne remplace jamais le DTO Transport. Les informations source-specific qui ne font pas partie du fait commun restent disponibles sur le DTO Transport et ne sont pas copiées dans Interface. ## 3. Sources relues ### 3.1 Sources internes Le gate a relu les familles prescrites par le prompt : ```text RULES.md docs/000-README.md docs/rules/RULES_GENERAL.md docs/rules/RULES_KSP.md docs/rules/RULES_RUST.md docs/rules/RULES_DEPENDENCIES.md docs/rules/RULES_DOCUMENTATION.md docs/rules/FILE_CONTRACTS.md docs/rules/VERSION_WORKFLOW.md docs/rules/PROMPT_STRUCTURE.md docs/architecture/000-README.md docs/architecture/001-PROJECT_OBJECTIVES.md docs/architecture/002-LAYERS_AND_DEPENDENCIES.md docs/architecture/003-COMPONENT_CONTRACTS.md docs/architecture/004-COMPONENT_INVENTORY.md docs/architecture/005-DEPENDENCY_GRAPH.md docs/architecture/006-WIRE_AND_PROGRAM.md docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md docs/plans/020-V0_2_13_INTERFACE_PLAN.md docs/validation/016-V0_2_13_INTERFACE.md docs/plans/022-V0_3_1_STORE_RAW_PLAN.md docs/validation/018-V0_3_1_STORE_RAW.md docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md ``` Les sources Rust relues couvrent `ksp-interface-lib`, `ksp-store-api`, la façade/backend Store et les modules HTTP/WS/gRPC/Helius event-like de `ksp-onchain-transport-lib` demandés par le prompt. ### 3.2 Surface Interface/Store stable constatée Inventaire crate-root actuel de `ksp-interface-lib` : ```text Pubkey ProgramAccountMeta ProgramInstruction MAX_PROGRAM_INSTRUCTION_ACCOUNTS MAX_PROGRAM_INSTRUCTION_DATA_LEN ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED ``` Modules de production actuels : ```text error program_account_meta program_instruction ``` Le manifeste Interface ne possède qu'une dependency normale : ```text ksp-core-lib ``` Le consumer runtime/public actuel identifié est `ksp-program-api`, qui réutilise `ProgramAccountMeta` et `ProgramInstruction`. Aucun consumer actuel n'impose encore un event d'acquisition. La surface Store stable expose exactement dix capabilities RAW : ```text RawTransactionRead RawTransactionWrite RawTransactionObservationRead RawTransactionObservationWrite RawTransactionRetentionRead RawTransactionRetentionWrite RawAccountStateRead RawAccountStateWrite RawAccountObservationRead RawAccountObservationWrite ``` ### 3.3 Sources externes courantes auditées le 2026-08-31 L'audit externe a porté sur les documents normatifs/courants suivants : ```text Solana RPC WebSocket: - slotSubscribe - slotsUpdatesSubscribe - rootSubscribe / index WebSocket - signatureSubscribe - logsSubscribe - voteSubscribe - accountSubscribe - programSubscribe Solana RPC HTTP: - getSignatureStatuses - getTransaction - getBlock Yellowstone gRPC upstream et version KSP : - KSP stable déclare `yellowstone-grpc-proto = ^12.6` - geyser.proto / SlotStatus / SubscribeUpdateSlot - transaction_status - account / transaction / block / block_meta / entry update families - upstream courant comparé pour vérifier que l'intersection SlotStatus retenue n'est pas une hypothèse obsolète Helius: - LaserStream-powered standard WebSockets - Enhanced WebSocket transactionSubscribe ``` Les conclusions externes qui structurent le plan sont : - `slotSubscribe` signale le traitement d'un nouveau slot et transporte `slot`, `parent` et le root courant ; - `slotsUpdatesSubscribe` est instable et publie `firstShredReceived`, `completed`, `createdBank`, `frozen`, `dead`, `optimisticConfirmation` et `root`, avec metadata conditionnelle ; - Yellowstone expose `Processed`, `Confirmed`, `Finalized`, `FirstShredReceived`, `Completed`, `CreatedBank` et `Dead` ; - `signatureSubscribe` est un abonnement one-shot à une signature, éventuellement précédé de `receivedSignature`, et n'est pas un snapshot HTTP ; - `getSignatureStatuses` reste un snapshot queryable avec confirmations/status/confirmationStatus ; - `voteSubscribe` publie des votes gossip pré-consensus, sans garantie qu'ils entrent dans le ledger ; - Helius conserve les méthodes standard Solana et possède en plus des extensions Enhanced WebSocket, notamment `transactionSubscribe`, qui restent provider-specific. ## 4. Invariants d'ownership Les règles directement structurantes restent : ```text KSP-TRANSPORT-002..004 KSP-STORE-001..002 KSP-NOTIFY-001..006 KSP-PROC-007..008 KSP-REL-005..016 DEP-KSP-* DEP-LOG-* DEP-STORE-* DEP-WORKER-* DEP-JOB-* ``` Le graphe cible reste : ```text ksp-interface-lib └── ksp-core-lib ``` Et les frontières restent : ```text Interface -X-> Transport Interface -X-> Store API / Store Interface -X-> Program API / Program Lib Interface -X-> Config / Wallet Transport -X-> Store API Store API -X-> Transport composition supérieure = owner des conversions ``` `ksp-program-api` reste un consumer de la surface Program-facing existante de `ksp-interface-lib`. Il n'a aucune raison de re-exporter les futurs événements d'acquisition. ## 5. Critères d'admission d'une famille Interface Une famille n'est admise que si toutes les conditions suivantes sont vraies : 1. le type est passif et ne possède aucun lifecycle/runtime ; 2. le fait est provider-neutral ; 3. il ne duplique pas un modèle persistant/replayable de `ksp-store-api` ; 4. l'intersection sémantique entre producers est précise et documentable ; 5. au moins deux producers/converters peuvent produire exactement ce fait, ou plusieurs consumers ont un besoin identique déjà démontré ; 6. le type n'est pas une union d'options destinée à masquer des sémantiques différentes ; 7. les détails non communs peuvent rester sur les DTOs Transport sans forcer Interface à devenir lossless pour le protocole source ; 8. un usage de composition proche est identifiable. Une égalité de noms ne suffit pas. Une proximité de commitment ne suffit pas. Une possibilité théorique de conversion ne suffit pas. ## 6. Inventaire producer -> nature de fait | Producer / surface | Nature du fait | Champs structurants obligatoires | Optionalité utile | Ordre / lifecycle | Timestamp semantics | Error semantics | Metadata session/provider | Volume potentiel | Persistent/replayable ? | | -------------------------------------- | ----------------------------------- | ----------------------------------------------------- | --------------------------------- | ------------------------------------------------- | ----------------------------------------- | -------------------------------------- | -------------------------------------- | --------------------- | --------------------------------------- | | Solana WS `slotSubscribe` | progression de slot | slot, parent, root | aucune | event répété à chaque slot traité | aucun | aucune | subscription id hors DTO métier | minime | non | | Solana WS `rootSubscribe` | progression de root | root slot | aucune | event répété lors du changement de root | aucun | aucune | subscription id | minime | non | | Solana WS `slotsUpdatesSubscribe` | lifecycle interne slot | slot, type, timestamp | parent/stats/error selon variante | multi-event par slot ; méthode instable | Unix update timestamp en ms | texte seulement pour `dead` | subscription id | faible à modéré | non | | Solana WS `logsSubscribe` | logs transaction realtime | signature, err, logs + context slot | err nullable | flux continu selon filtre/commitment | aucun provider timestamp | opaque RPC transaction error | filtre + commitment + subscription id | logs non bornés wire | non par défaut | | Solana WS `signatureSubscribe` | réception/terminal signature | signature en request ; context slot + result en event | received précoce | one-shot terminal ; reçu optionnel avant terminal | aucun | transaction error au terminal | commitment/request/subscription | minime | non | | Solana HTTP `getSignatureStatuses` | snapshot de status | signatures request ; slot/status/confirmationStatus | nulls/confirmations | requête ponctuelle, pas une transition | aucun | transaction error dans snapshot | search history request policy | faible | queryable côté node, pas event KSP | | Solana WS `voteSubscribe` | vote gossip pré-consensus | votePubkey, slots, hash, signature | timestamp | flux gossip ; aucune garantie ledger | timestamp vote optionnel | aucune erreur transaction canonique | subscription id | faible | non | | Solana WS `accountSubscribe` | changement d'état account | context slot + account payload | selon encoding/dataSlice | flux d'état selon commitment | aucun | payload account, pas error event | commitment/encoding/subscription | potentiellement élevé | peut alimenter `RawAccountState` | | Solana WS `programSubscribe` | changement account d'un programme | context + pubkey + account payload | selon encoding/filters | flux d'état filtré | aucun | payload account | program/filter/commitment/subscription | potentiellement élevé | peut alimenter `RawAccountState` | | Solana WS `blockSubscribe` | bloc/transactions realtime | slot/context + block ou erreur | block nullable | flux de blocs selon config | blockTime dans payload si présent | RPC/block error | filter/config/subscription | très élevé | conteneur d'acquisition | | Yellowstone `Slot` | lifecycle/commitment slot | slot, status | parent/dead_error/created_at | plusieurs statuts possibles par slot | `created_at` = création serveur update | dead diagnostic optionnel | filter echo + server metadata | faible | non | | Yellowstone `TransactionStatus` | update execution/status transaction | slot, signature, is_vote, index | err/created_at | flux transaction status | server `created_at` | erreur transaction encodée | filter echo | faible | non par lui-même | | Yellowstone `Account` | état account + provenance Geyser | account bytes/info + slot + is_startup | txn signature/created_at | flux state/replay startup | server `created_at` | decode/transport error hors payload | filters + write/version metadata | potentiellement élevé | peut alimenter `RawAccountState` | | Yellowstone `Transaction` | transaction complète + meta | slot + transaction info/meta | created_at | flux transaction | server `created_at` | transaction meta error | filters + transaction index | très élevé | peut alimenter `RawTransaction` | | Yellowstone `Block` / `BlockMeta` | bloc / metadata de bloc | slot + block/meta fields | champs block spécifiques | flux block/meta | server `created_at` | payload/meta-specific | filters | très élevé | conteneur d'acquisition | | Yellowstone `Entry` | entry Geyser | slot/index/entry-specific fields | created_at | flux protocol-specific | server `created_at` | protocol-specific | filters | modéré | non retenu | | Helius standard WebSocket | mêmes faits standard Solana | mêmes champs standard | mêmes optionalités | même wire sémantique, runtime provider différent | mêmes semantics Solana | mêmes semantics Solana | Helius session/runtime | selon méthode | suit la famille standard | | Helius Enhanced `transactionSubscribe` | transaction provider-filtered | slot/signature ou transaction selon details | options/detail modes | extension provider continue | provider payload semantics | provider/RPC semantics | filtres Helius + options + session | faible à très élevé | acquisition possible, Transport-owned | | Store notification after commit | wake-up de donnée durable | future référence durable compacte | à définir avec publisher/consumer | après commit ; jamais source de vérité | non requis par le contrat normatif actuel | code safe éventuel, pas payload source | mécanisme de diffusion indépendant | minime | référence une persistence déjà commitée | ### 6.1 Consumers actuels et imminents | Consumer | Besoin actuel / prévu | Dépendance à Interface event | Décision `pre.001` | | ----------------------------------------- | --------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------- | | `ksp-program-api` | ProgramInstruction / ProgramAccountMeta | non | préserver la surface Program existante ; aucun re-export acquisition | | `0.3.6` premier backfill RAW | range historique -> Transport -> RAW ingestion -> Store | non | ne pas forcer SlotLifecycle dans le backfill | | futur worker RAW live | subscriptions/fetch live multi-transport -> RAW ingestion | oui, candidat concret | consumer proche justifiant une projection slot lifecycle provider-neutral | | futurs processors CORE/DECODE/SPECIALIZED | backlog Store + wake-up post-commit | non pour event réseau | consommer Store/backlog ; ne pas détourner les events réseau | | apps de contrôle/inspection futures | observabilité et commandes de composants | non démontré | aucun type Interface ajouté uniquement pour une UI future | ## 7. Matrice de convergence sémantique | Nature candidate | Sources comparées | Intersection exacte retenue | Différences non représentables sans perte | Projection commune lossless pour le fait ciblé ? | Consumer concret | Owner | Décision | | ----------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------ | ----------------------------- | ------------------------------ | ------------------- | | lifecycle slot | slotSubscribe + slotsUpdates + rootSubscribe + Yellowstone Slot | Processed, FirstShredReceived, Completed, CreatedBank, Dead, OptimisticallyConfirmed, Rooted | Frozen ; parent/timestamps/diagnostics ; noms wire Confirmed/Finalized côté Yellowstone | oui, pour l'occurrence `slot + stage` | futur worker RAW live | Interface | ADMIS | | transaction execution | logsSubscribe + Yellowstone TransactionStatus + Helius transactionSubscribe | `slot + TransactionSignature + Succeeded/Failed` | metadata/provider/detail modes ; commitment/delivery ; erreur source | oui pour le fait d'exécution observée | futur worker RAW live | Interface | ADMIS `pre.003` | | logs realtime | logsSubscribe + transaction/meta Yellowstone/Helius | logs associés à une transaction | volume/bornes, richesse de source, disponibilité selon détail | non admis dans cette release sans consumer/bornes | hydratation possible | Interface si futur gate | IDÉE DIFFÉRÉE | | signature commitment/snapshot | getSignatureStatuses + signatureSubscribe | identité de signature seulement | snapshot HTTP vs transition one-shot commitment | non comme event d'exécution | aucun contrat unique démontré | Transport | REPORTÉ | | vote | voteSubscribe + Yellowstone transaction `is_vote` | qualificatif « vote » seulement | gossip pré-consensus vs transaction exécutée | non | aucun besoin commun démontré | Transport | REPORTÉ | | account/program | WS account/program + Yellowstone Account | état account complet possible | persistance/replay déjà possédés par RawAccountState/Observation | non comme event distinct | RAW ingestion | Store/Transport | REJET Interface | | transaction complète | HTTP/WS/Helius/Yellowstone transaction | transaction complète selon source | format/commitment/provider metadata ; RawTransaction déjà canonique Store | non comme second type | RAW ingestion | Store/Transport | REJET Interface | | block | getBlock/blockSubscribe/Yellowstone Block | slot + block container | payloads/options/reconstruction ; pas de consumer passif minimal prouvé | non | extraction RAW transaction | Transport | REJET Interface | | Yellowstone Entry | Yellowstone aujourd'hui | aucune multi-source démontrée | mono-producer et protocole-specific aujourd'hui | non aujourd'hui ; futur fait transversal -> Interface | aucun actuel | Transport DTO | REPORTÉ | | persisted-data available | Store after commit | référence durable compacte conceptuelle | event réseau sans relation ; owner déjà normé par KSP-NOTIFY | oui mais hors owner Interface | processors futurs | Store API | REJET Interface | | Helius Enhanced transaction | Helius transactionSubscribe | peut produire `TransactionExecutionEvent` lorsque signature/slot/outcome sont déterminables | filtres/options/provider behavior et DTO riche | DTO non ; projection sémantique admise via converter | futur worker RAW live | Transport DTO / Interface fact | PRODUCTEUR ADMIS | ### 7.1 Lifecycle de slot — ADMIS sous intersection stricte | Fait commun | Solana standard | Yellowstone | Stage KSP | Décision Interface | Metadata volontairement non commune | | ------------------------ | --------------------------------------- | -------------------- | ------------------------- | ------------------ | ------------------------------------------------------------------------------ | | slot traité | `slotSubscribe` | `Processed` | `Processed` | ADMIS | parent/root courant côté WS ; filters/created_at/parent côté Yellowstone | | premier shred reçu | `FirstShredReceived` | `FirstShredReceived` | `FirstShredReceived` | ADMIS | timestamp WS ; created_at/filters côté Yellowstone | | ingestion slot complétée | `Completed` | `Completed` | `Completed` | ADMIS | timestamp WS ; created_at/filters côté Yellowstone | | bank créé | `CreatedBank` | `CreatedBank` | `CreatedBank` | ADMIS | parent requis WS, optionnel Yellowstone ; il reste dans le DTO Transport riche | | slot mort | `Dead` | `Dead` | `Dead` | ADMIS | diagnostics source-specific et horodatages restent Transport | | confirmation optimiste | `OptimisticConfirmation` | `Confirmed` | `OptimisticallyConfirmed` | ADMIS | le nom KSP conserve la sémantique Solana ; delivery reste Transport | | root atteint | `rootSubscribe` / `slotsUpdates` `Root` | `Finalized` | `Rooted` | ADMIS | le nom KSP évite de généraliser `Finalized` au-delà du mapping audité | | frozen | `Frozen` | aucun statut exact | — | REJETÉ | pas d'intersection | Le fait commun est volontairement plus petit que chaque DTO producteur. La conversion **n'efface pas** le DTO Transport : elle ajoute une projection passive `slot + stage` pour les consumers qui n'ont besoin que de l'occurrence de l'étape commune. Le timestamp n'entre pas dans le contrat commun : `slotsUpdatesSubscribe.timestamp` décrit l'horodatage Unix de l'update côté validator alors que `YellowstoneUpdateTimestamp` est une metadata de création serveur. Les réunir sous un seul champ produirait une fausse équivalence. Le `parent`, le root courant exposé comme metadata de certaines notifications, `dead_error` et les détails de delivery restent source-owned ; un consumer qui en a besoin doit conserver le DTO Transport ou définir un contrat supérieur spécifique. Les mappings `OptimisticConfirmation -> OptimisticallyConfirmed <- Yellowstone Confirmed` et `Root -> Rooted <- Yellowstone Finalized` sont des normalisations sémantiques documentées, pas des renommages universels des commitments Solana. ### 7.2 Transaction execution — ADMIS `pre.003` `pre.003` confirme que l'**exécution observée d'une transaction** est distincte des snapshots/commitments de signature et possède une intersection multi-producer compacte : ```text Solana logsSubscribe -> context slot + signature base58 + err Yellowstone TransactionStatus -> slot + signature [u8; 64] + err + metadata Yellowstone Helius transactionSubscribe -> slot + signature + résultat d'exécution lorsque le detail mode expose l'erreur ``` Le contrat admis est : ```text TransactionSignature([u8; 64]) TransactionExecutionOutcome { Succeeded, Failed, } TransactionExecutionEvent { slot, signature, outcome, } ``` Décisions du gate : - `TransactionSignature` est une primitive passive Interface-owned de **64 octets déjà décodés** ; Interface n'ajoute ni base58, ni codec, ni dépendance Solana supplémentaire ; - cette primitive représente l'identité transactionnelle observée, pas une capability de signature Wallet ; - `logsSubscribe` fournit directement `slot`, `signature` et `err`; `err == null` produit `Succeeded`, une erreur produit `Failed` ; - Yellowstone `TransactionStatus` fournit exactement `slot`, signature et erreur optionnelle ; `is_vote`, `index`, filters et `created_at` restent Transport ; - Helius `transactionSubscribe` reste provider-specific : une projection n'est produite que lorsque le DTO/detail mode permet de déterminer `slot`, signature et outcome sans ambiguïté ; une forme `none`, inconnue ou sans information d'erreur ne fabrique aucun outcome ; - commitment, transaction index, logs, memo, block time, confirmation status, provider timestamps et erreurs opaques restent hors Interface ; - le futur worker RAW live peut utiliser ce fait comme signal d'acquisition/hydratation provider-neutral sans le confondre avec une donnée RAW persistée. `TransactionSignature` ne remplace pas `ksp-store-api::RawTransactionSignature`. Les deux contrats ont des owners distincts : Interface possède l'identité compacte d'un **event-only fact**, Store possède l'identité d'une **référence RAW persistante**. La conversion explicite entre les deux appartient à la composition conformément à `DEP-KSP-003`; aucune dépendance Interface -> Store n'est introduite. Les DTOs `logsSubscribe`, Yellowstone `TransactionStatus` et Helius `transactionSubscribe` restent intégralement Transport-owned. Interface ne contient aucun converter depuis ces types. ### 7.3 Logs realtime et signature commitment — DIFFÉRENCIÉS Les logs ne sont plus utilisés comme argument pour rejeter l'exécution commune : `logsSubscribe` peut produire un `TransactionExecutionEvent` sans que les lignes de logs entrent dans le contrat partagé. Une éventuelle famille distincte reste une **idée différée** : ```text TransactionLogEvent { slot, signature, logs, } ``` Elle n'est pas admise en `0.3.5` tant que les bornes, la disponibilité multi-producer et un consumer concret ne sont pas démontrés. Aucun `RawLog` n'est créé. Les surfaces de commitment/snapshot restent séparées de l'exécution : ```text HTTP getSignatureStatuses = snapshot interrogé, avec confirmations/status/confirmationStatus WS signatureSubscribe = transition one-shot vers le commitment demandé, éventuellement précédée de ReceivedSignature ``` Aucun `TransactionCommitmentEvent` n'est admis. Une future famille de commitment devra être auditée indépendamment et ne pourra pas être fusionnée avec `TransactionExecutionEvent` par une struct à nombreux `Option`. ### 7.4 Vote — REPORTÉ `voteSubscribe` est explicitement pré-consensus et ne garantit pas l'entrée du vote dans le ledger. Le booléen `is_vote` de la famille transaction Yellowstone qualifie une transaction et n'est pas une notification gossip équivalente. Aucun `VoteEvent` partagé n'est admis. ### 7.5 Account/program — REJET Interface Les updates account/program peuvent contenir l'état complet nécessaire à l'acquisition RAW. `ksp-store-api` possède déjà : ```text RawAccountState RawAccountObservation ``` Créer un second account event structurel dans Interface serait soit : - un doublon persistant déguisé ; - un wrapper des DTOs Transport ; - un type incomplet qui perd les conditions d'admission RAW. La conversion Transport -> Store API reste à la composition. ### 7.6 Transaction/block — REJET Interface `RawTransaction` et `RawTransactionObservation` sont déjà Store-owned. Les blocs servent de conteneurs d'acquisition ou de payloads Transport riches. Aucun `RawBlock`, `TransactionEvent` ou `BlockEvent` transversal n'est ajouté. ### 7.7 Yellowstone Entry — Transport aujourd'hui, ownership sémantique réservé `SubscribeUpdateEntry` reste un DTO Yellowstone strictement Transport-owned : un seul producer/protocole est démontré aujourd'hui et aucun consumer transversal proche ne justifie un type partagé. Cette décision ne réserve toutefois pas le **fait** à Transport : si une future seconde source permet de reconstruire un même `LedgerEntryEvent` provider-neutral, ce nouveau fait passif devra être audité pour ownership Interface, sans déplacer le DTO Yellowstone lui-même. ### 7.8 Notification after commit — REJET Interface Les règles `KSP-NOTIFY-001..006` sont explicites : le format canonique du signal « persisted data available » appartient à `ksp-store-api`, est publié après commit et ne constitue jamais le backlog. `0.3.5` ne crée aucun équivalent Interface. ## 8. API sketch admise La seule API planifiée pour `pre.002` est conceptuellement : ```rust #[non_exhaustive] pub enum SlotLifecycleStage { Processed, FirstShredReceived, Completed, CreatedBank, Dead, OptimisticallyConfirmed, Rooted, } pub struct SlotLifecycleEvent { slot: u64, stage: SlotLifecycleStage, } ``` Surface publique visée : ```text SlotLifecycleStage SlotLifecycleEvent::new(slot, stage) SlotLifecycleEvent::slot() SlotLifecycleEvent::stage() ``` Décisions de représentation : ```text SlotLifecycleStage -> #[non_exhaustive] + Clone + Copy + Debug + Eq + PartialEq SlotLifecycleEvent -> champs privés + Clone + Copy + Debug + Eq + PartialEq Hash -> absent tant qu'un consumer réel ne l'exige pas validation -> aucune borne variable ; slot u64 conservé exactement error code -> aucun ; aucune admission faillible n'est nécessaire ``` Contraintes : - pas de serde imposé ; - pas de `String`, JSON, bytes, timestamp ou diagnostic ; - pas de network id dupliquant `RawNetworkId` ; la composition est déjà liée à son contexte réseau ; - `Debug` est sûr par construction ; - aucun error code n'est nécessaire si tout `u64` de slot et toute variante publique construite sont valides ; - aucune dépendance nouvelle n'est requise ; - aucun trait universel `Event`, `EventSource`, `Subscriber` ou `Handler` ; - aucune conversion `From` dans Interface, car cela créerait une dépendance interdite ; - les converters appartiennent au futur consumer/composition ou, si un usage concret le justifie plus tard, à une crate de composition dédiée. Le nom final peut être ajusté pendant `pre.002` seulement si les canaris montrent une ambiguïté réelle. Le sens `slot + stage commun` ne doit pas être élargi. `OptimisticallyConfirmed` et `Rooted` sont volontairement préférés à `Confirmed` et `Finalized` afin de conserver la sémantique KSP du fait normalisé sans transformer les labels Yellowstone en terminologie universelle. ## 9. Consumer proche et usage concret L'architecture prévoit un futur worker RAW live : ```text subscriptions/fetch live -> ksp-onchain-transport-lib -> RAW ingestion -> D1 RAW ``` Ce worker est le consumer proche qui peut recevoir des événements de progression/lifecycle de slot venant soit des WebSockets standard, soit de Yellowstone sans connaître les DTOs des deux protocoles lorsqu'il n'a besoin que de déclencher/ordonner une réaction de composition propre au lifecycle. Le même worker peut consommer `TransactionExecutionEvent` comme signal compact qu'une signature a été exécutée dans un slot avec succès ou échec, puis décider dans sa propre policy/composition s'il doit hydrater/persister un `RawTransaction`. Le fait Interface ne contient ni payload ni provenance et n'est jamais utilisé comme preuve durable de présence dans Store. Ces APIs ne deviennent pas un prérequis de `0.3.6` : le premier backfill historique reste fondé sur Transport + Store et peut ne jamais consommer les events Interface. ## 10. Anti-duplication Store | Contrat Store comparé | Chevauchement Interface admis | Pourquoi il n'y a pas de seconde vérité | | ------------------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `RawTransaction` | slot + signature possibles via execution event | aucun payload/format/hash/block_time ; l'event ne peut reconstruire ni remplacer une transaction RAW | | `RawTransactionReference` | signature sémantiquement apparentée | la référence Store inclut `RawNetworkId` et possède l'identité durable ; Interface reste contextuel | | `RawTransactionSignature` | même primitive protocolaire 64 octets | wrapper Store et primitive Interface restent owner-specific ; conversion explicite à la composition | | `RawTransactionObservation` | référence transaction possible indirectement | aucune provenance acquisition, observation key ou timestamp durable | | `RawAccountState` | slot possible via lifecycle seulement | aucune pubkey/state hash/lamports/owner/data ; aucun état account | | `RawAccountObservation` | slot indirect possible | aucune référence account/provenance Geyser | | `RawAcquisitionProvenance` | aucun | pas d'origin/provider code/timestamps ; les metadata source restent Transport/composition | | `RawTimestamp` | aucun | aucun timestamp commun inventé | | `RawContentHash` | aucun | aucune identité de contenu durable | | `RawObservationKey` | aucun | aucune identité d'observation persistante | | notification after commit | aucun | event réseau observe l'acquisition ; wake-up Store référence une donnée déjà commitée | Le gate conserve les canaris conceptuels suivants : ```text SlotLifecycleEvent -X-> RawTransaction TransactionExecutionEvent -X-> RawTransaction TransactionExecutionEvent -X-> RawTransactionObservation TransactionSignature -X-> RawTransactionSignature implicit conversion Interface -X-> ksp-store-api Interface -X-> ksp-store-lib Store API -X-> Interface ``` Une couche de composition peut convertir explicitement `TransactionSignature::as_bytes()` vers `RawTransactionSignature::new(...)`, mais aucun `From` cross-domain n'est possédé par Interface ou Store API. Aucun champ d'un event admis ne constitue à lui seul une preuve RAW durable. ## 11. Graphe cible Après implémentation de la release : ```text ksp-interface-lib └── ksp-core-lib ``` Aucune nouvelle dependency normale ou feature n'est planifiée. Les consumers restent : ```text ksp-program-api -> ksp-interface-lib # ProgramInstruction / ProgramAccountMeta seulement future acquisition composition / RAW live worker -> ksp-onchain-transport-lib -> ksp-interface-lib # SlotLifecycleEvent / TransactionExecutionEvent -> ksp-store-lib / ksp-store-api selon ownership ``` ## 12. Threat model | Risque | Menace | Garde planifiée | | ------------------------------ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | mega enum | Interface devient catalogue de tous les transports | familles admises séparées et étroites ; aucun `Event` générique | | Option soup | sémantiques différentes fusionnées par champs optionnels | familles séparées : SlotLifecycle / TransactionExecution ; logs/commitment restent distincts | | persistence creep | event-only devient second Store | aucun RAW/payload/cursor/retention ; `KSP-NOTIFY-*` reste Store API | | transport wrapper | copie des DTOs WS/gRPC/provider | aucune metadata protocol/provider dans le type Interface | | faux timestamp commun | horodatages WS et Yellowstone confondus | aucun timestamp partagé | | root/finality confusion | label Yellowstone `Finalized` promu comme vérité universelle | stage KSP `Rooted` ; mapping source explicite `Root <-> Yellowstone Finalized` sans renommer le fait KSP | | optimistic/confirmed confusion | label Yellowstone `Confirmed` promu comme commitment générique | stage KSP `OptimisticallyConfirmed` ; mapping source explicite avec l'optimistic confirmation Solana | | dead diagnostic leak | texte provider/validator traverse une API partagée durable | stage `Dead` sans diagnostic ; détail reste Transport | | cross-network confusion | event sans réseau mélangé entre sessions | conversion autorisée seulement dans une composition déjà liée à un contexte réseau ; pas de network id dupliqué | | signature/payload leak | signature ou payload brut fuit via Debug | signature fixe 64 octets, Debug redacted ; aucun payload variable | | public enum breakage | ajout futur casse les matches externes | `SlotLifecycleStage` et `TransactionExecutionOutcome` non-exhaustive | | logging/runtime creep | crate passive acquiert tracing/Tokio/channel | aucune dependency/runtime/logging ajoutée | | Helius semantic promotion | DTO/provider extension devient norme KSP | `transactionSubscribe` reste Transport ; seule une projection sémantique commune peut devenir Interface | | Yellowstone protocol promotion | DTO `Entry`/filters/created_at deviennent Interface | types Yellowstone restent Transport ; un futur fait transversal est réaudité séparément | ## 13. Stratégie de tests ### 13.1 `pre.002` — modèle admis Tests unitaires Interface : - chaque stage public est distinct ; - `slot` est conservé sans narrowing ; - le constructeur/getters sont passifs et déterministes ; - le type n'emporte aucune metadata de transport. Tests d'intégration : - crate-root public API ; - external consumer ; - dependency boundary Core-only ; - exact production module/export inventory. ### 13.2 `pre.003` — transaction execution admis Tests unitaires Interface : - `TransactionSignature` conserve exactement 64 octets et masque ses bytes en `Debug` ; - `Succeeded` et `Failed` sont distincts ; - `TransactionExecutionEvent` conserve `u64::MAX`, signature et outcome sans narrowing ; - `Debug` d'event ne rend pas la signature. Tests d'intégration : - les trois nouveaux symboles sont disponibles depuis le crate-root ; - `TransactionExecutionOutcome` impose un wildcard downstream via `#[non_exhaustive]` ; - l'inventaire crate-root passe de 8 à 11 reexports ; - l'inventaire de production ajoute uniquement `transaction_execution`. ### 13.3 Convergence sans dépendance Transport `ksp-interface-lib` ne doit pas ajouter `ksp-onchain-transport-lib` même en dépendance normale pour « tester » les converters. Les preuves d'équivalence sémantique restent dans le plan/validation de release et les canaris de frontière Interface. Si un converter concret est introduit plus tard dans une crate de composition, sa propre crate devra tester les mappings exacts : ```text SolanaSlotNotification -> Processed SolanaSlotUpdate::FirstShredReceived -> FirstShredReceived SolanaSlotUpdate::Completed -> Completed SolanaSlotUpdate::CreatedBank -> CreatedBank SolanaSlotUpdate::Dead -> Dead SolanaSlotUpdate::OptimisticConfirmation -> OptimisticallyConfirmed SolanaRoot / SolanaSlotUpdate::Root -> Rooted YellowstoneSlotStatus::Confirmed -> OptimisticallyConfirmed YellowstoneSlotStatus::Finalized -> Rooted YellowstoneSlotStatus autres équivalents -> mêmes stages ``` Et devra explicitement refuser `Frozen` ou toute variante future sans mapping audité au lieu d'inventer un faux stage commun. ### 13.4 Traçabilité des candidats différés `TransactionExecutionEvent` est admis par `pre.003` et n'est donc plus un TODO. `TransactionLogEvent` reste explicitement une idée différée. Si cette idée reste hors scope à la réconciliation documentaire finale, elle doit être reportée dans la surface TODO/IDEAS durable appropriée au lieu d'être perdue avec le plan de release. ## 14. Prévision souple des prereleases La release reste dimensionnée pour une seule session. `SlotLifecycleEvent` est matérialisé en `pre.002` et `TransactionExecutionEvent` devient la seconde famille admise en `pre.003`; aucune troisième famille n'est ouverte sans nouveau gate. ### `0.3.5-pre.001` — audit + plan - lecture des règles/architectures/surfaces ; - audit externe actuel ; - matrice producer/fact et convergence ; - ownership et anti-duplication Store ; - threat model ; - API sketch ; - sizing et plan de release. ### `0.3.5-pre.002` — `SlotLifecycleEvent` - implémenter la famille minimale admise ; - crate-root exports ; - tests unitaires et public API ciblés ; - préserver le graphe Core-only. ### `0.3.5-pre.003` — `TransactionExecutionEvent` - gate signature/dependency : PASS avec `TransactionSignature([u8; 64])` passive, sans codec ni dépendance supplémentaire ; - mappings Solana `logsSubscribe` / Yellowstone `TransactionStatus` / Helius `transactionSubscribe` : PASS sous projection conservative ; - anti-duplication Store : PASS, conversion explicite Interface <-> Store à la composition ; - consumer concret : PASS, futur worker RAW live ; - implémenter `TransactionSignature`, `TransactionExecutionOutcome` et `TransactionExecutionEvent` seulement ; - ne pas introduire logs, commitment, metadata provider ou converters Transport dans Interface. ### `0.3.5-pre.004` — canaris externes + complétude API - external consumer pour toutes les familles effectivement admises ; - inventaire exact des modules/exports ; - canaris négatifs contre Transport/Store/runtime/serde/logging ; - non-exhaustive et stabilité de la surface. ### `0.3.5-pre.005` — hardening final - adversarial/API hardening ; - absence de payload/source metadata ; - absence de second RAW ; - validation du graphe et des dépendances ; - aucun élargissement fonctionnel opportuniste. ### `0.3.5-pre.006` — gate technique final - gates Rust/workspace complets ; - `cargo tree` Interface normal/features/duplicates ; - aucun smoke live requis : les contrats Interface restent passifs et ne possèdent aucun réseau. ### `0.3.5-pre.007` — réconciliation documentaire finale - plan + validation ; - README/USAGE Interface durables et version-neutral ; - architecture durable uniquement si le rôle Interface doit être explicité ; - aucun CHANGELOG/ROADMAP/prompt suivant dans cette tranche. ### `0.3.5-pre.008` — préparation de publication Lane minimale : ```text Cargo.toml CHANGELOG.md ROADMAP.md prompts/025-V0_3_6_START_PROMPT.md deltas/0.3.5/pre.008.md ``` Aucun code, README/USAGE, plan ou validation ne doit être rouvert ici. ### `0.3.5-rel.001` — stable - mécanique de publication uniquement ; - version Cargo finale `0.3.5` ; - aucun rattrapage fonctionnel/documentaire. ## 15. Gates opérateur ### 15.1 Après chaque overlay fonctionnel ```text cargo fmt --all python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.5 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-interface-lib ``` Ajouter `cargo test -p ksp-program-api` lorsqu'une modification publique Interface pourrait affecter son consumer actuel. ### 15.2 Gate technique final ```text cargo fmt --all python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.5 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-interface-lib cargo test --workspace cargo tree -p ksp-interface-lib --edges normal cargo tree -p ksp-interface-lib -e features cargo tree --duplicates ``` Aucun live test n'est justifié par `0.3.5` : un type passif ne doit pas acquérir de dépendance réseau pour prouver son existence. ## 16. Hors périmètre ```text grand enum Event event bus / channel / broadcaster scheduler / worker / job backfill 0.3.6 persistence ou migration Store nouvelle capability Store RawLog / RawBlock TransactionStatusObservation fusionné TransactionCommitmentEvent fusionné avec execution TransactionLogEvent sans nouveau gate de bornes/consumer VoteEvent partagé label KSP `Finalized` utilisé comme alias universel de root label KSP `Confirmed` utilisé comme alias universel d'optimistic confirmation Helius Enhanced DTO dans Interface Yellowstone Entry DTO dans Interface serde/wincode/borsh ajouté sans protocole réel logging/runtime dans Interface conversion Transport -> Interface possédée par Interface ``` ## 17. Critère de clôture de `0.3.5` La release peut fermer si et seulement si : ```text une famille minimale SlotLifecycle est publique et bornée intersection = Processed + FirstShredReceived + Completed + CreatedBank + Dead + OptimisticallyConfirmed + Rooted Frozen et toute variante future sans mapping audité ne sont pas normalisées artificiellement TransactionExecutionEvent est admis en pre.003 sous la forme minimale slot + TransactionSignature + outcome aucun payload/diagnostic/timestamp/provider metadata n'entre dans Interface Interface reste Core-only Store RAW 10/10 reste inchangé KSP-NOTIFY ownership reste Store API aucun event bus/runtime/persistence n'est ajouté external consumer et crate-root canaries passent README/USAGE finaux restent durables et version-neutral ``` Si l'implémentation montre qu'un de ces invariants ne peut pas être préservé, la famille SlotLifecycle est retirée plutôt qu'élargie artificiellement.