34 KiB
Plan 0.3.2 — Store/PostgreSQL runtime foundation
1. Objet et base
Cette release part exclusivement de la stable :
v0.3.1
workspace.package.version = 0.3.1
Le gate d'ouverture est :
0.3.2-pre.001
Les deux archives requises par le prompt sont disponibles et ont été auditées :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
[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 :
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 :
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 :
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 :
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 :
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 :
tokio-postgres
Raisons :
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 :
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 :
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 :
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 :
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 :
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 :
Disabled
VerifyFull
VerifyFull signifie :
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 :
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 :
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 :
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 Surface exacte matérialisée en pre.003
Les noms publics sont figés comme suit :
StoreBackendKind::Postgres
StoreBackendSettings::Postgres(PostgresStoreSettings)
PostgresTlsMode::{Disabled, VerifyFull}
PostgresPoolSettings
PostgresBootstrapSettings
PostgresStoreSettings
StoreSettings
Store
PostgresPoolSettings::default() et PostgresBootstrapSettings::default() portent exactement les bornes décidées par le gate pre.001. StoreSettings::with_default_shutdown(...) fournit uniquement la borne commune de shutdown à 5 s. Il n'existe toujours aucun Default pour PostgresStoreSettings ou StoreSettings, afin de ne jamais inventer une URI ou un backend de production.
Les validations pre.003 restent backend-neutral et sans parsing PostgreSQL : URI non vide, pool 1..64, timeouts pool/connect 100..60 000 ms, migration 1 000..300 000 ms, lock 100..120 000 ms et shutdown 100..30 000 ms. Le parsing/normalisation de l'URI et les options réellement honorées restent propriétaires de pre.005.
8.4 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 :
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 :
Store::open(settings).await -> Result<Store>
Étapes :
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 :
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 matérialisées
Domaine Store commun, figé en pre.003 :
ERROR_CODE_BACKEND_CLOSED -> store.backend_closed
ERROR_CODE_BACKEND_NOT_COMPILED -> store.backend_not_compiled
ERROR_CODE_BACKEND_OPEN_FAILED -> store.backend_open_failed
ERROR_CODE_SETTINGS_INVALID -> store.settings_invalid
ERROR_CODE_SHUTDOWN_TIMEOUT -> store.shutdown_timeout
Backend PostgreSQL :
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 :
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 :
ksp_store_schema_migrations
Elle contient au minimum :
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 :
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 :
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 :
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 :
StoreHealthState
StoreHealthSnapshot
StoreRuntimeSnapshot
Projection sûre seulement :
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 :
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 :
{
"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 :
# 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 :
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 :
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 :
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 :
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 :
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 :
une URI PostgreSQL dédiée lue depuis stdin
aucune variable d'environnement
aucun affichage de l'URI
Sécurité :
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 :
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 :
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 :
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.
Statut : matérialisé par 0.3.2-pre.003, gate opérateur requis.
La tranche fixe :
73 exports crate-root de ksp-store-lib : 60 reexports ksp-store-api + 13 éléments runtime Store
settings Config-independent sans serde/env
URI PostgreSQL intégralement redacted et sans getter public
pool/bootstrap/shutdown bornés selon pre.001
Store::open(settings).await
Store::close(self).await
postgres absent de la build -> store.backend_not_compiled avant I/O
postgres compilé avant pre.005 -> store.backend_open_failed/runtime_foundation_pending avant I/O
Le dernier comportement est volontairement transitoire : pre.003 ne retourne jamais un faux Store prêt. pre.005 remplacera uniquement ce stop de staging par la construction physique, puis pre.007 fermera le shutdown/health end-to-end. Aucun type PostgreSQL physique n'est public.
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 :
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 :
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
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 :
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 :
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.