# Plan `0.3.2` — Store/PostgreSQL runtime foundation ## 1. Objet et base Cette release part exclusivement de la stable : ```text v0.3.1 workspace.package.version = 0.3.1 ``` Le gate d'ouverture est : ```text 0.3.2-pre.001 ``` Les deux archives requises par le prompt sont disponibles et ont été auditées : ```text khadhroony-solana-project-v0.3.1.zip khadhroony-bot3_v0.5.3-pre.005-fix010.zip ``` La metadata Git n'est pas incluse dans l'archive KSP ; le tag `v0.3.1` ne peut donc pas être interrogé localement. Les preuves internes concordantes sont : ```text workspace.package.version = 0.3.1 deltas/0.3.1/rel.001.md présent prompts/021-V0_3_2_START_PROMPT.md présent crates/ksp-store-api présent crates/ksp-store-lib absent crates/ksp-store-postgres-lib absent ``` `ksp-store-api` est conservé comme contrat backend-agnostic publié par `0.3.1`. `0.3.2` ne doit pas le remodeler pour simplifier PostgreSQL. ## 2. Scope de la release `0.3.2` introduit ensemble : ```text ksp-store-lib ksp-store-postgres-lib Config std.store connexion/pool/TLS PostgreSQL moteur privé de migrations/bootstrap diagnostics/health runtime minimaux integration test PostgreSQL réel opt-in ``` La release reste une fondation runtime/backend. Elle n'implémente aucune persistence métier : ```text RawTransaction réservé à 0.3.3 RawAccountState réservé à 0.3.4 CORE / DECODE / D3 / D4 hors scope ``` Aucune table, index, repository ou capability RAW n'est créé uniquement pour démontrer que PostgreSQL fonctionne. ## 3. Divergence normative découverte à l'ouverture La base stable contient une divergence réelle entre les documents les plus récents et deux règles plus anciennes. Les documents déjà cohérents avec la trajectoire `0.3.1` sont : ```text docs/plans/022-V0_3_1_STORE_RAW_PLAN.md docs/architecture/003-COMPONENT_CONTRACTS.md docs/architecture/004-COMPONENT_INVENTORY.md docs/architecture/005-DEPENDENCY_GRAPH.md ``` Ils imposent : ```text ksp-store-lib -> ksp-store-api ksp-store-lib[postgres] -> ksp-store-postgres-lib ksp-store-postgres-lib -> ksp-store-api ksp-store-postgres-lib -X-> ksp-store-lib ``` En revanche, la base `v0.3.1` conservait encore : ```text KSP-API-006 DEP-STORE-002 section ksp-store-lib de docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md ``` avec l'ancien modèle où PostgreSQL était physiquement contenu dans `ksp-store-lib`. `pre.001` réconcilie cette divergence sans changer le scope : ```text KSP-API-006 façade/runtime Store dans ksp-store-lib ; PostgreSQL dans ksp-store-postgres-lib DEP-STORE-002 façade commune + backends optionnels par feature DEP-STORE-009 ownership physique PostgreSQL par ksp-store-postgres-lib DEP-STORE-010 consumers ordinaires -> ksp-store-lib, jamais backend direct architecture 008 même séparation durable ``` Cette réconciliation est un prérequis du scaffold `pre.002` ; elle ne constitue pas une implémentation backend. ## 4. Graphe Cargo cible ### 4.1 Graphe KSP Le graphe cible de `0.3.2` est : ```text ksp-store-lib ├── ksp-store-api ├── ksp-logging-lib └── [feature postgres] └── ksp-store-postgres-lib ksp-store-postgres-lib ├── ksp-store-api ├── ksp-logging-lib ├── tokio ├── tokio-postgres ├── deadpool-postgres ├── tokio-postgres-rustls ├── rustls └── sha2 à partir de la tranche migrations ``` Interdictions : ```text ksp-store-postgres-lib -X-> ksp-store-lib ksp-store-lib -X-> ksp-config-lib ksp-store-postgres-lib -X-> ksp-config-lib ksp-store-* -X-> Transport/Program/Materializer apps/workers/jobs -X-> ksp-store-postgres-lib ``` `ksp-config-lib` pourra dépendre de `ksp-store-lib` pour construire des settings effectifs, comme pour les autres adapters Config -> runtime. ### 4.2 Features de `ksp-store-lib` Contrat retenu : ```toml [features] default = ["postgres"] postgres = ["dep:ksp-store-postgres-lib"] ``` `ksp-store-lib --no-default-features` reste compilable. Le type public identifiant PostgreSQL existe même lorsque la feature n'est pas compilée afin qu'une configuration connue mais indisponible puisse produire une erreur stable et explicite : ```text STORE_BACKEND_NOT_COMPILED ``` Aucune feature de backend ne modifie `ksp-store-api`. ### 4.3 Versions externes candidates retenues au gate Audit au 29 août 2026 : ```text PostgreSQL stable courant 18.6 tokio-postgres 0.7.18 deadpool-postgres 0.14.1 tokio-postgres-rustls 0.14.0 rustls 0.23 sha2 0.11.0 refinery audité mais rejeté 0.9.2 ``` Les manifests utiliseront les contraintes compatibles avec `DEP-CARGO-*`, sans pin patch arbitraire. Les versions réellement résolues seront enregistrées par les graphes de la tranche qui les ajoute. Licences auditées : ```text tokio-postgres MIT OR Apache-2.0 deadpool-postgres MIT OR Apache-2.0 tokio-postgres-rustls MIT sha2 MIT OR Apache-2.0 refinery MIT ``` Sources primaires/techniques consultées : ```text https://www.postgresql.org/support/versioning/ https://www.postgresql.org/docs/current/libpq-ssl.html https://www.postgresql.org/docs/release/18.6/ https://docs.rs/tokio-postgres/0.7.18/tokio_postgres/ https://github.com/rust-postgres/rust-postgres/blob/master/tokio-postgres/Cargo.toml https://docs.rs/deadpool-postgres/0.14.1/deadpool_postgres/ https://docs.rs/tokio-postgres-rustls/0.14.0/tokio_postgres_rustls/ https://docs.rs/sha2/0.11.0/sha2/ https://docs.rs/refinery/0.9.2/refinery/ ``` ## 5. PostgreSQL support policy ### 5.1 Major minimal KSP `0.3.2` retient : ```text major PostgreSQL minimal supporté : 15 major testé prioritairement : 18 patch de référence au gate : 18.6 ``` PostgreSQL 14 est encore supporté upstream au moment du gate mais atteint son EOL le 12 novembre 2026. L'ajouter à une nouvelle fondation en août 2026 créerait une dette immédiate sans bénéfice démontré. Aucun plafond runtime strict n'est fixé à 18 : un serveur plus récent n'est pas rejeté uniquement à cause de son major. Les garanties de release portent sur le minimum 15 et le major courant réellement testé. ### 5.2 Driver Décision acquise confirmée : ```text tokio-postgres ``` Raisons : ```text driver async natif ciblé API de transaction explicite support TLS via MakeTlsConnect connection future explicite pas d'ORM ni macro SQL imposée compatible avec l'ownership backend séparé ``` La future implémentation ne doit jamais appeler un chemin qui lit implicitement `PG*`, `.pgpass` ou l'environnement KSP. La configuration `tokio_postgres::Config` est construite uniquement depuis des settings reçus de la façade. ## 6. Pooling ### 6.1 Décision Le pool externe retenu est : ```text deadpool-postgres 0.14.x ``` Le choix est fondé sur l'ownership/lifecycle et non sur la popularité. L'audit source montre que le manager : ```text crée Client + connection future tokio-postgres spawn la connection future conserve le JoinHandle dans ClientWrapper abort le JoinHandle au Drop du wrapper ``` Le pool possède aussi un `close()` et des timeouts wait/create/recycle. ### 6.2 Alternative rejetée `bb8-postgres` a été audité comme candidat maintenu, mais son manager spawn la connection future lors de `connect` sans conserver le `JoinHandle` dans l'objet retourné. Cette propriété est moins adaptée au critère KSP : ```text aucune connection task orpheline shutdown borné et observable ownership explicite du lifecycle ``` Construire un pool KSP complet est également rejeté pour `0.3.2` : cela réimplémenterait admission, waiters, recycle et fermeture sans besoin métier démontré. ### 6.3 Bornes runtime Settings KSP candidats, à matérialiser exactement en `pre.003`/`pre.005` : ```text max_connections défaut 8 plage 1..64 connect_timeout_ms défaut 10_000 plage 100..60_000 pool_wait_timeout_ms défaut 5_000 plage 100..60_000 pool_create_timeout_ms défaut 10_000 plage 100..60_000 pool_recycle_timeout_ms défaut 5_000 plage 100..60_000 shutdown_timeout_ms défaut 5_000 plage 100..30_000 ``` Ces bornes sont des garde-fous de ressources/lifecycle, pas des policies de workers/jobs. Aucun pool, client, statement cache ou type deadpool/tokio-postgres n'est exposé par `ksp-store-lib`. ## 7. TLS PostgreSQL ### 7.1 Connecteur retenu Décision : ```text tokio-postgres-rustls 0.14.x rustls 0.23 crypto provider aws-lc-rs root store système via native-certs ``` Ce choix reste cohérent avec la stack Rustls déjà présente dans le workspace et évite d'ajouter OpenSSL/native-tls comme dépendance fonctionnelle Store. ### 7.2 Modes publics La façade KSP n'expose initialement que : ```text Disabled VerifyFull ``` `VerifyFull` signifie : ```text TLS obligatoire chaîne certificat vérifiée contre les roots retenus nom serveur vérifié par rustls aucun fallback plaintext ``` Les modes `Prefer` et `Require` sans vérification de certificat ne sont pas exposés dans `0.3.2`. Le type `tokio_postgres::config::SslMode` ne suffit donc pas à exprimer la policy publique KSP ; il est un détail backend et sera forcé conformément au mode KSP. Custom CA, certificat client/mTLS et pinning sont reportés jusqu'à un besoin concret. Le backend ne lit jamais implicitement `sslrootcert`, `sslcert`, `sslkey` ou des fichiers libpq. ## 8. Store settings et backend selection ### 8.1 Ownership `StoreSettings` appartient à `ksp-store-lib` et ne dépend pas de Config. La forme retenue est typée, sans `serde_json::Value` opaque : ```text StoreSettings └── StoreBackendSettings └── Postgres(PostgresStoreSettings) ``` `PostgresStoreSettings` est un type KSP-owned de la façade, pas un type de `ksp-store-postgres-lib`. Il contient uniquement des primitives/settings KSP et reste disponible sans feature `postgres`. Lorsque la feature est active, `ksp-store-lib` convertit en privé ces settings vers un `PostgresBackendSettings` possédé par `ksp-store-postgres-lib`. Ce dernier n'est pas réexporté vers les consumers. ### 8.2 Surface candidate Les groupes de settings sont : ```text connection_uri String possédée, Debug redacted, aucun getter de diagnostic pool bornes de section 6 tls Disabled ou VerifyFull bootstrap auto_migrate + migration_timeout_ms + lock_timeout_ms shutdown_timeout_ms lifecycle commun ``` Bornes bootstrap : ```text auto_migrate défaut true migration_timeout_ms défaut 30_000 plage 1_000..300_000 migration_lock_ms défaut 10_000 plage 100..120_000 ``` Il n'existe pas de `Default` qui invente une URI de production. Config et les callers programmatiques doivent construire explicitement le backend settings. ### 8.3 URI et secrets La connexion URI est considérée sensible intégralement, même lorsqu'elle ne contient pas de mot de passe visible. Interdictions : ```text Debug de l'URI URI dans ErrorContext URI dans tracing URI dans health snapshot copie d'un message serveur contenant des valeurs arbitraires ``` Le backend parse la chaîne avec `tokio-postgres`, mais la policy TLS KSP ne peut pas être contournée par des paramètres URI conflictuels. Les options libpq susceptibles de charger des fichiers ou de modifier la policy TLS ne sont pas implicitement honorées par KSP ; les paramètres effectifs sont construits/normalisés depuis les settings typés. ## 9. Lifecycle `Store` ### 9.1 Ouverture Surface cible : ```text Store::open(settings).await -> Result ``` Étapes : ```text validate settings resolve backend kind reject known backend not compiled before I/O convert facade settings -> backend settings construct pool/TLS prove one bounded backend connection/readiness path run/verify bootstrap according to auto_migrate return Store only after readiness success ``` Le `Store` n'est pas `Clone` dans la fondation. Aucun pool/client backend ne peut s'échapper de la façade. ### 9.2 Fermeture Surface cible : ```text Store::close(self).await -> Result<()> ``` La fermeture consomme le Store, ferme le pool, interdit de nouvelles acquisitions et attend la terminaison nécessaire dans la borne `shutdown_timeout_ms`. `Drop` reste un fallback best-effort non bloquant ; le contrat de fermeture vérifiable est `close(self).await`. Cette forme limite structurellement le risque de close concurrent avec une opération qui emprunte encore le Store. ### 9.3 Erreurs stables candidates Domaine Store commun : ```text STORE_SETTINGS_INVALID STORE_BACKEND_NOT_COMPILED STORE_BACKEND_OPEN_FAILED STORE_BACKEND_CLOSED STORE_SHUTDOWN_TIMEOUT ``` Backend PostgreSQL : ```text STORE_POSTGRES_CONFIG_INVALID STORE_POSTGRES_CONNECT_FAILED STORE_POSTGRES_POOL_TIMEOUT STORE_POSTGRES_TLS_FAILED STORE_POSTGRES_MIGRATION_FAILED STORE_POSTGRES_MIGRATION_MISMATCH STORE_POSTGRES_SCHEMA_NEWER STORE_POSTGRES_HEALTH_FAILED ``` Le mapping final doit conserver un contexte sûr seulement : backend code, action, état, version de migration non secrète, durée/compteur borné. Aucun texte d'erreur distant arbitraire n'est propagé. ## 10. Migrations/bootstrap ### 10.1 Décision Le moteur de migrations `0.3.2` est KSP-owned et privé à `ksp-store-postgres-lib`. `refinery 0.9.2` a été audité : il fournit migrations embarquées, table d'historique, détection divergent/missing et transaction groupée. Il n'est pas retenu car la fondation KSP a besoin d'un contrat plus petit, d'un nom/table/lock précis, d'un mapping d'erreurs KSP strict et d'un checksum cryptographique explicitement stable. Son checksum courant repose sur un `u64` produit à partir du `DefaultHasher` Rust, ce qui n'est pas le contrat de persistence que KSP souhaite figer. ### 10.2 Ressources Les migrations futures sont : ```text SQL statique KSP-owned embarquées au build avec include_str! ordonnées par version entière croissante nom immuable borné checksum SHA-256 du SQL exact aucune découverte filesystem runtime aucun down migration implicite ``` Une correction de schéma est une nouvelle migration forward. ### 10.3 Metadata interne `0.3.2` autorise uniquement la relation d'infrastructure fixe : ```text ksp_store_schema_migrations ``` Elle contient au minimum : ```text version BIGINT PRIMARY KEY name TEXT NOT NULL checksum TEXT NOT NULL applied_at TIMESTAMPTZ NOT NULL ``` Aucun nom de table/schema n'est configurable par l'utilisateur dans cette fondation. Cela élimine le besoin normal de SQL dynamique pour les identifiants. La création/forme de cette table est elle-même protégée par un sentinel de bootstrap version `0` avec checksum connu ; les migrations métier futures commenceront à `1`. ### 10.4 Algorithme Bootstrap : ```text acquire dedicated pooled client begin transaction acquire advisory transaction lock KSP fixe avec attente bornée set transaction-local statement timeout depuis valeur bornée create/verify history metadata verify bootstrap sentinel load applied migrations ordered reject unknown/newer versions reject missing historical versions reject name/checksum divergence apply each pending embedded migration in order insert corresponding history row commit once ``` L'advisory lock est acquis sans attente infinie : KSP utilise une tentative non bloquante répétée jusqu'à la deadline plutôt qu'un lock bloquant non borné. Une erreur de migration fait rollback de la transaction entière du run courant. Les migrations déjà commitées lors d'un run antérieur restent intactes. ### 10.5 Aucun schéma métier en `0.3.2` La liste de migrations de production `0.3.2` ne crée : ```text aucune table RawTransaction aucune table RawAccountState aucune table CORE/DECODE/SPECIALIZED aucun index métier ``` La seule persistence autorisée est la metadata privée de migration/bootstrap. ## 11. Health/readiness Le health portable est retenu dans `0.3.2`, mais limité à la fondation runtime. Justification : ```text Store::open doit distinguer construit vs réellement prêt integration réelle doit prouver connect/bootstrap ops futures ont besoin d'un diagnostic safe sans pool leak close doit être observable sans exposer le backend ``` Surface candidate dans `ksp-store-lib` : ```text StoreHealthState StoreHealthSnapshot StoreRuntimeSnapshot ``` Projection sûre seulement : ```text backend kind state pool size/available sous forme de compteurs bornés schema/migration version courante pending migration count last safe error code éventuel ``` Interdits : URI, host, username, database name si sensible, SQL, nom physique de relation, server error string, pool/client handles. Le backend PostgreSQL peut utiliser `SELECT 1` et une introspection minimale interne, puis mapper son résultat vers la projection portable. ## 12. Config `std.store` ### 12.1 Ownership Seul `ksp-config-lib` possède : ```text config/std.store.json config/schemas/store.schema.json config/examples/store.example.json registry descriptors placeholder interpolation .env / process environment provenance/sensitivity adapter Config -> StoreSettings ``` Store et backend ne dépendent pas de Config et ne lisent aucun environnement. ### 12.2 Shape candidate Le document V1 est profilé comme les autres documents standards : ```json { "format_version": 1, "default_profile": "postgres_default", "profiles": [ { "profile_id": "postgres_default", "backend": "postgres", "postgres": { "connection_uri": "${KSP_SECRET_STORE_POSTGRES_URI:-postgresql://localhost/ksp}", "pool": { "max_connections": 8, "connect_timeout_ms": 10000, "wait_timeout_ms": 5000, "create_timeout_ms": 10000, "recycle_timeout_ms": 5000 }, "tls": { "mode": "verify_full" }, "bootstrap": { "auto_migrate": true, "migration_timeout_ms": 30000, "migration_lock_timeout_ms": 10000 }, "shutdown_timeout_ms": 5000 } } ] } ``` Le fallback localhost est classé `Secret` car il appartient à un placeholder `KSP_SECRET_*`; sa safe projection reste redacted. Il ne provoque aucune connexion automatique. `.env.example` ajoutera dans la même tranche : ```text # PostgreSQL connection URI used by Config std.store profiles. # KSP_SECRET_STORE_POSTGRES_URI=postgresql://user:password@localhost/ksp ``` La valeur d'exemple commentée n'est jamais un vrai credential. ### 12.3 Feature mismatch Config reconnaît `backend = "postgres"` indépendamment de la feature du consumer. L'adapter peut construire le type public `StoreSettings::Postgres`; c'est `ksp-store-lib` qui retourne `STORE_BACKEND_NOT_COMPILED` si la feature manque. Ainsi Config ne doit pas connaître les `cfg(feature = "postgres")` de chaque consumer. ## 13. Logging et redaction Les deux crates runtime auront un `constants.rs` privé avec un target explicite : ```text ksp-store-lib target ksp-store-lib ksp-store-postgres-lib target ksp-store-postgres-lib ``` Elles utilisent `ksp-logging-lib`, jamais `tracing` directement comme surface KSP. Événements sûrs candidats : ```text store_open_start / store_open_ready / store_close postgres_pool_open / postgres_pool_close postgres_migration_start / applied / current / failed postgres_health ``` Champs autorisés : action, backend code, durations, counts, migration version/name bornée. Sont interdits URI, SQL, bind values, password, remote error detail et payload arbitraire. Le `tracing` transitif éventuel de `deadpool-postgres` reste une dépendance externe interne ; KSP ne le réexporte pas et la politique Logging existante garde les targets externes hors des diagnostics KSP normaux sauf configuration explicite. ## 14. Audit d'héritage kbot3 ciblé L'archive historique a été relue sur : ```text ks-store/Cargo.toml ks-store/src/store.rs ks-store/src/postgres/** ks-store/migrations/postgres/** ks-config/src/store.rs config/store.config.json config/schemas/store.schema.json docs/architecture/STORAGE_ARCHITECTURE.md docs/guides/POSTGRES_STORAGE.md ``` Constats : ```text backend PostgreSQL monolithique à l'intérieur de ks-store SQLx PgPool StoreOpenOptions avec backend_options serde_json::Value max_connections / connect_timeout / auto_initialize_schema initialisation transactionnelle + advisory lock health SELECT 1 / current_schema / version schema N1-N3 large et repositories déjà métier configuration historique capable de résoudre directement environnement KS* ``` Classification : ```text REPRENDRE façade backend-agnostic comme intention pool borné + timeout explicite auto-init explicite advisory lock pour bootstrap health léger SQL/objets physiques privés logs structurés sans SQL/binds REDESSINER séparation ksp-store-lib / ksp-store-postgres-lib tokio-postgres au lieu de SQLx settings typés au lieu de backend_options JSON TLS explicite vérifié migration history version + SHA-256 lifecycle close explicite error mapping sans texte SQLx/serveur Config actuel KSP avec KSP_SECRET_* seulement REPORTER 16 tables N1-N3 historiques 240 ressources SQL métier 79 index repositories RAW/CORE/DECODE processing ledgers/replay maintenance DROP/TRUNCATE REJETER SQLx comme driver KSP backend PostgreSQL physiquement dans la façade lecture directe de l'environnement par Store/backend anciens prefixes KS*/KB* backend options JSON opaques exposition d'erreurs backend brutes statut de schéma basé uniquement sur présence d'objets sans historique immuable ``` L'archive kbot3 est une source de design historique, jamais une base de copie de code ou de schéma. ## 15. Threat model `0.3.2` ### 15.1 Credential/diagnostic leak Risque : DSN/password via Debug, errors, tracing, snapshots ou panics. Mesures : wrapper/settings Debug redacted, aucun DSN dans ErrorContext, mapping stable des erreurs, aucune remote error string, tests canary avec secret hostile. ### 15.2 Bypass Config par environnement libpq Risque : `PGHOST`, `PGPASSWORD`, `.pgpass`, `sslrootcert` ou autres sources implicites changent le runtime. Mesures : Store/backend ne lisent aucun env, configuration driver construite depuis settings explicites, source scanner KSP, integration test stdin sans env. ### 15.3 Connection string hostile Risque : URI surdimensionnée, malformed, options conflictuelles TLS, valeurs loggées. Mesures : borne de longueur, parse avant I/O, erreurs génériques, policy TLS typée qui prime, pas d'écho input. ### 15.4 Connection storm / pool non borné Risque : nombre excessif de connexions, waiters ou timeouts infinis. Mesures : `max_connections <= 64`, tous les timeouts bornés, deadpool configuré explicitement, aucun retry loop de connexion caché dans Store. ### 15.5 Hung connect / migration / shutdown Risque : futur bloqué indéfiniment. Mesures : connect/pool timeouts, migration deadline, advisory try-lock borné, statement timeout local, close timeout. ### 15.6 Concurrent migration runners Risque : DDL/history race. Mesures : transaction unique + advisory transaction lock fixe, history relue sous lock, test concurrent réel. ### 15.7 Historical migration modifiée Risque : source rebuildée avec SQL différent sous même version. Mesures : nom/version immuables + SHA-256 persisté, mismatch terminal avant migration suivante. ### 15.8 Migration partielle Risque : DDL appliqué sans history ou inversement. Mesures : transaction unique pour le run, history insert dans la même transaction, rollback sur erreur, test d'échec injecté privé. ### 15.9 SQL/dynamic identifier injection Risque : nom de schema/table fourni par Config interpolé dans SQL. Mesures : aucun nom physique configurable en `0.3.2`; ressources SQL statiques et valeurs paramétrées. ### 15.10 Feature/config mismatch Risque : Config demande PostgreSQL mais binaire sans feature. Mesures : type backend connu toujours présent + `STORE_BACKEND_NOT_COMPILED` avant I/O. ### 15.11 Connection task leaked/dropped Risque : future tokio-postgres non pilotée ou tâche orpheline après close. Mesures : deadpool-postgres retenu précisément pour ownership du JoinHandle par client wrapper ; aucun client/pool ne fuit de la façade ; close explicite + Drop fallback. ### 15.12 Schema history newer than runtime Risque : ancien binaire lancé contre DB migrée par une version plus récente. Mesures : version appliquée inconnue/supérieure => `STORE_POSTGRES_SCHEMA_NEWER`, aucune migration/down automatique. ### 15.13 Server error echo Risque : message PostgreSQL incorpore table/value/credential/SQL snippet. Mesures : conversion immédiate vers code KSP + contexte allowlisté ; source error brute non incluse dans Display/Debug public. ## 16. PostgreSQL integration test réel Le test opt-in sera dans `ksp-store-postgres-lib/tests/` et ignoré par défaut. Entrée : ```text une URI PostgreSQL dédiée lue depuis stdin aucune variable d'environnement aucun affichage de l'URI ``` Sécurité : ```text refuser de démarrer si ksp_store_schema_migrations existe déjà ne créer aucune table métier ne DROP ni database ni schema cleanup best-effort uniquement de la table metadata que le test a prouvé avoir créée ``` Scénario minimal : ```text connect et major >= 15 bootstrap initial second bootstrap idempotent deux bootstrap concurrents après reset contrôlé corruption test-only du checksum sentinel -> mismatch explicite restore/reset contrôlé migration failure injectée sous cfg(test) -> rollback/history inchangée health ready close explicite borné cleanup metadata ``` Le gate de référence visera PostgreSQL 18.6. Le test peut accepter un serveur >= 15, mais le rapport opérateur doit enregistrer le major réellement utilisé sans imprimer d'identité sensible. ## 17. Validation de dépendances et API Canaris à créer au fil des tranches : ```text ksp-store-lib --no-default-features compile postgres est feature default known-but-not-compiled error observable ksp-store-postgres-lib ne dépend pas ksp-store-lib Store/backend ne dépendent pas Config/Transport/Program/Materializer Store/backend production sources ne lisent pas env aucun type tokio-postgres/deadpool/rustls dans crate root ksp-store-lib ksp-store-api inchangé et dépend toujours seulement de ksp-core-lib aucune des 10 capabilities RAW implémentée par PostgreSQL en 0.3.2 aucune table RAW créée par migrations ``` La façade `ksp-store-lib` réexportera la surface crate-root de `ksp-store-api` nécessaire aux consumers, mais ne réexportera aucun type `ksp-store-postgres-lib`. ## 18. Sizing et prévision souple recalibrée Le scope reste clôturable dans une session si les responsabilités restent fines. La prévision initiale du prompt est conservée avec deux précisions : la réconciliation normative est absorbée par `pre.001`, et le choix migrations KSP-owned évite une tranche supplémentaire. ### `pre.001` — Audit, design, threat model et réconciliation normative Livrer le présent plan, la matrice validation, l'audit externe/kbot3, les décisions pool/TLS/migrations/Config, le graphe exact et la correction des règles Store obsolètes. Aucun code runtime. ### `pre.002` — Scaffold des deux crates + feature graph Créer les crates, manifests, modules minimaux, `postgres` default, `--no-default-features`, constants/logging targets et canaris de dépendances. Ajouter seulement les dépendances nécessaires au scaffold retenu. **Statut : matérialisé par `0.3.2-pre.002`. Le gate opérateur confirme le graphe et les tests ; `pre.002-fix.001` corrige les warnings `TRACING_TARGET` sans modifier le scope avant passage à `pre.003`.** La tranche matérialise exactement : ```text ksp-store-lib -> ksp-store-api ksp-store-lib[postgres] -> ksp-store-postgres-lib ksp-store-postgres-lib -> ksp-store-api ksp-store-postgres-lib -X-> ksp-store-lib ``` Les deux crates possèdent déjà leur `src/constants.rs` et leur `TRACING_TARGET`, comme l'exigent les règles KSP-TRACE-102/103. Le gate opérateur de `pre.002` a toutefois révélé que le simple réexport privé produisait `unused_imports` et `dead_code` tant qu'aucun comportement de production n'émet encore de log. `pre.002-fix.001` conserve donc l'invariant de tracing et ajoute uniquement un canari compile-time privé `const _: &str = TRACING_TARGET;` : le target participe au scaffold compilé sans faux log, sans `allow(...)` et sans ajouter prématurément `ksp-logging-lib`. Tokio, tokio-postgres, Deadpool, Rustls et SHA-256 restent absents tant que leurs tranches d'usage réel ne sont pas ouvertes. ### `pre.003` — Settings + backend selection + lifecycle contracts Matérialiser `StoreSettings`, `StoreBackendSettings`, `PostgresStoreSettings`, erreurs stable, façade `Store` sans connexion lourde et réexports API. Tester la feature mismatch. ### `pre.004` — Config `std.store` Document/schema/example/registry/adaptor, `.env.example`, provenance/sensitivity/redaction, packaging resources strictement nécessaires. Aucun env dans Store/backend. ### `pre.005` — PostgreSQL connection + deadpool + Rustls Implémenter parse/normalisation URI, pool borné, connect/open/close, VerifyFull/Disabled, timeouts et erreurs redacted. Aucun SQL métier. ### `pre.006` — Migration/bootstrap foundation Créer history metadata, sentinel, SHA-256, advisory lock borné, transaction, version/checksum/newer/missing handling et rollback. Aucun schéma RAW. ### `pre.007` — Composition end-to-end + health Fermer `StoreSettings -> Store -> PostgresBackend`, health/readiness portable, close et mapping diagnostics. ### `pre.008` — PostgreSQL integration réelle Ajouter/exécuter le smoke opt-in non destructif : initial/idempotent/concurrent/mismatch/failure/close sur serveur réel. ### `pre.009` — Hardening/completeness/dependency matrix Inputs hostiles, redaction, no-env, exact exports/modules, external backend compatibility, `--no-default-features`, graphes/features/duplicates, non-régression API. ### `pre.010` — Gate technique final Workspace/clippy/tests, targeted tests, PostgreSQL live gate, graphes Cargo finaux. Aucun développement fonctionnel nouveau. ### `pre.011` — Réconciliation documentaire finale README/USAGE des deux crates, plan/validation/indexes/architecture réellement impactés. Pas de CHANGELOG/ROADMAP/prompt suivant. ### `pre.012` — Préparation de publication minimale Uniquement la lane autorisée : ```text Cargo.toml CHANGELOG.md ROADMAP.md prompts/022-V0_3_3_START_PROMPT.md deltas/0.3.2/pre.012.md ``` ### `rel.001` — Publication stable Version stable + delta de publication uniquement. ## 19. Critères de clôture de la release `0.3.2` ne ferme que si : ```text les deux crates existent feature postgres default et no-default compile graphe backend séparé prouvé tokio-postgres/deadpool/Rustls bornés et privés Config std.store possédée par ksp-config-lib aucune lecture env/PG*/.pgpass dans Store/backend migration history version/checksum/lock/rollback prouvée health/readiness safe prouvé PostgreSQL réel vert close borné vert aucun RawTransaction/RawAccountState implémenté aucun SQL métier RAW livré ksp-store-api non régressé README/USAGE et validation réconciliés workspace/clippy/tests/graphes verts ``` ## 20. Hors scope explicite ```text RawTransaction persistence/queries/retention RawAccountState persistence/queries/retention batch/backlog/priority worker/job claim/lease métier notifications persistées replay Core/Decode schema métier kbot3 SQLx ORM custom CA/mTLS sans besoin concret maintenance destructive publique Store Desk ``` ## 21. Questions restantes Aucune question architecturale ne bloque `pre.002`. Les détails suivants sont réservés à leur tranche sans rouvrir les décisions du gate : ```text nom exact des structs/methods settings en pre.003 codes numériques/strings finaux des ErrorCode en pre.003 mapping exact deadpool timeouts en pre.005 construction exacte du rustls RootCertStore en pre.005 DDL précis de ksp_store_schema_migrations en pre.006 forme finale des snapshots health en pre.007 ``` Toute découverte qui exigerait : ```text un type PostgreSQL dans ksp-store-api une dépendance backend -> ksp-store-lib une lecture directe d'environnement une table RAW métier dans 0.3.2 ``` est considérée comme contradiction architecturale et doit être réauditée avant poursuite.