# 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 : ```text 0.3.1 = ksp-store-api uniquement 0.3.2 = ksp-store-lib + ksp-store-postgres-lib feature postgres par défaut PostgreSQL de référence via tokio-postgres ``` 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 `0.3.2` puisse ensuite demander : > quelle représentation PostgreSQL satisfait le mieux ce contrat ? 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 / D1 RAW**. La crate doit posséder : ```text modèles objet/struct persistants communs références durables provenance d'acquisition outcomes d'écriture/idempotence queries et pagination bornées health/readiness communs utiles contrats/capabilities backend extensibles format canonique de notification de disponibilité si la référence RAW est stabilisée ``` La façade runtime concrète `Store`, la sélection d'un backend compilé et l'orchestration commune appartiendront à `ksp-store-lib` en `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`. Elle 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 Program decoder Materializer ``` 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` `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` 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 `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 / RawLog / 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. ## 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 / D1 RAW : définition retenue ### 6.1 Rôle N1 est la couche **acquisition persistée / RAW replayable**. Elle ne se limite pas à une table `raw_transactions`. Elle regroupe les faits d'acquisition nécessaires pour rejouer ultérieurement la normalisation générique Solana sans redemander arbitrairement la donnée au provider. La première surface concrète `0.3.1` retient : ```text RawTransaction RawTransactionObservation RawLog RawLogObservation provenance commune références durables communes ``` Les familles suivantes sont explicitement prévues mais reportées tant que leur acquisition/replay exact n'est pas suffisamment auditée : ```text RawAccount / account observation RawBlock RawSlot / slot update si un besoin durable distinct existe autres familles d'acquisition réellement nécessaires ``` Aucun enum fermé ne doit empêcher leur ajout. ### 6.2 Transport n'est pas une famille RAW Les mécanismes suivants sont des **origines d'acquisition**, pas des modèles persistants distincts : ```text HTTP JSON-RPC WebSocket JSON-RPC Yellowstone gRPC provider extension import backfill ``` Interdit : ```text RawHttpTransaction RawWsTransaction RawGrpcTransaction ``` La même transaction logique acquise par HTTP, WS et gRPC doit converger vers un `RawTransaction` commun, avec plusieurs observations/provenances si nécessaire. ### 6.3 Transaction canonique et observation 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 ``` Exemple : ```text RawTransaction T ^ | +---------------+---------------+ | | | HTTP/Helius obs WS/Helius obs gRPC/PublicNode obs ``` La duplication d'observation ne duplique pas automatiquement la transaction canonique. ### 6.4 Raw logs `RawLog` est une famille N1 distincte et n'est pas confondue avec la future décomposition CORE des logs. Elle doit préserver l'observation de logs suffisamment fidèlement pour : ```text conserver l'ordre des lignes conserver slot/signature/identité applicable conserver le statut source nécessaire permettre replay/inspection permettre de relier un log à une transaction lorsqu'elle est connue ``` Une observation `logsSubscribe` peut également servir de déclencheur pour acquérir ensuite la transaction complète. Le modèle de provenance ne doit pas empêcher une lineage future : ```text RawLogObservation -> déclenche acquisition transaction RawTransactionObservation -> RawTransaction ``` Cette relation causale n'est pas un orchestrateur et n'est pas obligatoirement implémentée dans `0.3.1`. ### 6.5 Autres RAW Le design doit permettre d'ajouter plus tard des familles distinctes sans créer un `RawAnything { kind, json }` universel. Le partage se fait par composition de primitives communes : ```text RawAcquisitionProvenance RawPayload RawContentHash / idempotence key RawDataReference PageRequest / Page ``` et non par effacement des sémantiques propres à chaque famille. ## 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. ## 8. Future décomposition N1 -> niveau générique Solana La frontière architecturale actuelle reste nommée : ```text RAW -> CORE -> DECODE -> SPECIALIZED ``` Le nom `CORE` n'est pas considéré comme irréversible. Une revue de nomenclature pourra être faite avant l'ouverture effective de D2 sans modifier les responsabilités déjà fixées. Le rôle de D2 reste en revanche clair : décomposer/normaliser une transaction RAW en faits Solana génériques indépendants des decoders Program. Le futur flux doit permettre notamment : ```text RawTransaction -> transaction/message générique -> account keys -> instruction top-level #0 -> instruction top-level #1 -> CPI/inner instruction #1.0 -> CPI/inner instruction #1.1 -> logs/meta/balances/return data applicables ``` Chaque instruction/CPI doit ensuite pouvoir être traitée indépendamment : ```text instruction générique -> decoder disponible ? oui -> decode non -> Unsupported/NotApplicable durable sans bloquer le reste ``` `0.3.1` ne crée aucun type D2/CORE 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. ## 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 Logs L'identité exacte d'un `RawLog` sera stabilisée avec son modèle concret ; elle doit être déterministe et ne doit pas dépendre d'un 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 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 un contrat public d'extension, mais son modèle interne reste privé. En `0.3.2`, `ksp-store-lib::Store` enveloppera ce contrat et deviendra la seule façade de consommation normale des jobs/workers/apps. ### 11.2 Object-safety et async La future sélection runtime impose que le contrat backend puisse être stocké derrière une abstraction dynamique sans connaître le moteur concret. La stratégie candidate est : ```text StoreBackend: Send + Sync méthodes object-safe futures boxed KSP-owned via un alias StoreFuture<'a, T> future ksp-store-lib::Store possède Arc ``` 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 Éviter un trait de 150 méthodes. La candidate est une composition de capabilities N1 : ```text StoreHealth RawTransactionStore RawLogStore ``` Un contrat composite `StoreBackend` peut agréger les capabilities N1 nécessaires à la future façade `ksp-store-lib::Store`. Les futures couches D2/D3/D4 ne sont pas ajoutées par anticipation. ### 11.4 Opérations atomiques métier Aucun handle de transaction SQL/public n'est exposé. Les invariants multi-écritures sont exprimés par des opérations logiques communes. Candidate : ```text persist_transaction_acquisition(transaction, observation) record_transaction_observation(observation) get_raw_transaction(reference) list_raw_transactions(query, page) list_transaction_observations(query, page) persist_log_acquisition(raw_log, observation) record_log_observation(observation) get_raw_log(reference) list_raw_logs(query, page) list_log_observations(query, page) health() ``` `persist_*_acquisition` signifie au contrat que le RAW et son observation réussissent atomiquement ou échouent ensemble. PostgreSQL réalisera cela avec une transaction privée en `0.3.2`; un autre backend utilisera son mécanisme natif. ### 11.5 Outcomes Le vocabulaire candidat : ```text RawWriteOutcome Inserted AlreadyPresent Conflict = Error KSP stable, pas un succès silencieux ``` Pour une acquisition combinée, l'outcome doit distinguer au minimum l'état du RAW et de l'observation sans révéler de clé backend. ### 11.6 Pagination Le concept historique `PageRequest/PageSlice` est repris avec : ```text default = 100 maximum = 500 cursor opaque et borné ordre déterministe par query ``` Le cursor est un token d'implémentation retourné par la façade et réinjecté tel quel par le consumer. Son contenu ne devient pas API et ne doit pas être utilisé comme transport d'un SQL fragment. ## 12. Références durables et notifications L'ownership `KSP-NOTIFY-001..006` reste à `ksp-store-api`. Le candidat est une référence compacte : ```text RawDataReference Transaction(RawTransactionReference) Log(RawLogReference) futures variantes non exhaustives ``` Puis un signal logique : ```text RawDataAvailable { reference, } ``` Le mécanisme de diffusion est hors scope. Ordre obligatoire : ```text persist commit notify ``` La notification ne transporte pas le payload RAW et ne remplace jamais `list_*`/backlog Store. Si les références concrètes ne sont pas assez stabilisées lors de leur tranche, le type de notification peut être reporté sans déplacer son ownership. ## 13. Health et diagnostics La façade peut exposer un health commun minimal : ```text Unknown Healthy Degraded Unhealthy ``` avec un backend code sûr et un code diagnostic borné si nécessaire. Ne pas 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`. `ksp-store-lib` ne projette vers le contrat commun 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 ``` 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`. ### 15.2 Matrice d'héritage | Concept historique | Observation kbot3 | Décision | Application KSP | |-----------------------------------------|-------------------------------------------------------------------|------------|----------------------------------------------------------------------------------------------------------| | séparation façade Store / PostgreSQL | PostgreSQL et `sqlx::PgPool` privés | REPRENDRE | API commune séparée; façade + backend PostgreSQL reportés à `0.3.2` | | `StoreOpenOptions` / backend selection | backend + `serde_json::Value` opaque dans Store | REDESSINER | Config produit les settings communs; `ksp-store-lib` sélectionne un backend compilé et construit `Store` | | health/readiness | contrat backend-neutral présent | REPRENDRE | health minimal portable dans `ksp-store-api` | | `contracts/dto/raw.rs` | transaction canonique + observations transaction/account | REDESSINER | transaction + logs + provenance commune, sans JSON universel ni lifecycle D2/D3 | | `contracts/entity/raw.rs` | rows exposant `i64`, JSON et timestamps backend-shaped | REDESSINER | entités logiques sans PK SQL ni type physique | | `RawTransactionStore` | `has_*` puis inserts séparés | REDESSINER | write idempotent atomique sans check-then-insert | | repository traits | capabilities async séparées | REPRENDRE | capabilities N1 séparées + façade commune | | pagination | 100 par défaut, 500 max, cursor opaque | REPRENDRE | mêmes bornes candidates, cursor toujours opaque | | replay contracts | filtres mêlés au processing N2/N3 | REPORTER | `0.3.1` fournit seulement lectures RAW nécessaires au futur replay | | error contracts | erreurs Store historiques | REDESSINER | `ksp_core_lib::Error/Result` + codes Store stables | | schema validation / migration bootstrap | validation stricte des objets PostgreSQL | REPORTER | responsabilité `ksp-store-postgres-lib` en `0.3.2` | | migrations atomiques | ressources SQL actives nombreuses | REPORTER | aucune migration dans Store API | | constraints/indexes | 79 index attendus | REPORTER | design physique seulement après stabilisation API | | idempotence / uniqueness | observation keys + canonical identity | REPRENDRE | sémantique publique déterministe; mécanisme backend privé | | raw transaction table | table physique `k_sol_raw_transactions` | REPORTER | `RawTransaction` commun maintenant; table privée à `ksp-store-postgres-lib` en `0.3.2` | | acquisition observations | transaction + account observations | REPRENDRE | concept N1 central; transaction + log initialement | | processing ledger | couvre N2/N3 processing | REPORTER | appartient aux futures couches de processing, pas N1 foundation | | CORE tables | transaction, keys, instructions, CPI, logs, balances, return data | REPORTER | future couche D2, aucune surface en `0.3.1` | | DECODE coverage/events | contrats decoder/materializer | REPORTER | future D3 | | materialization journal | `k_sol_mat_outputs` | REPORTER | future D3 | | maintenance truncate/drop | scripts destructifs dédiés | REJETER | aucune API runtime générique destructive Store | | Config Store | backend + options JSON historiques | REDESSINER | Config reste owner; settings Store/URI secrets adaptés vers `ksp-store-lib` en `0.3.2` | | runtime diagnostics | résumés sanitised mais backend riches | REDESSINER | health portable dans API; détails backend privés | ## 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 bornée 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` | implémentations Debug manuelles/redacted pour conteneurs sensibles | | secret provider/endpoint | provenance ne contient que des ids/codes sûrs, jamais URL/credential | | 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 | | backend error leak | mapping vers erreurs KSP sûres; source externe auditée avant chaînage | | closed-world backend | contrat implémentable hors `ksp-store-lib` | | closed-world RAW family | types/familles extensibles sans JSON fourre-tout | | cross-backend mismatch | conformance suite commune sur chaque implémentation | | scope creep D2/D3/D4 | aucun type CORE/DECODE/SPECIALIZED 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/log identity validation atomic-operation contract fake backend pagination bounds notification reference contract si introduit 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 | Visibilité | Introduction | Raison | |------------------------------|----------------------------|---------------------------------|---------------------|-------------------------------------------| | `RawTransaction` | `ksp-store-api` | public | `0.3.1` | modèle persistant commun | | `RawTransactionObservation` | `ksp-store-api` | public | `0.3.1` | provenance/acquisition distincte du RAW | | `RawLog` | `ksp-store-api` | public | `0.3.1` | seconde famille N1 explicitement demandée | | `RawLogObservation` | `ksp-store-api` | public | `0.3.1` | provenance logs | | `RawPayload` | `ksp-store-api` | public | `0.3.1` | contrat persistant replayable versionné | | RAW references | `ksp-store-api` | public | `0.3.1` | reads, notification, lineage | | write outcomes | `ksp-store-api` | public | `0.3.1` | sémantique idempotente commune | | query/page | `ksp-store-api` | public | `0.3.1` | backlog/replay backend-agnostic | | health portable | `ksp-store-api` | public | `0.3.1` | consumer commun | | backend extension trait | `ksp-store-api` | public | `0.3.1` | implémentation externe réelle | | `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 settings internes | `ksp-store-postgres-lib` | privé ou constructor-boundary | `0.3.2` | construction du backend de référence | | PostgreSQL rows | `ksp-store-postgres-lib` | privé | `0.3.2` | mapping physique | | pool/connection | `ksp-store-postgres-lib` | privé | `0.3.2` | détail runtime | | SQL/statements | `ksp-store-postgres-lib` | privé | `0.3.2` | détail backend | | migrations/schema | `ksp-store-postgres-lib` | privé/opérateur | `0.3.2` | détail backend | | transport -> RAW conversion | composition/pipeline futur | privé/réutilisable selon besoin | release acquisition | `DEP-KSP-003` | | RAW -> D2 normalization | future pipeline générique | hors `0.3.1` | série suivante | aucune dépendance Program | | notification mechanism | futur host/runtime | hors API de diffusion | ultérieur | Store API possède seulement le format | ## 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 D2/CORE structs et persistence RAW -> D2 normalizer Program decode Materializer processing ledger worker/job/backfill application Store/RAW notification transport concret retention/archive/purge lifecycle truncate/drop/maintenance API ``` ## 23. Prévision souple recalibrée `0.3.1` Chaque tranche vise environ 15–20 minutes de travail effectif. ### `pre.001` — Audit, modèle N1 et split API/backend Base, règles, architecture, audit kbot3, audit PostgreSQL/driver futur, RAW transactions/logs, ownership, API candidate, threat model, tests et sizing. ### `pre.002` — Scaffold `ksp-store-api` Créer uniquement la crate API, manifest Core-only, façade crate-root et documentation initiale. Aucun modèle fonctionnel lourd. ### `pre.003` — Primitives RAW communes + transaction Introduire payload/reference/provenance/idempotence/timestamps bornés puis `RawTransaction` et son observation. ### `pre.004` — RAW logs + extensibilité N1 Introduire `RawLog`/observation, vérifier ordering/identity/bounds et verrouiller l'absence de type RAW fourre-tout. ### `pre.005` — Capabilities backend extensibles Implémenter les contrats object-safe et l'external backend canary, sans façade runtime `Store` et sans runtime DB. La façade commune appartient à `ksp-store-lib` en `0.3.2`. ### `pre.006` — Queries, pagination, outcomes et notification reference Finaliser reads/list/backlog bornés, atomic acquisition contract et référence de notification si suffisamment stable. ### `pre.007` — Adversarial/API hardening + completeness Payload/cursor/provenance hostile, Debug, external backend, exact exports/modules, dependency firewall et scope négatif D2/D3/D4. ### `pre.008` — Gate technique final Workspace complet, tests Store API, graphes Cargo. Aucun développement fonctionnel nouveau. ### `pre.009` — Réconciliation documentaire finale README/USAGE, plan, validation et références durables réellement concernées. Aucun `CHANGELOG.md`, `ROADMAP.md` ni prompt suivant. ### `pre.010` — Préparation de publication minimale Uniquement : ```text Cargo.toml CHANGELOG.md ROADMAP.md prompts/021-V0_3_2_START_PROMPT.md delta pre.010 ``` Le prompt `0.3.2` ouvre ensemble `ksp-store-lib` et `ksp-store-postgres-lib`, avec feature `postgres` par défaut et `tokio-postgres` strictement dans la crate backend. ### `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 codec/normalizer D2 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 contract 0.3.2 ksp-store-lib + ksp-store-postgres-lib / PostgreSQL reference via tokio-postgres 0.3.3 generic Interface/wire additions nécessaires à acquisition/normalisation 0.3.4 ksp-job-api + premier backfill RAW concret 0.3.5 application backfill/inspection RAW ensuite worker/service live RAW avant ouverture D2 ``` Cette renumérotation remplace la prévision `0.3.1..0.3.4` de la base `v0.2.14`; `pre.001-fix.001` l'écrit immédiatement dans le `ROADMAP.md` pour éviter de poursuivre avec une trajectoire fausse. ## 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 modèle objet RAW commun stable RawTransaction + observations stables RawLog + observations stables future extension N1 non bloquée contrats backend communs stables backend externe implémentable sans ksp-store-lib aucun type backend/SQL public queries/pagination bornées idempotence/conflict semantics explicites atomic acquisition contract explicite références durables stables notification format introduit ou reporté explicitement aucune dépendance PostgreSQL aucune surface D2/D3/D4 workspace et graphes entièrement verts prompt `0.3.2` cohérent avec ksp-store-lib + ksp-store-postgres-lib + tokio-postgres ```