# ksp-store-postgres-lib `ksp-store-postgres-lib` est le backend PostgreSQL physique officiel du Store KSP. La crate implémente la fondation connexion/pool/TLS/migrations/health derrière `ksp-store-lib`. Elle dépend directement de `ksp-store-api` mais ne dépend jamais de la façade `ksp-store-lib`. ## Responsabilités La crate possède seule pour PostgreSQL : - le parsing et la normalisation de la configuration physique `tokio-postgres` ; - le pool borné `deadpool-postgres` ; - la policy TLS physique avec Rustls ; - les roots système et le provider cryptographique AWS-LC ; - le bootstrap/moteur de migrations privé KSP ; - la table metadata `ksp_store_schema_migrations` ; - la migration logique V000 relocalisée sous `migrations/v000_bootstrap/` et son checksum SHA-256 historique ; - l'advisory transaction lock borné des migrations ; - les snapshots runtime/health sûrs destinés au bridge de façade ; - la fermeture explicite du pool et son fallback `Drop` best-effort ; - la classification d'erreurs backend sans conserver le texte d'erreur PostgreSQL. ## Frontière d'utilisation Les applications, jobs et workers KSP ne dépendent normalement pas de cette crate : ```text consumer -> ksp-store-lib -> [feature postgres] ksp-store-postgres-lib ``` La surface publique de cette crate existe pour le bridge inter-crates et les tests d'intégration backend. Elle ne constitue pas une seconde façade Store. `ksp-store-postgres-lib` ne réexporte pas `tokio-postgres`, Deadpool ou Rustls. ## Connexion et pool `PostgresBackend::open` : 1. valide et normalise l'URI fournie explicitement ; 2. impose la policy TLS typée ; 3. construit un pool borné ; 4. prouve une connexion physique ; 5. vérifie/applique le bootstrap selon les settings ; 6. ne retourne qu'après succès de cette fondation. Le backend ne lit aucun environnement, `.env`, `PG*`, `.pgpass` ou fichier TLS implicite libpq. ## TLS Les modes sont exactement : ```text Disabled VerifyFull ``` `VerifyFull` exige : - TLS ; - roots système ; - certificat valide ; - vérification de l'identité serveur ; - aucune dégradation automatique en plaintext. Les configurations ne permettant pas de vérifier une identité serveur, comme `hostaddr` seul, sont rejetées. ## Migrations La fondation historique V000 est embarquée sous : ```text migrations/v000_bootstrap/tables/001_ksp_store_schema_migrations.sql ``` La vertical slice V001 est ensuite répartie sous `migrations/v001_raw_transaction/{tables,constraints,indexes}/` tout en restant une migration logique unique. V000 crée la metadata privée : ```text ksp_store_schema_migrations ``` Le moteur vérifie version, nom et checksum SHA-256, sérialise les runners par advisory transaction lock et refuse une history divergente ou plus récente que le runtime. V001 possède désormais le schéma physique `RawTransaction` et son contrat de compatibilité. Les lectures exactes sont acquises depuis `pre.004`; `pre.005` ajoute les écritures atomiques transaction + observation, l'idempotence réelle et la classification de conflit. `pre.006` ajoute la navigation keyset déterministe sur l'index `(slot, signature)` et son cursor opaque lié à la requête. `pre.007` ajoute les transitions de rétention atomiques `Full -> Archived -> Purged` et le rejet explicite de `Compacted` tant qu'aucune représentation compacte réelle n'existe. ## Health et erreurs `PostgresBackendRuntimeSnapshot` et `PostgresBackendHealthSnapshot` ne contiennent que des compteurs et états sûrs destinés à la façade. `PostgresBackendError` ne conserve que : ```text PostgresBackendErrorKind phase statique ``` Le texte d'erreur PostgreSQL, l'URI, SQL et les valeurs bind ne traversent pas cette frontière. ## Support PostgreSQL La politique de support de `0.3.2` fixe PostgreSQL 15 comme major minimal. Le test live de fondation refuse explicitement un serveur plus ancien ; le backend ne fixe aucun plafond arbitraire de major PostgreSQL. La compatibilité de migration reste basée sur le schéma KSP. La preuve opérateur réelle et le major effectivement exercé sont conservés dans la matrice de validation, pas dans cette documentation durable. ## Lectures RAW `0.3.3-pre.004` Le backend expose désormais quatre lectures étroites qui retournent uniquement des modèles `ksp-store-api` : ```text get_raw_transaction get_raw_transaction_observation get_raw_transaction_retention_state get_raw_transaction_tombstone ``` Le SQL et les rows restent privés au backend. Le mapping PostgreSQL est fallible et couvre notamment `NUMERIC(20,0) -> u64`, `BIGINT -> u32/u64`, timestamps bornés, bytes de taille fixe et codes de provenance. Une ligne stockée incompatible produit uniquement `PostgresBackendErrorKind::DataInvalid`; un échec de SELECT produit `ReadFailed`. Les lectures portant un `RawTransactionReference` rejettent un réseau différent avant toute acquisition de pool. `Full` lit le payload chaud, `Archived` le reconstruit depuis la relation archive et `Purged` retourne `None`; le tombstone reste accessible séparément pour `Purged`. ## Écritures RAW `0.3.3-pre.005` Le backend expose deux écritures étroites : ```text persist_raw_transaction_acquisition record_raw_transaction_observation ``` L'acquisition canonique et son observation sont commises dans une seule transaction PostgreSQL. L'insertion utilise les clés uniques physiques sans prélecture `has_*`; après un conflit unique, le backend verrouille la ligne gagnante et compare le contenu réel avant de conclure `AlreadyPresent` ou `Conflict`. Les octets du payload sont comparés lorsqu'ils existent encore : le hash seul ne constitue jamais une preuve d'idempotence. Un tombstone `Purged` compatible produit `SkippedPurged/NotRecorded` en mode normal. `ForceRehydrate` restaure `Full` et l'observation dans la même transaction. Une observation supplémentaire ne crée jamais implicitement son canonique ; une référence absente est classée `ReferenceNotFound` et un canonical purgé retourne `NotRecorded`. Les erreurs physiques d'écriture sont réduites à `WriteFailed`; aucune erreur serveur, SQLSTATE, query ou valeur de bind n'est conservée. ## Pagination RAW `0.3.3-pre.006` `list_raw_transactions` parcourt uniquement les références canoniques récupérables, en excluant les tombstones `Purged`. L'ordre total est : ```text Ascending = (slot ASC, signature ASC) Descending = (slot DESC, signature DESC) ``` La continuation est une keyset stricte `>` / `<`, jamais un `OFFSET`. Le cursor backend V1 fait exactement 109 octets et transporte `KSPT`, version 1, le dernier slot, la dernière signature et un digest SHA-256 lié au réseau, à la direction et aux bornes de slots. Un cursor rejoué sous une autre query est `QueryInvalid`. `RawPageLimit` n'est pas réduit par une policy KSP : PostgreSQL utilise `LIMIT requested + 1`, avec comme seule borne physique `requested <= i64::MAX - 1`. Une valeur supérieure produit `PageLimitUnsupported` sans clamp. La pagination ne promet aucun snapshot inter-pages. ## Rétention RAW `0.3.3-pre.007` `transition_raw_transaction_retention` applique uniquement les transitions physiques supportées : ```text Full -> Archived -> Purged ``` Le backend verrouille la ligne canonique avec `FOR UPDATE`, valide la forme physique courante, compare `current` avec `expected` et n'effectue la mutation qu'en cas de correspondance. `current == target` retourne `AlreadyAtTarget`; une race ayant déplacé l'état ailleurs retourne `ExpectedStateMismatch`. Une référence absente est `ReferenceNotFound`. `Full -> Archived` copie d'abord les octets exacts dans `ksp_raw_transaction_archive_payloads`, puis retire le payload chaud et passe l'état à `archived` dans la même transaction. `Archived -> Purged` supprime l'archive, efface `block_time`, conserve les cinq champs de tombstone et passe l'état à `purged` atomiquement. Les écritures d'acquisition/rehydration et les transitions utilisent le même verrou canonique, ce qui sérialise les races purge/ForceRehydrate. Toute transition dont `expected` ou `target` vaut `Compacted` est rejetée avant `pool.get()` avec `RetentionCompactionUnsupported` et le code stable `store.postgres_retention_compaction_unsupported`. PostgreSQL n'utilise pas TOAST comme faux contrat de compaction. ## Hors périmètre actuel La crate ne contient encore : - aucune implémentation complète des six traits `RawTransaction*` de `ksp-store-api` ; - aucun dispatch métier dans `ksp-store-lib` ; - aucune implémentation PostgreSQL des capabilities `RawAccount*` ; - aucune orchestration worker/job ; - aucun transport d'acquisition ou decoder Program. ## Documentation - [`USAGE.md`](USAGE.md) — bridge physique et lifecycle ; - [`../ksp-store-lib/README.md`](../ksp-store-lib/README.md) — façade runtime destinée aux consumers ; - [`../../docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md`](../../docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md) — architecture Store ; - [`../../docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md`](../../docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md) — décisions pool/TLS/migrations ; - [`../../docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md`](../../docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md) — preuves déterministes et PostgreSQL réel.