181 lines
9.3 KiB
Markdown
181 lines
9.3 KiB
Markdown
<!-- file: crates/ksp-store-postgres-lib/README.md -->
|
|
<!-- version: 6 -->
|
|
|
|
# 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.
|