v0.3.3-pre.012-fix.001

This commit is contained in:
2026-08-30 17:46:49 +02:00
parent 9f2d5ea704
commit 7af751c888
8 changed files with 511 additions and 393 deletions

View File

@@ -1,11 +1,11 @@
<!-- file: crates/ksp-store-postgres-lib/README.md -->
<!-- version: 9 -->
<!-- version: 10 -->
# 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`.
La crate implémente connexion, pool, TLS, migrations, health et persistence RAW 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
@@ -15,10 +15,10 @@ La crate possède seule pour PostgreSQL :
- 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 ;
- le bootstrap et le 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 ;
- le schéma physique et les statements privés `RawTransaction` ;
- 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.
@@ -31,7 +31,7 @@ Les applications, jobs et workers KSP ne dépendent normalement pas de cette cra
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.
La surface publique de cette crate existe pour le bridge inter-crates et les tests/intégrations backend. Elle ne constitue pas une seconde façade Store.
`ksp-store-postgres-lib` ne réexporte pas `tokio-postgres`, Deadpool ou Rustls.
@@ -57,33 +57,15 @@ Disabled
VerifyFull
```
`VerifyFull` exige :
`VerifyFull` exige TLS, roots système, certificat valide et vérification de l'identité serveur. Une configuration ne permettant pas de vérifier cette identité, comme `hostaddr` seul, est rejetée.
- TLS ;
- roots système ;
- certificat valide ;
- vérification de l'identité serveur ;
- aucune dégradation automatique en plaintext.
## Migrations et schéma
Les configurations ne permettant pas de vérifier une identité serveur, comme `hostaddr` seul, sont rejetées.
Le moteur de migrations embarqué vérifie version logique, 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.
## Migrations
Le bootstrap metadata est conservé comme migration V000. La migration logique V001 matérialise le schéma `RawTransaction` en ressources séparées `tables/`, `constraints/` et `indexes/` afin que le backend puisse vérifier leur compatibilité effective sans transformer les fichiers SQL en parser généraliste.
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`. `pre.008` ferme la conformance backend en implémentant les six traits `RawTransaction*` directement sur `PostgresBackend`.
La base est liée à un seul `RawNetworkId` via `ksp_store_identity`. Une migration enregistrée mais physiquement divergente est un mismatch ; les réparations additives sûres dépendent de `schema_autoupdate`.
## Health et erreurs
@@ -100,13 +82,11 @@ Le texte d'erreur PostgreSQL, l'URI, SQL et les valeurs bind ne traversent pas c
## 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.
Le major minimal supporté est PostgreSQL 15. Le backend ne fixe aucun plafond arbitraire de major ; la compatibilité opérationnelle reste fondée sur le contrat de schéma KSP et l'introspection du catalogue.
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 transaction
## Lectures RAW `0.3.3-pre.004`
Le backend expose désormais quatre lectures étroites qui retournent uniquement des modèles `ksp-store-api` :
Le backend expose :
```text
get_raw_transaction
@@ -115,79 +95,56 @@ 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.
Le SQL et les rows restent privés. 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.
`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`.
`Full` lit le payload chaud, `Archived` le reconstruit depuis la relation archive et `Purged` retourne `None`; le tombstone reste accessible séparément.
## Écritures RAW `0.3.3-pre.005`
## Écritures RAW transaction
Le backend expose deux écritures étroites :
Le backend expose :
```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.
L'acquisition canonique et son observation initiale sont commises dans une seule transaction PostgreSQL. Les clés uniques physiques fournissent l'admission idempotente ; après un conflit unique, le backend verrouille la ligne gagnante et compare le contenu réel avant de conclure `AlreadyPresent` ou `Conflict`.
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`.
Un tombstone `Purged` compatible produit `SkippedPurged/NotRecorded` en mode normal. `ForceRehydrate` restaure explicitement le payload `Full` et l'observation dans la même transaction.
Les erreurs physiques d'écriture sont réduites à `WriteFailed`; aucune erreur serveur, SQLSTATE, query ou valeur de bind n'est conservée.
## Pagination RAW transaction
## Pagination RAW `0.3.3-pre.006`
`list_raw_transactions` parcourt les références canoniques récupérables avec un ordre total `(slot, signature)`. Les tombstones `Purged` sont exclus.
`list_raw_transactions` parcourt uniquement les références canoniques récupérables, en excluant les tombstones `Purged`. L'ordre total est :
La continuation est une keyset stricte, jamais un `OFFSET`. Le cursor backend V1 est opaque et lié au réseau, à la direction et aux bornes de slots de la query. Store n'impose aucun plafond métier arbitraire à la taille de page ; seule la limitation physique du `LIMIT + 1` PostgreSQL est exposée.
```text
Ascending = (slot ASC, signature ASC)
Descending = (slot DESC, signature DESC)
```
## Rétention RAW transaction
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 :
`transition_raw_transaction_retention` applique les transitions physiques :
```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`.
Le backend verrouille la ligne canonique avec `FOR UPDATE`, compare l'état courant à l'état attendu et applique la mutation atomiquement. L'archivage conserve le payload exact dans la relation archive ; la purge conserve seulement le tombstone minimal.
`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 impliquant `Compacted` est rejetée avec `RetentionCompactionUnsupported` tant qu'aucune représentation compactée réelle n'est implémentée.
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
## Preuve PostgreSQL live `0.3.3`
La crate ne contient :
La vertical slice `RawTransaction` possède un test PostgreSQL réel opt-in dédié :
```text
postgres_raw_transaction_live
```
Il refuse une base où une table KSP gérée existe déjà, lit lURI dédiée uniquement sur `stdin`, ne laffiche jamais et nettoie seulement le schéma quil a lui-même créé. La preuve couvre bootstrap V000/V001, binding réseau, réparation additive contrôlée, écritures atomiques, concurrence réelle, observations, pagination/cursor, rétention/ForceRehydrate, races et rollback par annulation dune tâche bloquée sur un verrou PostgreSQL.
Le test reste `#[ignore]` dans les gates ordinaires. Après les corrections de compatibilité catalogue/drift de `pre.009-fix.001..006`, il passe sur PostgreSQL 17 et a été rejoué une seconde fois avec succès au gate technique final `pre.011`. Son exécution réelle et le major exercé sont enregistrés dans la matrice de validation de la release.
## Hors périmètre actuel
La crate ne contient encore :
- aucune capability `RawAccount*` ;
- aucune implémentation PostgreSQL des capabilities `RawAccount*` ;
- aucune orchestration worker/job ;
- aucun transport d'acquisition ou decoder Program.
- aucun transport d'acquisition ou decoder Program ;
- aucune policy autonome de batch, priorité ou rétention.
## Documentation
- [`USAGE.md`](USAGE.md) — bridge physique et lifecycle ;
- [`USAGE.md`](USAGE.md) — guide pratique du bridge physique et de ses capabilities ;
- [`../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 de fondation ;
- [`../../docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md`](../../docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md) — design et séquencement `RawTransaction` ;
- [`../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md`](../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md) — preuves déterministes et PostgreSQL réel `RawTransaction`.
- [`../../docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md`](../../docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md) — validation de fondation ;
- [`../../docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md`](../../docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md) — design `RawTransaction` ;
- [`../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md`](../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md) — validation `RawTransaction`.