# Plan `0.3.1` — Store API RAW foundation ## 1. Statut et base Ce plan est établi par `0.3.1-pre.001` à partir de la release stable `v0.2.14` et de l'archive historique obligatoire `khadhroony-bot3_v0.5.3-pre.005-fix010.zip`. La base KSP vérifiée à l'ouverture est : ```text workspace.package.version = 0.2.14 deltas/0.2.14/rel.001.md présent prompts/020-V0_3_1_START_PROMPT.md présent ksp-program-api présent ksp-store-api absent ksp-store-lib absent ``` L'archive opérateur ne contient pas de metadata Git exploitable ; le tag `v0.2.14` ne peut donc pas être revérifié localement. La version Cargo, le delta `rel.001`, le prompt 020 et la surface Program API publiée concordent avec la base stable attendue. Le journal opérateur fourni avec l'archive montre un baseline stable intégralement vert avant ouverture de `0.3.1` : audits Rust/Markdown, `cargo check --workspace`, Clippy et `cargo test --workspace` passent. Après application de `pre.001`, la version Cargo cible est : ```text 0.3.1-pre.1 ``` ## 2. Décision opérateur qui recalibre le prompt initial Le brainstorming de `pre.001` a séparé la création de l'API Store de son implémentation PostgreSQL. La trajectoire devient d'abord : ```text 0.3.1 = ksp-store-api uniquement 0.3.2 = ouverture conjointe ksp-store-lib + ksp-store-postgres-lib fondation runtime/backend PostgreSQL uniquement feature postgres par défaut PostgreSQL de référence via tokio-postgres 0.3.3 = même paire de crates / persistence PostgreSQL RawTransaction complète 0.3.4 = même paire de crates / RawAccountState + complétude Store RAW ``` Le redécoupage `pre.010` conserve donc les deux crates Store/PostgreSQL **ensemble à chaque release**, mais sépare la charge en trois slices. Il évite de cumuler dans une seule session création de façade, Config, connexions/migrations, persistence transaction, rétention et persistence account. Cette décision remplace pour `0.3.1` la mission combinée `ksp-store-api + ksp-store-lib` décrite dans le prompt de démarrage. `pre.001-fix.001` réconcilie immédiatement le `ROADMAP.md` afin que la trajectoire globale ne conserve pas une séquence désormais fausse. Conséquences immédiates : ```text aucune dépendance PostgreSQL en 0.3.1 aucun tokio-postgres dans Cargo en 0.3.1 aucun pool aucune table aucun SQL aucune migration aucun std.store aucun ksp-store-lib ``` `0.3.1` doit stabiliser le contrat logique suffisamment pour que la série `0.3.2`–`0.3.4` puisse ensuite demander : > quelle représentation PostgreSQL satisfait le mieux ce contrat, puis comment l'implémenter famille par famille sans réduire le contrat API ? et non : > comment exposer les tables déjà créées ? ## 3. Mission recalibrée `0.3.1` introduit `ksp-store-api` comme **modèle logique commun et API fonctionnelle backend-agnostic du niveau N1 RAW/acquisition**. La crate doit posséder : ```text modèles objet/struct persistants communs observations d'acquisition persistantes lorsque leur conservation est utile références durables outcomes d'écriture/idempotence queries cursorisées sans plafond métier arbitraire contrats/capabilities backend extensibles cycle de rétention logique du RAW et tombstones anti-rebackfill ``` Le health/readiness runtime et un éventuel type canonique dédié de wake-up ne sont finalement pas matérialisés dans `0.3.1`. Les références durables de l'API suffisent pour la foundation RAW ; le health appartient à la façade runtime `ksp-store-lib` et peut être cadré dès la fondation `0.3.2`, tandis qu'un contrat de notification dédié ne sera ajouté que lorsqu'un consumer/publisher réel en aura besoin conformément aux règles KSP-NOTIFY. La release doit aussi auditer les autres données on-chain réellement utiles afin de distinguer explicitement : ```text modèle N1 persistant/replayable modèle d'observation persistant modèle event-only non persisté DTO Transport seulement ``` La façade runtime concrète `Store`, la sélection d'un backend compilé et l'orchestration commune appartiendront à `ksp-store-lib` à partir de `0.3.2`. Les consumers ordinaires jobs/workers/apps dépendront alors uniquement de `ksp-store-lib`, qui réexportera la surface commune nécessaire de `ksp-store-api`. `0.3.3` et `0.3.4` complèteront cette même façade et le même backend PostgreSQL sans introduire une seconde architecture. `ksp-store-api` ne devient pas propriétaire de tous les messages inter-crates. Un modèle passif partagé qui ne représente aucune donnée persistée/rejouable et sert uniquement à transporter un événement entre acquisition et traitement relève préférentiellement de `ksp-interface-lib`. La frontière exacte doit être documentée avant création d'un tel type afin d'éviter deux structs concurrentes représentant le même fait. `ksp-store-api` ne possède pas : ```text schéma physique rows backend SQL migrations pool/connection transactions SQL backend selection par lecture Config tokio-postgres PostgreSQL/MySQL/SQLite/RocksDB/ClickHouse types Transport runtime Program decoder Materializer scheduler/event bus/notification runtime ``` Le modèle public `ksp-store-api` est volontairement réutilisable par toute implémentation future. Un backend peut choisir une représentation physique radicalement différente sans modifier les DTO/entités et opérations logiques consommés par l'extérieur. ## 4. Modèle d'extensibilité backend ### 4.1 Graphe durable Le graphe cible devient : ```text ksp-store-api modèles + contrats communs | +------------------+------------------+ | | v v ksp-store-lib ksp-store-postgres-lib façade/runtime commun impl PostgreSQL backend dispatch tokio-postgres | ^ | feature postgres (default) ----------+ | +-- feature mysql ------> futur ksp-store-mysql-lib +-- autres features ----> futurs backends ``` Règles de dépendances : ```text ksp-store-lib -> ksp-store-api ksp-store-lib[postgres] -> ksp-store-postgres-lib ksp-store-postgres-lib -> ksp-store-api futur ksp-store-mysql-lib -> ksp-store-api ksp-store-postgres-lib -X-> ksp-store-lib backend alternatif -X-> ksp-store-lib ``` `ksp-store-lib` est donc le point de consommation normal du workspace, tandis que `ksp-store-api` reste le contrat d'implémentation partagé entre la façade et les backends. ### 4.2 Features de `ksp-store-lib` La fondation `0.3.2` introduira au minimum : ```text default = [postgres] postgres -> dépendance optionnelle ksp-store-postgres-lib ``` Les futures features peuvent ajouter `mysql`, `sqlite`, `rocksdb`, `clickhouse` ou tout autre backend réellement implémenté. Plusieurs features backend peuvent être compilées simultanément ; la feature décide **quels backends sont disponibles dans le binaire**, jamais lequel est sélectionné au runtime. La Config décide le backend actif parmi ceux compilés. Un backend KSP connu mais absent des features du binaire doit produire une erreur distincte et stable de type `STORE_BACKEND_NOT_COMPILED`, sans fallback silencieux vers PostgreSQL ou un autre backend. ### 4.3 Config, URI et secrets `ksp-store-api`, `ksp-store-lib` et les crates backend ne lisent jamais directement `.env` ni les variables KSP/KSPB. `ksp-config-lib` reste propriétaire des documents, placeholders, `.env`, provenance et sensibilité. La configuration future suit : ```text std.store / profil composite | v ksp-config-lib résolution ${KSP_SECRET_*} + validation schema/sémantique | v settings runtime ksp-store-lib | v backend compilé sélectionné | v crate backend privée ``` Règles fixées pour `0.3.2` puis conservées par `0.3.3`, `0.3.4` et les futurs backends : - utiliser une URI/DSN lorsque le moteur possède une forme URI naturelle (`postgresql://...`, futur `mysql://...`, etc.) ; - permettre des options typées backend-specific uniquement lorsqu'elles sont réellement nécessaires et sans `serde_json::Value` opaque comme contrat runtime ; - considérer l'URI entière comme `Secret` dès qu'elle peut embarquer user/password/token ou autre credential ; - obtenir les secrets via des placeholders `KSP_SECRET_*` résolus par Config, avec inventaire dans `.env.example` au même delta que leur première utilisation ; - ne jamais versionner un vrai `.env` ni un URI/credential réel ; - ne jamais exposer URI, password, token, query secret ou chemin sensible dans `Debug`, logs, erreurs ou health ; - distinguer `backend inconnu` de `backend KSP connu mais non compilé` avant tentative de connexion ; - éviter tout fallback implicite vers `PG*`, `.pgpass` ou autre source d'environnement lue directement par le driver/backend lorsque Config a déjà fourni les settings effectifs. La forme exacte de `StoreSettings` et du document `std.store` appartient au design de fondation `0.3.2`; `0.3.1` fixe seulement ces responsabilités et invariants. ### 4.4 Surface consumer Les jobs/workers/apps ne dépendent pas directement de `ksp-store-postgres-lib` ni des futurs backends. Le chemin normal est : ```text ksp-job-* / ksp-worker-* / app -> ksp-store-lib -> réexporte les types API communs nécessaires -> expose Store/open/read/write/query communs -> dispatch vers le backend choisi par Config ``` Le consumer ne contient aucun `#[cfg(feature = "postgres")]` métier et ne branche pas sur `postgres`, `mysql` ou un nom de moteur pour lire/écrire les données. ### 4.5 Modèle public et modèle interne Trois surfaces restent strictement distinctes : ```text ksp-store-api RawTransaction / observations persistantes / provenance / queries / outcomes = modèle objet et contrats communs à toutes les implémentations ksp-store-lib Store / lifecycle / dispatch / settings runtime publics communs = façade de consommation normale ksp-store-postgres-lib Postgres*Row / SQL / schema / migrations / pool / statements = implémentation privée au backend ``` Aucun type de row backend, handle de pool, statement préparé, migration ou nom d'objet SQL ne traverse `ksp-store-lib` vers ses consumers. ### 4.6 Frontière `ksp-interface-lib` / `ksp-store-api` Les deux crates ne doivent pas devenir des définitions concurrentes du même modèle. Règle de décision : ```text fait durable/replayable/queryable par Store -> ksp-store-api observation durable attachée à un fait Store -> ksp-store-api message passif event-only échangé entre composants sans contrat de persistence -> ksp-interface-lib signal canonique « donnée Store persistée disponible » qui ne transporte qu'une référence durable -> ksp-store-api pour le format uniquement shape spécifique à HTTP/WS/gRPC/provider -> Transport seulement ``` Lorsqu'un type Interface correspond exactement à une partie d'un modèle Store, `ksp-store-api` peut le réutiliser uniquement si cela ne crée ni dépendance de protocole ni sémantique incomplète. Sinon la composition effectue une conversion explicite. Aucun copier-coller de structs quasi identiques n'est accepté pour éviter une dérive silencieuse des deux contrats. Cette règle sera réauditée au moment d'introduire les événements realtime (`logsSubscribe`, slot/vote/status) et les futurs types Interface nécessaires à l'acquisition. ## 5. Sources KSP relues Le gate `pre.001` a relu les sources 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/007-EXECUTION_AND_POLICY.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 ``` Les règles directement structurantes sont : ```text KSP-API-001..007 KSP-CONFIG-001..018 KSP-NOTIFY-001..006 KSP-STORE-001..002 KSP-DATA-001..004 KSP-PIPE-001..007 DEP-KSP-001..005 DEP-CARGO-001..007 DEP-STORE-001..008 DEP-TRANSPORT-001..005 DEP-PIPE-001..008 DEP-WORKER-001..003 DEP-JOB-001..003 ``` Le résultat normatif principal est : ```text Store API possède les contrats persistants Store API -X-> Transport/Program/Materializer Transport -X-> Store API conversion explicite à la composition persist -> commit -> notification notification != backlog at-least-once + persistence idempotente ``` ## 6. N1 RAW : définition retenue ### 6.1 Rôle et critère d'admission N1 est la couche **acquisition / RAW replayable**. Toutes les réponses Transport ne deviennent pas automatiquement des modèles Store. Un type n'entre dans la taxonomie N1 que s'il apporte au moins une valeur durable au pipeline : ```text persistence / replay future décomposition structurelle future interprétation/décodage état durable nécessaire au processing ``` Les données utiles uniquement comme déclencheur realtime peuvent avoir un modèle passif commun, mais ce modèle est event-only et ne reçoit pas automatiquement une capability Store. Une même entité N1 ne peut agréger plusieurs sources que si chaque source candidate peut être convertie **sans perte de la sémantique exigée par le contrat commun**. Il est interdit de fabriquer un modèle universel rempli de `Option` uniquement pour faire entrer des réponses incompatibles. La règle d'admission est donc : ```text HTTP / WS / gRPC / provider response | v satisfait intégralement le contrat canonique KSP ciblé ? | | oui non | | v v même modèle N1 autre modèle / event + observation ou DTO Transport seulement ``` Une matrice de compatibilité source -> modèle doit être produite avant de figer chaque nouvelle famille. ### 6.2 `RawTransaction` et observation `RawTransaction` est la première famille persistante certaine de `0.3.1`. Les sources capables de fournir la transaction complète et la meta nécessaire au replay structurel doivent converger vers le même modèle, indépendamment de l'origine : ```text getTransaction getBlock utilisé comme conteneur d'acquisition transactionSubscribe lorsque la forme fournit le contenu complet yellowstone Transaction future source équivalente auditée ``` Le contrat sépare : ```text RawTransaction = fait RAW canonique source-independent et replayable RawTransactionObservation = fait qu'une source donnée a observé/acquis cette transaction ``` Une réponse ne contenant qu'une signature, un status ou un sous-ensemble de champs ne produit jamais un `RawTransaction` artificiellement incomplet. La même transaction acquise plusieurs fois converge vers une seule identité logique et plusieurs observations : ```text RawTransaction T ^ | +---------------+---------------+ | | | HTTP/provider obs WS/provider obs gRPC/provider obs ``` ### 6.3 Logs de transaction vs notification de logs Les `logMessages` contenus dans la meta d'une transaction appartiennent au `RawTransaction`. Ils ne constituent pas une seconde entité N1 `RawLog`. Leur extraction individuelle appartient à la future décomposition STRUCTURAL : ```text RawTransaction -> STRUCTURAL -> TransactionLogLine / relation instructionnelle future ``` À l'inverse, `logsSubscribe` fournit un message realtime centré sur : ```text slot signature error/status ordered log lines ``` Ce message peut déclencher un traitement ou l'hydratation de la transaction complète, mais il ne doit pas être confondu avec les logs extraits de `RawTransaction`. Le candidat de travail est donc un modèle event-only tel que `TransactionLogNotification`/`RawLogNotification`, dont l'ownership sera tranché entre `ksp-interface-lib` et `ksp-store-api` selon la règle §4.6. **Aucune persistence Store de ces notifications n'est requise par défaut.** Le pipeline realtime futur pourra faire : ```text logsSubscribe -> event passif -> worker/analyser -> éventuellement getTransaction(signature) -> persistence RawTransaction ``` Le Store n'envoie lui-même aucun événement. ### 6.4 `RawAccountState` et observation — matrice `pre.004` L'audit de la surface KSP réelle confirme qu'un **état complet de compte** possède une sémantique commune entre HTTP, WebSocket et Yellowstone lorsque la source fournit les bytes complets et un slot durable. Le Store ne conserve pas la forme d'encodage réseau : base58/base64/base64+zstd et protobuf doivent être décodés avant construction du modèle commun. Le modèle commun matérialisé est : ```text RawAccountStateReference network pubkey slot state_hash RawAccountState reference lamports owner executable rent_epoch complete data bytes RawAccountObservation observation_key account reference provenance optional write_version optional transaction_signature optional is_startup ``` `state_hash` fait partie de la référence car une même account peut subir plusieurs écritures dans un slot, alors que les surfaces HTTP/WS standards n'exposent pas le `write_version` Yellowstone. Le digest permet à plusieurs sources observant **le même état complet** de converger sans promouvoir un ordinal provider-specific dans l'identité commune. Le producer/converter possède le calcul déterministe du digest ; Store API ne choisit pas l'algorithme de hash. La matrice d'admission courante est : | Source KSP | État complet commun | Slot durable | Admission `RawAccountState` | |------------------------------------|---------------------|---------------|-------------------------------------------------------------------------------------| | HTTP `getAccountInfo` | oui | oui | oui si account non-null, bytes complets et aucun `dataSlice`/`jsonParsed` | | HTTP `getMultipleAccounts` | oui | oui | oui par position non-null si bytes complets ; pubkey reprise depuis la requête | | HTTP `getProgramAccounts` | oui | conditionnel | oui seulement avec résultat contextualisé + bytes complets ; forme bare refusée | | WS `accountSubscribe` | oui | oui | oui si bytes complets ; pubkey reprise depuis l'identité de subscription | | WS `programSubscribe` | oui | conditionnel | oui seulement pour la forme contextualisée + bytes complets ; forme bare event-only | | Helius standard account/program WS | oui | idem standard | mêmes règles que le wire Solana standard réutilisé | | Yellowstone `Account` | oui | oui | oui si `accounts_data_slice` n'a pas tronqué les bytes | Règles négatives : ```text jsonParsed request-side data slice Yellowstone accounts_data_slice program account sans context/slot account absent/null -X-> RawAccountState persistant incomplet ``` `space` n'est pas conservé comme vérité indépendante : lorsqu'on possède les bytes complets, leur longueur est déterministe. Les enrichissements Yellowstone `write_version`, `txn_signature` et `is_startup` appartiennent à `RawAccountObservation`. Le timestamp serveur et les filters/capture ids restent de la provenance lorsque le converter peut les représenter sans perte utile. La borne initiale Store-owned est : ```text complete account data <= 16 MiB ``` Elle est un admission guard KSP, pas une affirmation sur la limite protocolaire Solana. ### 6.5 Transaction status — convergence insuffisante pour un modèle Store unique L'audit `pre.004` conclut que les trois surfaces candidates ne représentent pas encore exactement le même fait : | Source | Sémantique principale | Conclusion `0.3.1` | |-------------------------------|---------------------------------------------------------------------------|-----------------------------------------------------| | HTTP `getSignatureStatuses` | snapshot interrogé : slot, confirmations, error, confirmation status | candidat snapshot durable, non figé | | WS `signatureSubscribe` | event one-shot : received puis/ou commitment demandé atteint | event runtime, ownership Interface/worker à étudier | | Yellowstone TransactionStatus | update d'exécution : slot, signature, vote, index, error, sans commitment | event/status provider-neutral potentiel, non figé | Créer maintenant un `TransactionStatusObservation` rempli d'options ferait perdre la distinction entre **snapshot interrogé**, **transition de commitment** et **update d'exécution**. Aucun modèle Store n'est donc ajouté en `pre.004`. TODO avant matérialisation : ```text séparer explicitement snapshot durable vs event realtime étudier l'ownership ksp-interface-lib des events passifs prouver la correspondance des états/commitments prévoir un éventuel wake-up worker/analyser sans notification émise par Store ``` ### 6.6 Logs, slot, vote, block et Yellowstone Entry — classification fermée `pre.004` Classification actuelle après audit : ```text logsSubscribe -> event realtime passif distinct -> signature + error + ordered log lines + context slot -> pas de RawLog Store -> ownership ksp-interface-lib/worker à préciser slot/root/slotsUpdates -> event-only candidat -> aucune persistence N1 démontrée vote -> event-only candidat si la forme commune utile est prouvée -> aucune persistence N1 par défaut getBlock / Yellowstone Block -> source/conteneur d'acquisition de RawTransaction -> RawBlock persistant reste IDEA uniquement Yellowstone Entry -> transport-only -> explicitement non retenu actuellement ``` Les logs contenus dans `RawTransaction` restent distincts de `logsSubscribe` : les premiers sont de la matière replayable de transaction et seront extraits en N2 STRUCTURAL ; le second est un événement realtime léger pouvant éventuellement déclencher l'hydratation de la transaction complète. `RawBlock` ne doit être rouvert que si un besoin block-level non reconstructible apporte une valeur concrète au pipeline. Recréer le ledger bloc par bloc sans besoin supplémentaire irait à l'encontre de l'objectif KSP de produire des unités directement exploitables. ### 6.7 Modèles et capabilities sont indépendants La présence d'un modèle partagé n'implique pas une persistence. Exemple : ```text TransactionLogNotification = peut exister comme contrat passif event-only TransactionLogNotificationStore = ne doit pas exister sans besoin durable démontré ``` De même, `RawAccountState` peut être figé comme modèle commun avant que `ksp-store-postgres-lib` n'implémente sa capability. Cette séparation permet d'inventorier correctement N1 sans forcer tous les backends à stocker toutes les familles. ## 7. Payload replayable ### 7.1 Décision de niveau API `ksp-store-api` possède un **payload de persistence RAW KSP**, pas un payload réseau provider. Le contrat candidat est un conteneur borné et versionné : ```text RawPayload format_id format_version bytes content_hash ``` `format_id` identifie un format de persistence KSP source-independent. Il ne vaut pas `helius_json`, `yellowstone_proto` ou un autre wire provider. La conversion : ```text transport model -> conversion explicite -> RawPayload KSP ``` reste hors de `ksp-store-api`. ### 7.2 Pas de codec improvisé `0.3.1` ne choisit pas un codec réseau ou Solana concurrent. `bincode` reste interdit pour les codecs wire KSP. Le premier format concret de transaction canonique doit pouvoir être produit par la future couche de conversion à partir des wires génériques KSP. Sa définition détaillée peut évoluer pendant `0.3.1` tant que les invariants publics restent : ```text source-independent versionné borné lossless pour la normalisation générique couverte Debug sans bytes hashable/idempotent ``` Le Store ne prétend pas que des bytes arbitraires sans format connu sont replayables. ### 7.3 Contrat matérialisé par `pre.003` La première surface concrète reste indépendante de tout codec et de tout backend : ```text RawPayload RawFormatId format_version: u32 non nul bytes: Box<[u8]> non vide RawContentHash: [u8; 32] RawTransactionReference RawNetworkId RawTransactionSignature: [u8; 64] RawTransaction reference slot: u64 block_time: Option payload RawTransactionObservation RawObservationKey: [u8; 32] transaction reference RawAcquisitionProvenance ``` Les bornes initiales sont des **admission guards Store**, jamais des affirmations sur les maxima du protocole Solana : ```text logical code UTF-8 <= 128 bytes canonical RAW payload <= 16 MiB source payload size meta <= 64 MiB Unix timestamp <= 9999-12-31T23:59:59.999Z ``` `RawPayload` ne calcule pas lui-même le digest et ne vérifie pas le contenu du format. Le producer/converter propriétaire du format KSP doit fournir des bytes déjà canoniques et leur digest déterministe. Cette séparation permet de conserver `ksp-store-api -> ksp-core-lib` uniquement. Le payload et `RawTransaction` ne sont volontairement pas `Clone` dans cette foundation afin de ne pas encourager des copies implicites d'un document RAW potentiellement volumineux. Les références, signatures, digests, timestamps et observations compactes restent clonables/copiables lorsqu'approprié. ## 8. Future décomposition N1 -> niveau STRUCTURAL Le nom de travail de N2 devient **STRUCTURAL**. `CORE` est abandonné dans le nouveau plan parce qu'il décrivait mal une opération qui consiste principalement à décomposer des données Solana brutes en sous-composants génériques. La progression conceptuelle devient : ```text N1 RAW -> N2 STRUCTURAL lorsque le type est réellement décomposable -> N3 DECODED ultérieur -> N4 DOMAIN ultérieur ``` Cette progression n'est **pas une chaîne obligatoire pour toutes les familles N1** : ```text RawTransaction -> STRUCTURAL -> DECODED -> DOMAIN RawAccountState -> peut aller directement vers un decoder futur si aucune décomposition N2 utile n'existe realtime event -> worker/analyser, sans N2 nécessaire status/slot/vote -> peut rester un fait/event terminal ``` Le premier et seul cas N2 certain aujourd'hui est `RawTransaction`. Le futur flux transactionnel doit permettre notamment : ```text RawTransaction -> StructuralTransaction/message -> account keys/références -> StructuralInstruction top-level #0 -> StructuralInstruction top-level #1 -> inner/CPI instruction #1.0 -> inner/CPI instruction #1.1 -> transaction logs/meta/balances/return data applicables ``` Chaque instruction/CPI doit posséder une identité/path stable et être traitable indépendamment. Un decoder absent ou défaillant pour une instruction ne bloque jamais les autres unités structurales de la transaction. N2 n'est toutefois pas défini comme « transaction split » : si une autre famille N1 démontre plus tard une vraie décomposition structurelle utile, elle peut rejoindre ce niveau sans changer sa responsabilité. N3 DECODED et N4 DOMAIN ne sont pas conçus dans `0.3.1`. Le plan doit seulement garantir que le RAW transactionnel conserve tout ce qui sera nécessaire à une décomposition STRUCTURAL lossless pour les futurs decoders. `0.3.1` ne crée aucun type STRUCTURAL et ne dépend pas de `ksp-program-api`. ## 9. Provenance commune La provenance candidate doit pouvoir représenter sans secret : ```text network/cluster provider code endpoint logical id optionnel transport/protocol code acquisition method origin live/backfill/import/replay/repair commitment optionnel capture/session/filter logical ids optionnels observed/received timestamp source payload size/hash optionnels ``` Interdits : ```text endpoint URL DSN API key token/password payload source complet par simple diagnostic ``` Les codes provider/protocol/method restent ouverts et bornés ; aucun enum provider fermé n'est introduit dans Store API. `pre.003` matérialise cette politique avec `RawProvenanceCode`, réutilisé pour les codes logiques provider/protocol/method/endpoint/commitment/session/filter. Les valeurs sont non vides, bornées et limitées à un alphabet logique sûr ; une URL contenant `/`, des contrôles ou des espaces ne peut donc pas être stockée accidentellement dans ces champs. Cette validation n'autorise pas le caller à y placer un secret alphanumérique : le contrat reste explicitement « logical code only ». `RawAcquisitionProvenance::new(...)` ne prend que les champs obligatoires ; les informations optionnelles sont ajoutées par builders dédiés. `observed_at`, lorsqu'il existe, ne peut pas être postérieur à `received_at`, et le payload source n'est jamais conservé, seulement sa taille bornée et/ou un digest optionnel. ## 10. Identité et idempotence ### 10.1 Principes L'API ne fournit pas de méthode `has_*` à appeler avant un write idempotent. Le backend doit recevoir une opération unique avec ces sémantiques : ```text clé déjà absente -> Inserted même clé + même contenu canonique -> AlreadyPresent même clé + contenu différent -> Conflict ``` Cela évite un check-then-insert race. ### 10.2 Transactions La clé logique d'un `RawTransaction` est dérivée au minimum du réseau et de la signature canonique. Un hash du contenu canonique protège contre une collision logique avec contenu divergent. La signature ne doit pas être représentée comme un identifiant SQL `i64` dans l'API. ### 10.3 Observations Chaque observation possède une clé d'idempotence déterministe fournie par le producer/composition selon un contrat documenté. Deux acquisitions légitimes distinctes peuvent donc être conservées même si elles pointent vers le même RAW. ### 10.4 Account states La référence matérialisée par `pre.004` est : ```text RawAccountStateReference network pubkey slot canonical state hash ``` Le hash couvre conceptuellement l'état canonique complet et sert à distinguer/converger les écritures multiples possibles dans un même slot sans dépendre de `write_version`. Le Store API ne calcule pas ce digest et ne transforme pas les bytes de transport. Une `RawAccountObservation` possède sa propre `RawObservationKey`; plusieurs acquisitions HTTP/WS/gRPC peuvent donc viser la même référence d'état sans être fusionnées comme observations. ### 10.5 Événements et autres familles Les événements realtime non persistés n'ont pas d'identité Store à inventer. Si une autre famille persistante est admise ultérieurement, son identité d'idempotence doit être définie avec le modèle concret et ne jamais dépendre d'une primary key backend. Aucune promesse exactly-once distribuée n'est faite. ## 11. API candidate ### 11.1 Contrats communs `ksp-store-api` définit le modèle persistant et les opérations backend-agnostic qu'une implémentation doit satisfaire. `0.3.1` ne construit pas de backend, ne sélectionne aucun moteur et n'introduit pas encore la façade runtime concrète `Store`. Le backend concret implémente des contrats publics d'extension, mais son modèle interne reste privé. À partir de `0.3.2`, `ksp-store-lib::Store` enveloppera progressivement ces contrats et deviendra la seule façade de consommation normale des jobs/workers/apps. La conformance PostgreSQL des familles RAW est ensuite matérialisée par slices en `0.3.3` puis `0.3.4`. ### 11.2 Object-safety et async La future sélection runtime impose que les capabilities backend puissent être stockées derrière une abstraction dynamique sans connaître le moteur concret. La stratégie candidate est : ```text capabilities Send + Sync méthodes object-safe futures boxed KSP-owned via un alias StoreApiFuture<'a, T> future ksp-store-lib::Store compose les capabilities disponibles ``` Cette stratégie évite une dépendance `async-trait` uniquement pour masquer la transformation. Le coût d'une box de future est accepté au niveau Store, dominé par l'I/O de persistence, et doit rester mesurable si un chemin futur démontre le contraire. ### 11.3 Capabilities séparées Éviter un trait monolithique exigeant tous les types de données à chaque backend. `pre.005` matérialise une composition fine par famille et par direction : ```text RawTransactionRead RawTransactionWrite RawTransactionObservationRead RawTransactionObservationWrite RawAccountStateRead RawAccountStateWrite RawAccountObservationRead RawAccountObservationWrite ``` Les capabilities d'écriture distinguent deux usages : ```text persist_raw_*_acquisition(raw, observation) = création/admission atomique du RAW + observation record_raw_*_observation(observation) = acquisition supplémentaire d'un RAW déjà persistant = ne retransmet pas le payload volumineux ``` Les traits sont `Send + Sync`, dyn-compatible et retournent `StoreApiFuture<'a, T>`, alias KSP basé uniquement sur `Pin>`. Un backend externe peut donc les implémenter sans `async-trait`, `tokio`, `ksp-store-lib` ou crate backend officielle. `StoreHealth`, les listes/queries et les outcomes détaillés ne sont pas artificiellement introduits dans cette tranche. `pre.006` finalise les résultats d'écriture et le lifecycle logique avant fermeture de l'API. Un modèle event-only ne crée aucune capability Store par défaut. Aucun trait monolithique `StoreBackend` n'est introduit : un backend peut implémenter uniquement les familles réellement supportées. La future façade `ksp-store-lib::Store` peut exposer seulement les capabilities réellement compilées/supportées et produire une erreur stable lorsqu'une opération demandée n'est pas disponible. ### 11.4 Opérations atomiques métier Aucun handle de transaction SQL/public n'est exposé. La surface matérialisée par `pre.005` commence par les opérations unitaires nécessaires : ```text get_raw_transaction(reference) persist_raw_transaction_acquisition(transaction, observation) get_raw_transaction_observation(observation_key) record_raw_transaction_observation(observation) get_raw_account_state(reference) persist_raw_account_acquisition(state, observation) get_raw_account_observation(observation_key) record_raw_account_observation(observation) ``` Les opérations `persist_raw_*_acquisition` signifient au contrat que le RAW et son observation réussissent atomiquement ou échouent ensemble. PostgreSQL réalisera cela avec une transaction privée dans la slice `RawTransaction` `0.3.3`, puis avec le même invariant pour `RawAccountState` en `0.3.4`; un autre backend utilisera son mécanisme natif. Les opérations `record_raw_*_observation` supposent que la référence RAW ciblée existe déjà et permettent de retenir une acquisition supplémentaire sans retransmettre la donnée RAW complète. Les listes, queries et outcomes détaillés restent à finaliser dans `pre.006`; le backlog métier reste hors Store API et appartiendra au futur processing/job layer. Le health runtime reste une responsabilité de la future façade `ksp-store-lib` en `0.3.2`. Aucune transaction backend publique n'est nécessaire pour exprimer les opérations Store. ### 11.5 Outcomes `pre.006` matérialise un vocabulaire commun d'idempotence : ```text RawEntityWriteOutcome Inserted AlreadyPresent Rehydrated SkippedPurged RawObservationWriteOutcome Inserted AlreadyPresent NotRecorded RawAcquisitionWriteOutcome entity + observation ``` `Inserted` et `AlreadyPresent` sont des succès idempotents. Une même identité logique accompagnée d'un contenu divergent produit `ERROR_CODE_RAW_CONFLICT`; aucun backend ne peut résoudre ce cas par overwrite silencieux. Pour `RawTransaction`, `Normal` et `ForceRehydrate` sont deux modes explicites : un tombstone purgé provoque `SkippedPurged` en mode normal ; seul `ForceRehydrate` autorise une réhydratation compatible. Le mode forcé n'annule pas les règles de conflit de contenu/format. ### 11.6 Pagination et queries Le Store fournit un mécanisme de navigation, **pas une policy d'exécution**. `pre.006` introduit : ```text RawPageLimit > 0 aucun maximum fonctionnel KSP arbitraire RawPageCursor opaque <= 4 KiB pour borner le token hostile RawPageRequest RawPage RawSlotRange RawSortDirection RawTransactionQuery RawAccountStateQuery ``` Une requête de `5_000_000` éléments n'est pas ramenée silencieusement à `500`, `1000` ou une autre policy Store. Si un backend ne peut physiquement servir qu'une partie de la demande en une opération, il peut retourner cette partie avec un cursor de continuation ou une erreur backend réellement liée à sa capacité ; `ksp-store-lib` ne doit pas inventer un plafond inférieur par prudence. Le cursor est un token d'implémentation retourné puis réinjecté tel quel par le consumer. Son contenu ne devient pas API, ne transporte jamais un fragment SQL public et sa borne de 4 KiB protège l'admission du token sans limiter le nombre de résultats. Les queries `0.3.1` décrivent seulement des critères de données (network, slot range, compte optionnel, ordre, page). Elles ne définissent pas de backlog métier, de taille de batch worker, de priorité ou de policy executor. Le futur processing ledger/job layer décidera quoi traiter et dans quel volume. ## 12. Références, événements et cycle de vie N1 ### 12.1 Références durables `ksp-store-api` doit posséder les références compactes nécessaires aux lectures, au replay et aux relations entre couches, sans encoder une PK SQL. Pour la première famille : ```text RawTransactionReference network signature ``` Les autres références sont ajoutées uniquement avec leur modèle réel. ### 12.2 Événements : ownership hors Store runtime Le Store runtime persiste et lit. Il ne possède ni scheduler, ni event bus, ni mécanisme de publication et ne doit pas s'appuyer sur une notification native de base de données pour piloter le pipeline. Les règles `KSP-NOTIFY-001..006` restent applicables, mais `0.3.1` n'ajoute **aucun type de notification dédié**. Les références durables déjà exposées par `ksp-store-api` fournissent l'identité compacte nécessaire à un futur contrat de wake-up si un publisher/consumer réel le justifie. La base et `ksp-store-lib` ne publient pas eux-mêmes le signal. Le futur ordre d'orchestration est : ```text worker/acquisition -> Store.persist(...) -> succès/commit durable -> publisher worker/analyser/runtime -> notification canonique de référence si nécessaire ``` La notification persistée-data reste un wake-up et jamais la source de vérité du backlog. Les messages realtime d'acquisition (`logsSubscribe`, slot/vote, etc.) suivent la frontière §4.6 : `ksp-interface-lib` est le owner préféré lorsqu'ils ne représentent aucune donnée Store persistée. ### 12.3 Preuves de processing versionnées Un simple champ : ```text processed = true ``` n'est pas une preuve durable suffisante. L'audit kbot2/kbot3 confirme l'intérêt d'un ledger version-aware identifié au minimum par : ```text stage processor identity/name processor version input identity input hash/version terminal status ``` Le contrat concret de processing sera introduit avec les jobs/couches concernés, pas dans la foundation RAW par anticipation. En revanche, les modèles N1 et leur rétention doivent rester compatibles avec ce futur ledger et avec le force replay. ### 12.4 Rétention, compression, archivage et purge N1 ne doit pas imposer la conservation éternelle du payload RAW en stockage chaud. Le cycle logique candidat, repris puis redessiné depuis kbot2, est : ```text Full -> Compacted (optionnel) Full/Compacted -> Archived -> Purged ``` Ces états décrivent **la disponibilité du payload**, pas le succès des processors supérieurs. La décision d'éligibilité appartient à un futur worker/job/maintenance policy qui vérifie les preuves de processing nécessaires. Le Store applique une transition bornée/atomique mais ne décide pas lui-même qu'une transaction est suffisamment décodée. Une policy typique pourra exiger par exemple : ```text STRUCTURAL courant réussi pour le hash RAW courant unités structurales dans un état terminal selon la coverage policy active horizon de rétention minimal atteint aucun traitement/replay explicitement en cours archive durable confirmée si la policy exige une archive avant purge ``` L'expression « toutes les instructions sont décodées » ne peut pas être un absolu open-world : un nouveau decoder peut apparaître plus tard. Les états terminaux doivent donc être évalués par rapport à une **coverage/version policy explicite**. Une instruction marquée `no_decoder/unsupported` peut rester redécodable ultérieurement depuis N2 STRUCTURAL sans exiger le RAW si N2 a conservé les faits nécessaires. La purge physique du payload ne supprime pas l'identité logique. Un tombstone minimal durable doit rester afin d'empêcher un backfill normal de réacquérir indéfiniment une transaction déjà traitée : ```text network transaction signature / identité logique slot lorsqu'il est connu canonical format id/version content hash retention state processing/retention provenance minimale nécessaire ``` Comportement attendu : ```text backfill normal + tombstone -> skip / AlreadyKnownPurged backfill explicitement forcé -> réhydratation autorisée selon policy ``` Le tombstone ne doit pas prétendre remplacer le RAW pour replay. Si une nouvelle version STRUCTURAL a réellement besoin du document RAW purgé, une réhydratation forcée depuis une archive ou le réseau est nécessaire. La policy est **spécifique à chaque famille**. `RawAccountState`, qui pourrait aller directement vers un decoder futur, ne peut pas réutiliser automatiquement la même politique de purge que `RawTransaction`. `0.3.1` doit préparer les contrats logiques de rétention/tombstone sans implémenter compression, stockage d'archive, cron/worker de purge ou maintenance PostgreSQL. ## 13. Health et diagnostics — reportés au runtime Store `0.3.1` n'expose aucun `StoreHealth` dans `ksp-store-api`. Le health/readiness est un état runtime de la façade et des backends, pas une propriété nécessaire des modèles RAW persistants. `0.3.2` devra cadrer dans `ksp-store-lib` une projection portable uniquement si les consumers runtime en ont réellement besoin ; les backends pourront fournir leurs diagnostics privés derrière cette projection. Ne jamais exposer dans l'API commune : ```text migration SQL state pool handles connection count détaillé par driver server error brut DSN SQLSTATE arbitraire présenté comme contrat portable ``` Les diagnostics spécifiques à PostgreSQL appartiennent à `ksp-store-postgres-lib`. Si `ksp-store-lib` expose un health commun en `0.3.2`, il ne projette que les états réellement portables et sanitised. ## 14. Dependency graph `0.3.1` Graphe maximal par défaut : ```text ksp-store-api └── ksp-core-lib ``` Aucune dépendance externe n'est nécessaire pour la foundation candidate : ```text pas de serde pas de chrono pas de tokio pas de async-trait pas de futures-util pas de ksp-interface-lib tant qu'un type Interface réel n'est pas requis ``` Les timestamps persistants peuvent être représentés par un newtype KSP en unités Unix explicites plutôt que d'ajouter `chrono` par réflexe. Le choix exact est prouvé dans la tranche de modèle. ## 15. Audit historique kbot3 ### 15.1 Surface auditée L'archive historique a été extraite et les surfaces prescrites ont été relues, notamment : ```text ks-store/Cargo.toml ks-store/README.md ks-store/USAGE.md ks-store/TODO.md ks-store/src/lib.rs ks-store/src/store.rs ks-store/src/contracts/** ks-store/src/postgres/** ks-store/migrations/postgres/** config/store.config.json config/schemas/store.config.schema.json ks-config/src/store.rs docs/architecture/STORAGE_ARCHITECTURE.md docs/guides/POSTGRES_STORAGE.md docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md olddocs/archivekbot2/docs/RAW_STORAGE_LIFECYCLE.md olddocs/archivekbot2/docs/CORE_EXTRACTION_SELECTION_AND_REPLAY.md olddocs/archivekbot2/docs/CORE_EXTRACTION_CONTRACTS.md ``` Le Store historique couvre 16 tables N1-N3, 240 ressources SQL atomiques et 79 index attendus. Cette surface confirme l'intérêt de la séparation façade/backend, de l'idempotence et des observations, mais elle est beaucoup trop large pour `0.3.1`. L'archive contient aussi les documents kbot2 historiques. Ils avaient déjà formalisé deux idées utiles : un cycle RAW `full/compacted/archived/purged` et un processing ledger versionné pour décider skip/replay. KSP reprend les concepts mais rejette le couplage à un simple `processing_state` global et redessine la rétention comme une policy externe au Store. ### 15.2 Matrice d'héritage | Concept historique | Observation kbot2/kbot3 | Décision | Application KSP | |------------------------------------------|-------------------------------------------------------------------|------------|-------------------------------------------------------------------------------------------------------------------| | séparation façade Store / PostgreSQL | PostgreSQL et driver privés | REPRENDRE | API commune séparée ; paire façade/backend ouverte en `0.3.2` puis complétée par slices `0.3.3`/`0.3.4` | | `StoreOpenOptions` / backend selection | backend + options historiques partiellement opaques | REDESSINER | Config produit les settings ; `ksp-store-lib` sélectionne un backend compilé | | health/readiness | contrat backend-neutral présent | REPORTER | health runtime portable à cadrer dans la fondation `ksp-store-lib` `0.3.2`; aucun `StoreHealth` dans Store API | | transaction canonique source-independent | HTTP/WS/gRPC convergent vers une transaction canonique | REPRENDRE | `RawTransaction` commun seulement si la source satisfait le contrat complet | | acquisition observations | transaction + account observations séparées du RAW | REPRENDRE | concept N1 central ; transaction d'abord, account prévu après matrice de compatibilité | | ancienne raw WS notification | kbot2 l'a supprimée de la baseline persistante | REPRENDRE | `logsSubscribe`/events restent event-only par défaut ; pas de table de notification brute | | logs transactionnels | conservés puis extraits dans la couche Core historique | REDESSINER | restent dans `RawTransaction`, puis deviennent des unités N2 STRUCTURAL ; pas de `RawLog` persistant séparé | | account observations/states | N1 account observation + N2 account state existaient | REDESSINER | `RawAccountState`/observation stabilisés en N1 ; N2 seulement si une décomposition réellement utile est démontrée | | repository traits | capabilities async séparées | REPRENDRE | capabilities read/write fines, aucune obligation de supporter toutes les familles | | pagination | cursor opaque historique | REDESSINER | limite caller > 0 sans plafond métier KSP ; cursor hostile borné uniquement | | replay contracts | sélection + force replay version-aware | REPRENDRE | futur replay piloté par processor/version/input hash, jamais par simple bool | | `processing_state` RAW | `received/core_extracted/failed` facilitait la queue | REDESSINER | peut servir d'index de travail futur mais ne constitue jamais la preuve durable unique de traitement | | processing ledger | stage + processor/version + input/hash + status | REPRENDRE | future preuve durable versionnée ; reportée aux couches/jobs concernés | | retention lifecycle | `full/compacted/archived/purged` déjà envisagé dans kbot2 | REDESSINER | états logiques backend-agnostic + policy externe + tombstone anti-rebackfill | | CORE tables | transaction, keys, instructions, CPI, logs, balances, return data | REDESSINER | future couche N2 renommée STRUCTURAL ; aucune surface créée en `0.3.1` | | DECODE coverage/events | états `decoded/ignored/unsupported/failed` versionnés | REPORTER | future N3 ; utile à la policy de rétention mais pas défini dans la foundation RAW | | materialization journal | sorties génériques versionnées | REPORTER | future N3/N4 | | raw transaction table | table physique unique par signature | REPORTER | modèle commun maintenant ; table privée à `ksp-store-postgres-lib` en `0.3.2` | | schema validation / migrations / indexes | forte surface PostgreSQL | REPORTER | responsabilité `ksp-store-postgres-lib` | | maintenance truncate/drop | scripts destructifs dédiés | REJETER | aucune API runtime générique destructive ; rétention contrôlée distincte | | Config Store | backend/options historiques | REDESSINER | Config reste owner ; URI/secrets adaptés vers `ksp-store-lib` en `0.3.2` | | modèles partagés hors Store | kbot3 recommandait de ne pas dupliquer les modèles communs | REPRENDRE | frontière explicite `ksp-interface-lib` / `ksp-store-api`, conversion explicite lorsque les sémantiques diffèrent | ## 16. Audit PostgreSQL et driver de référence futur ### 16.1 Décision Le choix opérateur est fixé pour la version suivante : ```text ksp-store-lib façade/runtime commun feature postgres = feature par défaut feature postgres -> ksp-store-postgres-lib ksp-store-postgres-lib backend officiel de référence = PostgreSQL driver interne = tokio-postgres ``` Ce choix n'ajoute aucune dependency à `0.3.1`. ### 16.2 État externe audité au 28 août 2026 Sources primaires actuelles consultées : documentation PostgreSQL et documentation/package `tokio-postgres`. Résultats : ```text PostgreSQL stable courant : 18.6, publié le 13 août 2026 tokio-postgres courant : 0.7.18, publié le 12 juin 2026 edition tokio-postgres : Rust 2024 MSRV tokio-postgres : Rust 1.85 runtime async : Tokio prepared statements : supportés transactions / isolation : supportées COPY in/out : supporté pipelining : supporté TLS : connector externe, à choisir explicitement en 0.3.2 ``` `tokio-postgres::Config` peut être construit et renseigné programmaticalement. `0.3.2` devra éviter les chemins de configuration implicites : l'URI/les options effectives viennent de `ksp-config-lib` via les settings publics de `ksp-store-lib`, puis sont converties vers le backend PostgreSQL sans lecture directe de `.env`, `PG*` ou `.pgpass`. ### 16.3 SQLx SQLx `0.9.0` a été réaudité comme alternative mais n'est plus retenu après décision opérateur. La comparaison a néanmoins confirmé l'intérêt d'éviter dans KSP les defaults de connexion influencés par `PG*`/`.pgpass` lorsque le backend peut être construit explicitement depuis Config. La performance ne constitue pas à elle seule la raison du choix : `0.3.2` devra optimiser les vrais chemins KSP avec prepared statements, batching/COPY lorsque justifié, transactions et indexes adaptés. ## 17. Contrat attendu de `0.3.2` sans le concevoir physiquement ici `0.3.2` introduira **deux crates dans la même release** : ```text ksp-store-lib ksp-store-postgres-lib ``` `ksp-store-lib` devra : ```text dépendre de ksp-store-api réexporter la surface API commune nécessaire aux consumers exposer la façade Store et son lifecycle posséder la sélection/dispatch des backends compilés activer postgres par défaut importer ksp-store-postgres-lib seulement sous feature postgres accepter les settings effectifs fournis par Config rejeter distinctement un backend connu mais non compilé ne jamais exposer les types internes d'un backend ``` `ksp-store-postgres-lib` devra implémenter **le même modèle objet et les mêmes contrats** de `ksp-store-api` et pourra créer librement : ```text PostgresStoreBackend Postgres*Row privés migrations schema version prepared statements indexes pool transaction handles privés mapping Rust API <-> PostgreSQL tokio-postgres ``` Les consumers jobs/workers/apps dépendront uniquement de `ksp-store-lib`, avec la feature par défaut ou une sélection explicite de features. Les crates backend alternatives dépendront de `ksp-store-api`, pas de `ksp-store-lib`. La compatibilité PostgreSQL devra être testée par round-trip des modèles API et par introspection/validation du schema réel. ## 18. Sorties PostgreSQL du prompt initial explicitement déplacées Le prompt de démarrage demandait encore à `pre.001` un schéma PostgreSQL candidat, une migration policy et un gate PostgreSQL live parce que `0.3.1` devait initialement contenir API **et** implémentation. La décision de split rend ces sorties prématurées. Elles sont donc classées ainsi : | Sortie initialement demandée | Décision `pre.001` | Release propriétaire | |------------------------------------------------|--------------------|--------------------------------| | tables/colonnes/PK/FK/indexes RAW | REPORTER | `0.3.2 ksp-store-postgres-lib` | | mapping `u64`/bytes/timestamps vers PostgreSQL | REPORTER | `0.3.2` | | migration layout/version/checksum | REPORTER | `0.3.2` | | schema introspection/conformance | REPORTER | `0.3.2` | | pool/TLS/timeouts | REPORTER | `0.3.2` | | PostgreSQL live gate | REPORTER | `0.3.2` | | SQL injection/statement policy exécutable | REPORTER | `0.3.2` | Les invariants API qui contraignent déjà ce futur schéma restent fixés maintenant : ```text identités logiques non dépendantes d'une PK SQL idempotence Inserted/AlreadyPresent/Conflict atomicité RAW + observation pagination cursorisée sans plafond métier KSP payload versionné et borné provenance sans secret round-trip exact du modèle objet commun ``` Aucun nom de table candidat n'est donc produit par `0.3.1-pre.001`. C'est une conséquence volontaire du split, pas une omission du gate. La trajectoire roadmap corrigée est : ```text 0.3.1 ksp-store-api RAW/observations 0.3.2 ksp-store-lib + ksp-store-postgres-lib 0.3.3 wires Interface nécessaires à l'acquisition/normalisation future 0.3.4 ksp-job-api + premier backfill 0.3.5 application backfill/inspection RAW ``` ## 19. Threat model `0.3.1` | Menace | Réponse de design | |--------------------------------|--------------------------------------------------------------------------------------| | payload hostile trop grand | bornes à la construction des contrats RAW avant persistence | | raw payload leak via `Debug` | `Debug` manuel/redacted pour conteneurs sensibles | | secret provider/endpoint | provenance = ids/codes sûrs, jamais URL/credential | | source partielle | ne produit pas un modèle canonique incomplet ; autre model/event ou Transport only | | mismatch HTTP/WS/gRPC | matrice d'admission source -> modèle avant stabilisation de chaque famille | | duplicate race | write idempotent atomique, jamais `has_*` comme précondition | | même clé / contenu divergent | conflit explicite, pas d'overwrite silencieux | | partial RAW + observation | opération `persist_*_acquisition` atomique au contrat | | cursor hostile | taille/limit bornées ; cursor opaque non interprété par consumer | | query non bornée | aucune liste publique sans `PageRequest` borné | | backend leak | aucun type driver/row/table/SQL dans la façade | | event persistence creep | event-only != capability Store ; owner Interface préféré si non persistant | | stale `processed = true` | future preuve stage+processor/version+input hash, pas un bool comme source de vérité | | purge prématurée | policy externe + preuves versionnées + transition atomique | | re-backfill après purge | tombstone minimal durable ; réhydratation uniquement en mode forcé | | purge casse nouveau structural | archive/réseau forcé nécessaire si nouvelle version N2 exige le RAW | | closed-world backend | contrat implémentable hors `ksp-store-lib` | | closed-world RAW family | modèles/capabilities séparés, ajoutables sans JSON fourre-tout | | scope creep N2/N3/N4 | aucun type STRUCTURAL/DECODED/DOMAIN implémenté dans `0.3.1` | Les menaces DSN/SQL injection/schema drift/migrations restent documentées pour `0.3.2`, où elles deviennent exécutables. Elles ne justifient pas des types SQL dans l'API. ## 20. Stratégie de tests `0.3.1` Prévoir : ```text unit tests module-local public API canaries crate-root external backend canary object-safe backend contract canary manifest/dependency firewall exact public export inventory exact production module inventory bounds payload/provenance/cursor Debug/redaction tests idempotence outcome semantics transaction identity validation source-to-model admission matrix canaries transaction logs remain owned by RawTransaction before STRUCTURAL extraction account/status model compatibility canaries lorsqu'introduits event-only model does not imply Store capability atomic-operation contract fake backend pagination cursor/zero-limit admission sans plafond métier arbitraire retention state/tombstone transition invariants normal backfill skips purged tombstone; forced rehydrate remains explicit processing proof is not represented by a lone boolean release completeness cargo tree -p ksp-store-api --edges normal cargo tree --duplicates ``` Le **backend externe canary** doit implémenter le contrat avec une petite implémentation mémoire de test située hors des modules de production. Il prouve : ```text aucune dépendance à ksp-store-lib aucune connaissance PostgreSQL implémentation des mêmes capabilities API round-trip via le contrat backend commun ``` Aucun PostgreSQL live test n'appartient à `0.3.1` puisque le backend PostgreSQL n'existe pas encore. ## 21. Ownership | Concept | Owner candidat/fixé | Visibilité | Introduction | Raison | |--------------------------------------|-------------------------------|----------------------------|-------------------------------------|------------------------------------------------------------------| | `RawTransaction` | `ksp-store-api` | public | `0.3.1` | modèle persistant/replayable commun | | `RawTransactionObservation` | `ksp-store-api` | public | `0.3.1` | acquisition durable distincte du RAW | | transaction log messages RAW | dans `RawTransaction` | public via modèle RAW | `0.3.1` | font partie de la transaction avant décomposition | | structural transaction log lines | future couche N2 | hors `0.3.1` | série STRUCTURAL | unités extraites après décomposition | | `RawAccountState` | `ksp-store-api` | public | `0.3.1` | état canonique commun HTTP/WS/gRPC sous admission stricte | | `RawAccountObservation` | `ksp-store-api` | public | `0.3.1` | provenance/acquisition de l'état compte | | `TransactionStatusObservation` | non fixé | absent | reporté | HTTP snapshot, WS transition et Yellowstone update non fusionnés | | log/status/slot/vote event-only | préférer `ksp-interface-lib` | public passif | future tranche Interface si retenue | échange inter-composants sans persistence Store | | `RawRetentionState` / tombstone | `ksp-store-api` | public logique | `0.3.1` | cycle de vie RAW backend-agnostic stabilisé | | processing ledger versionné | future contrat Store commun | hors surface initiale RAW | avec jobs/N2/N3 | preuve durable stage/version/input, pas bool | | RAW references | `ksp-store-api` | public | `0.3.1` | reads/replay/lineage | | write outcomes | `ksp-store-api` | public | `0.3.1` | sémantique idempotente commune | | query/page | `ksp-store-api` | public | `0.3.1` | navigation/replay backend-agnostic | | health runtime | `ksp-store-lib` | hors `0.3.1` | `0.3.2` à cadrer | état runtime, non requis par les modèles RAW | | backend capabilities | `ksp-store-api` | public | `0.3.1` | implémentations externes | | `Store` facade | `ksp-store-lib` | public | `0.3.2` | point de consommation commun | | backend dispatch/settings | `ksp-store-lib` | public/privé selon contrat | `0.3.2` | features disponibles + sélection Config | | PostgreSQL rows/pool/SQL/migrations | `ksp-store-postgres-lib` | privé/opérateur | `0.3.2` puis `0.3.3`/`0.3.4` | fondation puis schémas RAW par slice | | transport -> RAW conversion | composition/pipeline futur | privé/réutilisable | release acquisition | conversion explicite, jamais dépendance inverse | | RAW -> STRUCTURAL | future pipeline générique | hors `0.3.1` | série suivante | aucune dépendance Program | | event runtime / scheduler / analyzer | worker/analyser/runtime futur | hors Store | ultérieur | Store ne notifie pas lui-même | | retention policy/maintenance worker | job/worker/opérateur futur | hors Store API décisionnel | ultérieur | décide l'éligibilité ; Store applique la transition | ## 22. Hors scope strict `0.3.1` ```text ksp-store-lib PostgreSQL runtime tokio-postgres dependency pool/TLS PostgreSQL SQL/tables/indexes/migrations std.store Config backend MySQL/SQLite/Oracle/RocksDB/ClickHouse Transport acquisition runtime conversion Transport -> RAW fonctionnelle N2 STRUCTURAL structs et persistence RAW -> STRUCTURAL processor N3 DECODED N4 DOMAIN/materialization processing ledger concret worker/job/backfill application Store/RAW event bus/scheduler/analyser runtime compression algorithm concret archive backend concret retention cron/worker automatique truncate/drop/maintenance API générique ``` Les **contrats logiques** de rétention/tombstone N1 et la compatibilité avec un futur processing ledger peuvent en revanche être définis dans `ksp-store-api` sans implémenter ces mécanismes. ## 23. Prévision souple recalibrée `0.3.1` Chaque tranche vise environ 15–20 minutes de travail effectif. ### `pre.001` — Audit, taxonomie N1 et split API/backend Base, règles, architecture, audit kbot2/kbot3, audit PostgreSQL/driver futur, frontière Interface/Store, classification persistence/event/Transport-only, rétention/tombstones, ownership, threat model, tests et sizing. ### `pre.002` — Scaffold `ksp-store-api` + taxonomie Créer uniquement la crate API, manifest Core-only, façade crate-root et modules de modèle/capability sans dépendance backend. Verrouiller le vocabulaire RAW/STRUCTURAL et la séparation model/capability. ### `pre.003` — Primitives RAW communes + transaction Introduire payload/reference/provenance/idempotence/timestamps bornés puis `RawTransaction` et son observation. Les logs inclus restent dans la transaction. ### `pre.004` — Matrice cross-source + familles N1 prévues Audit HTTP/WS/gRPC matérialisé. `RawAccountState`/observation sont admis avec bytes complets + slot et enrichissements source-specific séparés. `TransactionStatusObservation` est différé car snapshot HTTP, transition WS et update Yellowstone ne convergent pas encore assez. `logsSubscribe`/slot/vote restent event-only candidats, block reste conteneur/IDEA et Entry reste rejeté. ### `pre.005` — Capabilities backend extensibles Matérialiser les contracts read/write object-safe pour transaction/account et leurs observations, l'alias `StoreApiFuture`, ainsi qu'un canari backend externe. Les écritures d'acquisition imposent RAW + observation atomiques et les observations supplémentaires peuvent être enregistrées sans retransmettre le RAW. Aucune façade runtime `Store`, aucun trait monolithique backend et aucun runtime DB. ### `pre.006` — Queries, outcomes et lifecycle RAW Finaliser reads/list cursorisés sans plafond métier arbitraire, outcomes idempotents/conflict, atomic acquisition contract, `RawRetentionState`, tombstone minimal et sémantiques normal-skip/force-rehydrate sans implémenter compression/archive physique ni policy executor. ### `pre.007` — Boundary/adversarial hardening + completeness Payload/cursor/provenance hostile, Interface/Store ownership canaries, admission matrix, retention races, exact exports/modules, external backend, dependency firewall et scope négatif N2/N3/N4. La tranche matérialise aussi le résultat distinct `ExpectedStateMismatch` pour une compare-and-transition de rétention perdue : un backend ne doit confondre ni une course concurrente, ni un état déjà à la cible, ni une transition effectivement appliquée. Les canaris de complétude figent la surface publique N1, les inventaires de modules/capabilities et l'absence de modèles event-only ou STRUCTURAL/DECODED/DOMAIN dans `ksp-store-api`. ### `pre.008` — Gate technique final **Statut : PASS opérateur.** La tranche reste un gate pur : aucun nouveau modèle, capability, test métier, backend, codec, event-only type ou comportement runtime. Elle synchronise uniquement la version workspace et les preuves documentaires du gate final. Le gate opérateur a été exécuté après `cargo clean` : audits Rust/Markdown, `cargo check --workspace`, Clippy, tests ciblés de toutes les crates présentes, `cargo test --workspace`, builds Tauri des trois Desk et graphes Cargo sont verts. La commande ciblée initiale `cargo test -p ksp-program-lib` était un nom de package erroné ; `cargo test -p ksp-program-api` a ensuite été exécuté séparément et passe intégralement. ### `pre.009` — Réconciliation documentaire finale **Statut : matérialisé par `0.3.1-pre.009`.** Le plan, la validation, l'inventaire/graphe d'architecture, les dépendances des futurs consumers Store et `IDEAS.md` sont réconciliés avec la surface réellement livrée. `README.md` ne porte pas d'inventaire Store détaillé et aucun USAGE Store n'existe encore : aucune modification artificielle n'y est ajoutée. Aucun `CHANGELOG.md`, `ROADMAP.md` ni prompt suivant n'est touché. ### `pre.010` — Redécoupage documentaire des futures slices Store/PostgreSQL **Statut : matérialisé par `0.3.1-pre.010`.** Cette tranche rouvre uniquement la responsabilité documentaire de trajectoire après décision opérateur postérieure à `pre.009`. Elle découpe l'ancien `0.3.2` surdimensionné en trois releases où `ksp-store-lib` et `ksp-store-postgres-lib` progressent toujours ensemble : fondation runtime/backend, `RawTransaction`, puis `RawAccountState` + complétude. Aucun `CHANGELOG.md`, `ROADMAP.md` ni prompt suivant n'est touché dans cette tranche afin de respecter la séparation des couloirs de fermeture. ### `pre.011` — Préparation de publication minimale Uniquement : ```text Cargo.toml CHANGELOG.md ROADMAP.md prompts/021-V0_3_2_START_PROMPT.md delta pre.011 ``` Le prompt `0.3.2` ouvre ensemble `ksp-store-lib` et `ksp-store-postgres-lib`, mais **uniquement pour la fondation runtime/backend PostgreSQL**. Les surfaces `RawTransaction` et `RawAccountState` restent réservées respectivement à `0.3.3` et `0.3.4`. ### `rel.001` — Publication stable Mécanique de publication uniquement. La release reste estimée clôturable dans une session si les tranches conservent cette granularité et si aucun processor STRUCTURAL/decoder n'est tiré dans le scope. ## 24. Trajectoire `0.3.x` résultante La prévision durable devient, sous réserve des gates de chaque release : ```text 0.3.1 ksp-store-api / N1 RAW + observations + lifecycle logique 0.3.2 Store/PostgreSQL foundation / runtime, Config, connexion, migrations, health 0.3.3 Store/PostgreSQL RawTransaction / persistence + observations + query + retention 0.3.4 Store/PostgreSQL RawAccountState + complétude/conformance RAW cross-family 0.3.5 ksp-interface-lib additions nécessaires aux événements/acquisitions partagés 0.3.6 ksp-job-api + premier backfill RAW concret 0.3.7 application backfill/inspection RAW ensuite worker/service live RAW avant ouverture N2 STRUCTURAL ``` Les événements realtime non persistés et les modèles Interface peuvent être avancés ou retardés selon le premier consumer réel ; ils ne doivent pas être artificiellement absorbés par Store. Le redécoupage `pre.010` remplace la renumérotation précédente `0.3.2..0.3.5`. Les deux crates `ksp-store-lib` et `ksp-store-postgres-lib` restent développées de pair dans les trois slices `0.3.2`–`0.3.4`; seule la responsabilité fonctionnelle de chaque release est réduite. ## 25. Critères de fermeture de `0.3.1` La stable `0.3.1` est prête seulement si : ```text ksp-store-api existe seule dans le domaine Store RawTransaction + observations stables aucune source partielle ne fabrique un RawTransaction incomplet logs transactionnels restent dans RawTransaction jusqu'à N2 STRUCTURAL RawAccountState/observation audités, modèles et capabilities stabilisés sous admission cross-source stricte TransactionStatusObservation classé explicitement logsSubscribe/slot/vote classés event-only/Interface, reportés ou rejetés explicitement RawBlock reste IDEA sans persistence tant qu'un besoin block-level n'est pas démontré Yellowstone Entry reste explicitement hors taxonomie active frontière ksp-interface-lib / ksp-store-api documentée et sans modèles concurrents models et capabilities séparés contrats backend communs stables backend externe implémentable sans ksp-store-lib aucun type backend/SQL public queries/pagination cursorisée sans plafond métier KSP idempotence/conflict semantics explicites atomic acquisition contract explicite références durables stables RawRetentionState/tombstone empêchent le rebackfill normal après purge force rehydrate reste explicitement distinct aucun simple processed bool n'est présenté comme preuve suffisante de traitement future preuve processing versionnée stage/processor/input reste possible health/readiness runtime et type de wake-up dédié restent explicitement hors `ksp-store-api` `0.3.1` aucune dépendance PostgreSQL aucune surface N2 STRUCTURAL/N3 DECODED/N4 DOMAIN implémentée workspace et graphes entièrement verts prompt 0.3.2 cohérent avec la fondation conjointe ksp-store-lib + ksp-store-postgres-lib + tokio-postgres, sans absorber RawTransaction/RawAccountState ```