v0.3.3-pre.012-fix.001
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-store-lib/README.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# ksp-store-lib
|
||||
|
||||
@@ -31,7 +31,7 @@ Une instance `Store` représente exactement :
|
||||
1 Store = 1 RawNetworkId + 1 backend physique sélectionné
|
||||
```
|
||||
|
||||
Le runtime Store n'est pas un multiplexeur multi-database ou multi-réseau. La sélection d'un target nommé appartient à Config. Le document `std.store` peut donc définir plusieurs targets indépendants — par exemple Devnet, Mainnet et Testnet — mais un appel à `Store::open` reçoit les settings d'un seul target.
|
||||
Le runtime Store n'est pas un multiplexeur multi-database ou multi-réseau. La sélection d'un target nommé appartient à Config. Le document `std.store` peut donc définir plusieurs targets indépendants, mais un appel à `Store::open` reçoit les settings d'un seul target.
|
||||
|
||||
Cette séparation permet d'utiliser des bases PostgreSQL distinctes par réseau tout en conservant le réseau dans l'identité logique des données RAW.
|
||||
|
||||
@@ -63,19 +63,9 @@ Store ne lit ni `.env`, ni variables `KSP_*` / `KSPB_*`, ni variables/fichiers i
|
||||
|
||||
`ksp-config-lib` possède `std.store`, la résolution des secrets et la sélection du target. Il construit ensuite un `StoreSettings` backend-neutral. L'URI PostgreSQL reste nécessaire au runtime mais n'a aucun getter public dans `ksp-store-lib` et son `Debug` est redacted.
|
||||
|
||||
Les targets committed sont actuellement :
|
||||
## Surface RawTransaction
|
||||
|
||||
```text
|
||||
devnet -> network devnet -> base indépendante
|
||||
mainnet -> network mainnet-beta -> base indépendante
|
||||
testnet -> network testnet -> base indépendante
|
||||
```
|
||||
|
||||
Les credentials restent dans les variables `KSP_SECRET_STORE_*_POSTGRES_URI` ou le `.env` possédé par Config.
|
||||
|
||||
## Surface actuelle et hors périmètre
|
||||
|
||||
Depuis `0.3.3`, `Store` implémente les six capabilities transactionnelles acquises dans `ksp-store-api` :
|
||||
`Store` implémente les six capabilities transactionnelles acquises dans `ksp-store-api` :
|
||||
|
||||
```text
|
||||
RawTransactionRead
|
||||
@@ -86,7 +76,18 @@ RawTransactionRetentionRead
|
||||
RawTransactionRetentionWrite
|
||||
```
|
||||
|
||||
Le consumer continue à manipuler uniquement les modèles et outcomes backend-neutral. Les erreurs physiques PostgreSQL sont projetées vers des codes Store stables tels que `store.wrong_network`, `store.raw_reference_not_found`, `store.postgres_read_failed`, `store.postgres_write_failed`, `store.postgres_data_invalid` et `store.postgres_page_limit_unsupported`.
|
||||
Le consumer manipule uniquement les modèles et outcomes backend-neutral. Les erreurs physiques PostgreSQL sont projetées vers des codes Store stables sans exposer le backend.
|
||||
|
||||
La façade fournit ainsi :
|
||||
|
||||
- lecture d'une transaction canonique, de ses observations et de sa rétention ;
|
||||
- écriture atomique transaction + observation ;
|
||||
- ajout idempotent d'observations ;
|
||||
- pagination keyset déterministe par cursor opaque ;
|
||||
- application de transitions de rétention demandées par le caller ;
|
||||
- validation réseau avant dispatch lorsqu'un input porte explicitement son réseau.
|
||||
|
||||
## Hors périmètre
|
||||
|
||||
Sont volontairement hors de cette surface :
|
||||
|
||||
@@ -95,11 +96,9 @@ Sont volontairement hors de cette surface :
|
||||
- transport d'acquisition, Program decoding et materialization ;
|
||||
- exposition publique de SQL, pool, client, row, statement ou transaction PostgreSQL.
|
||||
|
||||
La vertical slice `RawTransaction` est désormais dispatchée ; `RawAccountState` reste la tranche suivante de `0.3.4` afin que la façade conserve une progression explicite par capability.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`USAGE.md`](USAGE.md) — construction des settings, ouverture, health et fermeture ;
|
||||
- [`USAGE.md`](USAGE.md) — guide pratique de construction, lifecycle et capabilities Store ;
|
||||
- [`../ksp-store-postgres-lib/README.md`](../ksp-store-postgres-lib/README.md) — responsabilité du backend PostgreSQL physique ;
|
||||
- [`../../config/std.store.json`](../../config/std.store.json) — targets Store committed ;
|
||||
- [`../../docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md`](../../docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md) — architecture durable Store ;
|
||||
|
||||
@@ -1,36 +1,58 @@
|
||||
<!-- file: crates/ksp-store-lib/USAGE.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# Utilisation de ksp-store-lib
|
||||
|
||||
## 1. Dépendance et features
|
||||
## 1. Dépendance et backend compilé
|
||||
|
||||
Le consumer runtime normal dépend uniquement de la façade :
|
||||
Le consumer runtime dépend de la façade commune :
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
ksp-store-lib = { path = "../ksp-store-lib" }
|
||||
```
|
||||
|
||||
La feature par défaut est :
|
||||
La feature par défaut compile le backend PostgreSQL :
|
||||
|
||||
```text
|
||||
postgres
|
||||
```
|
||||
|
||||
Pour construire un binaire sans backend physique :
|
||||
Pour compiler la façade sans backend physique :
|
||||
|
||||
```toml
|
||||
ksp-store-lib = { path = "../ksp-store-lib", default-features = false }
|
||||
```
|
||||
|
||||
Dans ce mode, le type PostgreSQL reste connu par la surface de settings mais `Store::open` retourne `ERROR_CODE_BACKEND_NOT_COMPILED` avant toute I/O si PostgreSQL est sélectionné.
|
||||
Dans ce mode, les settings PostgreSQL restent représentables, mais `Store::open` retourne `ERROR_CODE_BACKEND_NOT_COMPILED` avant toute I/O si PostgreSQL est sélectionné.
|
||||
|
||||
Un consumer ordinaire ne dépend pas directement de `ksp-store-postgres-lib`.
|
||||
Un consumer applicatif ordinaire ne dépend pas directement de `ksp-store-postgres-lib`.
|
||||
|
||||
## 2. Construire des settings PostgreSQL programmatiquement
|
||||
## 2. Obtenir les settings depuis Config
|
||||
|
||||
La construction directe est utile pour les tests, outils internes ou compositions qui n'utilisent pas `ksp-config-lib`.
|
||||
Le chemin applicatif recommandé passe par `ksp-config-lib`, propriétaire de `std.store`, de la résolution `.env` et des secrets.
|
||||
|
||||
```rust
|
||||
fn resolve_store_settings(
|
||||
engine: &ksp_config_lib::ConfigDocumentEngine,
|
||||
environment: &ksp_config_lib::ConfigEnvironment,
|
||||
target: std::option::Option<&str>,
|
||||
) -> ksp_core_lib::Result<ksp_store_lib::StoreSettings> {
|
||||
let resolved = engine.load_resolved_store_config(target, environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
return std::result::Result::Ok(resolved.into_settings());
|
||||
}
|
||||
```
|
||||
|
||||
Chaque `StoreSettings` sélectionne exactement un réseau logique et un backend physique. La sélection d'un target nommé appartient à Config ; `Store` ne route pas automatiquement entre plusieurs targets.
|
||||
|
||||
## 3. Construire des settings programmatiquement
|
||||
|
||||
La construction directe est utile pour les tests et outils qui ne passent pas par Config.
|
||||
|
||||
```rust
|
||||
fn programmatic_store_settings(connection_uri: std::string::String) -> ksp_store_lib::Result<ksp_store_lib::StoreSettings> {
|
||||
@@ -52,8 +74,7 @@ fn programmatic_store_settings(connection_uri: std::string::String) -> ksp_store
|
||||
ksp_store_lib::StoreBackendSettings::Postgres(postgres),
|
||||
);
|
||||
|
||||
let validation = settings.validate();
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
if let std::result::Result::Err(error) = settings.validate() {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
@@ -61,11 +82,11 @@ fn programmatic_store_settings(connection_uri: std::string::String) -> ksp_store
|
||||
}
|
||||
```
|
||||
|
||||
`PostgresStoreSettings` ne fournit volontairement aucun getter public de l'URI. Son `Debug` remplace cette valeur par `<redacted>`.
|
||||
`PostgresStoreSettings` ne fournit aucun getter public de l'URI. Son `Debug` remplace cette valeur par `<redacted>`.
|
||||
|
||||
## 3. Ouvrir et fermer un Store
|
||||
## 4. Ouvrir, sonder et fermer un Store
|
||||
|
||||
`Store::open` est async et ne retourne un succès qu'après que le backend compilé a prouvé sa fondation runtime.
|
||||
`Store::open` est async et ne retourne un succès qu'après validation des settings, ouverture du backend compilé et bootstrap requis.
|
||||
|
||||
```rust
|
||||
async fn use_store(settings: ksp_store_lib::StoreSettings) -> ksp_store_lib::Result<()> {
|
||||
@@ -95,130 +116,11 @@ async fn use_store(settings: ksp_store_lib::StoreSettings) -> ksp_store_lib::Res
|
||||
}
|
||||
```
|
||||
|
||||
`Store::close(self)` consomme l'instance afin qu'une fermeture explicite ne puisse pas être suivie d'une nouvelle opération via la même valeur.
|
||||
`Store::close(self)` consomme l'instance. Une fermeture explicite ne peut donc pas être suivie d'une nouvelle opération via la même valeur.
|
||||
|
||||
## 4. Construire les settings depuis Config
|
||||
## 5. Lire une transaction RAW
|
||||
|
||||
Le chemin applicatif recommandé utilise `ksp-config-lib`, propriétaire du document `std.store`, de `.env` et des secrets.
|
||||
|
||||
Après construction du `ConfigDocumentEngine` :
|
||||
|
||||
```rust
|
||||
fn resolve_store_settings(
|
||||
engine: &ksp_config_lib::ConfigDocumentEngine,
|
||||
environment: &ksp_config_lib::ConfigEnvironment,
|
||||
target: std::option::Option<&str>,
|
||||
) -> ksp_core_lib::Result<ksp_store_lib::StoreSettings> {
|
||||
let resolved = engine.load_resolved_store_config(target, environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
return std::result::Result::Ok(resolved.into_settings());
|
||||
}
|
||||
```
|
||||
|
||||
Targets committed :
|
||||
|
||||
```text
|
||||
devnet -> RawNetworkId("devnet")
|
||||
mainnet -> RawNetworkId("mainnet-beta")
|
||||
testnet -> RawNetworkId("testnet")
|
||||
```
|
||||
|
||||
Chaque target peut utiliser une URI PostgreSQL distincte. `default_profile` sélectionne un seul target ; `Store` ne route pas automatiquement entre plusieurs targets.
|
||||
|
||||
## 5. Settings disponibles
|
||||
|
||||
### `PostgresPoolSettings`
|
||||
|
||||
Valeurs par défaut :
|
||||
|
||||
```text
|
||||
max_connections 8
|
||||
connect_timeout 10 s
|
||||
wait_timeout 5 s
|
||||
create_timeout 10 s
|
||||
recycle_timeout 5 s
|
||||
```
|
||||
|
||||
Les getters sont :
|
||||
|
||||
```text
|
||||
max_connections()
|
||||
connect_timeout()
|
||||
wait_timeout()
|
||||
create_timeout()
|
||||
recycle_timeout()
|
||||
```
|
||||
|
||||
`validate()` vérifie les bornes sans I/O.
|
||||
|
||||
### `PostgresBootstrapSettings`
|
||||
|
||||
Valeurs par défaut :
|
||||
|
||||
```text
|
||||
schema_autocreate true
|
||||
schema_autoupdate true
|
||||
migration_timeout 30 s
|
||||
migration_lock_timeout 10 s
|
||||
```
|
||||
|
||||
Getters :
|
||||
|
||||
```text
|
||||
schema_autocreate()
|
||||
schema_autoupdate()
|
||||
migration_timeout()
|
||||
migration_lock_timeout()
|
||||
```
|
||||
|
||||
Le constructeur de compatibilité `new(..., auto_migrate, ...)` continue de mapper cette valeur sur les deux politiques, mais les nouveaux callers doivent préférer les deux switches séparés.
|
||||
|
||||
### `StoreSettings`
|
||||
|
||||
La surface expose :
|
||||
|
||||
```text
|
||||
backend()
|
||||
backend_kind()
|
||||
network()
|
||||
shutdown_timeout()
|
||||
validate()
|
||||
```
|
||||
|
||||
`StoreSettings::new` permet de choisir explicitement le timeout de shutdown. `StoreSettings::with_default_shutdown` utilise la borne commune par défaut de 5 secondes.
|
||||
|
||||
## 6. Health et diagnostics
|
||||
|
||||
`StoreRuntimeSnapshot` est synchrone et ne déclenche aucune I/O. Il expose uniquement :
|
||||
|
||||
```text
|
||||
backend_kind
|
||||
network
|
||||
pool_capacity
|
||||
pool_size
|
||||
pool_available
|
||||
pool_waiting
|
||||
```
|
||||
|
||||
`StoreHealthSnapshot` ajoute une probe async bornée :
|
||||
|
||||
```text
|
||||
state = Ready | NotReady
|
||||
migration_version
|
||||
pending_migration_count
|
||||
last_error_code
|
||||
runtime snapshot
|
||||
```
|
||||
|
||||
Aucun snapshot n'expose URI, host, user, database, SQL, handle backend ou texte d'erreur PostgreSQL.
|
||||
|
||||
## 7. Utiliser les capabilities RawTransaction
|
||||
|
||||
Depuis `0.3.3`, `Store` implémente directement les six traits `RawTransaction*`. Le consumer importe le trait correspondant puis appelle la méthode sur la façade :
|
||||
Importer le trait correspondant suffit pour utiliser la façade :
|
||||
|
||||
```rust
|
||||
use ksp_store_lib::RawTransactionRead;
|
||||
@@ -231,9 +133,146 @@ async fn read_transaction(
|
||||
}
|
||||
```
|
||||
|
||||
Le même pattern s'applique à l'écriture, aux observations et à la rétention. Les opérations portant un réseau explicite sont validées contre le réseau du `Store` avant dispatch vers le backend.
|
||||
Une transaction absente retourne `None`. Une transaction `Purged` retourne également `None` pour le payload canonique ; son tombstone reste accessible via la capability de rétention.
|
||||
|
||||
Codes runtime principaux :
|
||||
## 6. Paginer les références de transactions
|
||||
|
||||
La pagination est keyset et utilise un cursor opaque. Le consumer ne doit pas interpréter ses bytes.
|
||||
|
||||
```rust
|
||||
use ksp_store_lib::RawTransactionRead;
|
||||
|
||||
async fn first_transaction_page(
|
||||
store: &ksp_store_lib::Store,
|
||||
network: ksp_store_lib::RawNetworkId,
|
||||
) -> ksp_store_lib::Result<ksp_store_lib::RawPage<ksp_store_lib::RawTransactionReference>> {
|
||||
let limit = ksp_store_lib::RawPageLimit::new(100);
|
||||
let limit = match limit {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
let slots = ksp_store_lib::RawSlotRange::new(std::option::Option::None, std::option::Option::None);
|
||||
let slots = match slots {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
let query = ksp_store_lib::RawTransactionQuery::new(
|
||||
network,
|
||||
slots,
|
||||
ksp_store_lib::RawSortDirection::Ascending,
|
||||
ksp_store_lib::RawPageRequest::first(limit),
|
||||
);
|
||||
|
||||
return store.list_raw_transactions(&query).await;
|
||||
}
|
||||
```
|
||||
|
||||
Pour continuer, recopier le cursor retourné par `RawPage::next_cursor()` dans `RawPageRequest::after`. Le réseau, la direction et les bornes de slots doivent rester identiques à ceux de la query ayant produit le cursor.
|
||||
|
||||
## 7. Persister une acquisition canonique
|
||||
|
||||
La transaction canonique et son observation initiale forment une seule opération atomique.
|
||||
|
||||
```rust
|
||||
use ksp_store_lib::RawTransactionWrite;
|
||||
|
||||
async fn persist_acquisition(
|
||||
store: &ksp_store_lib::Store,
|
||||
transaction: ksp_store_lib::RawTransaction,
|
||||
observation: ksp_store_lib::RawTransactionObservation,
|
||||
) -> ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome> {
|
||||
return store
|
||||
.persist_raw_transaction_acquisition(
|
||||
transaction,
|
||||
observation,
|
||||
ksp_store_lib::RawTransactionAcquisitionMode::Normal,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
```
|
||||
|
||||
Le mode `Normal` respecte un tombstone `Purged`. `ForceRehydrate` doit être choisi explicitement lorsqu'un caller veut restaurer un payload purgé et que l'identité retenue est compatible.
|
||||
|
||||
Un contenu divergent sous la même identité produit `ERROR_CODE_RAW_CONFLICT`; Store ne remplace jamais silencieusement le contenu gagnant.
|
||||
|
||||
## 8. Lire et ajouter une observation
|
||||
|
||||
Une observation supplémentaire référence une transaction canonique déjà durable.
|
||||
|
||||
```rust
|
||||
use ksp_store_lib::RawTransactionObservationRead;
|
||||
use ksp_store_lib::RawTransactionObservationWrite;
|
||||
|
||||
async fn use_observation(
|
||||
store: &ksp_store_lib::Store,
|
||||
key: &ksp_store_lib::RawObservationKey,
|
||||
observation: ksp_store_lib::RawTransactionObservation,
|
||||
) -> ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome> {
|
||||
let existing = store.get_raw_transaction_observation(key).await;
|
||||
if let std::result::Result::Err(error) = existing {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
return store.record_raw_transaction_observation(observation).await;
|
||||
}
|
||||
```
|
||||
|
||||
`record_raw_transaction_observation` ne crée pas implicitement le canonique. Une référence absente est signalée par `ERROR_CODE_RAW_REFERENCE_NOT_FOUND`.
|
||||
|
||||
## 9. Lire la rétention et le tombstone
|
||||
|
||||
```rust
|
||||
use ksp_store_lib::RawTransactionRetentionRead;
|
||||
|
||||
async fn read_retention(
|
||||
store: &ksp_store_lib::Store,
|
||||
reference: &ksp_store_lib::RawTransactionReference,
|
||||
) -> ksp_store_lib::Result<std::option::Option<ksp_store_lib::RawRetentionState>> {
|
||||
let tombstone = store.get_raw_transaction_tombstone(reference).await;
|
||||
if let std::result::Result::Err(error) = tombstone {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
return store.get_raw_transaction_retention_state(reference).await;
|
||||
}
|
||||
```
|
||||
|
||||
Le tombstone minimal est utile uniquement lorsque le payload a été purgé ; il ne remplace pas le modèle canonique lorsqu'un payload est encore disponible.
|
||||
|
||||
## 10. Appliquer une transition de rétention
|
||||
|
||||
La policy qui décide qu'une transition est autorisée appartient au caller. Store applique uniquement la transition demandée de manière atomique.
|
||||
|
||||
```rust
|
||||
use ksp_store_lib::RawTransactionRetentionWrite;
|
||||
|
||||
async fn archive_transaction(
|
||||
store: &ksp_store_lib::Store,
|
||||
reference: ksp_store_lib::RawTransactionReference,
|
||||
) -> ksp_store_lib::Result<ksp_store_lib::RawRetentionWriteOutcome> {
|
||||
let transition = ksp_store_lib::RawTransactionRetentionTransition::try_new(
|
||||
reference,
|
||||
ksp_store_lib::RawRetentionState::Full,
|
||||
ksp_store_lib::RawRetentionState::Archived,
|
||||
);
|
||||
let transition = match transition {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
return store.transition_raw_transaction_retention(transition).await;
|
||||
}
|
||||
```
|
||||
|
||||
Le backend PostgreSQL supporte physiquement `Full -> Archived -> Purged`. Une transition impliquant `Compacted` est rejetée par ce backend tant qu'aucune représentation compactée réelle n'est implémentée.
|
||||
|
||||
## 11. Diagnostics et erreurs
|
||||
|
||||
Les snapshots et erreurs de façade n'exposent ni URI, host, user, database, SQL, handle backend, valeur de bind ni texte d'erreur PostgreSQL.
|
||||
|
||||
Les codes Store utiles incluent notamment :
|
||||
|
||||
```text
|
||||
store.wrong_network
|
||||
@@ -245,17 +284,10 @@ store.postgres_page_limit_unsupported
|
||||
store.postgres_retention_compaction_unsupported
|
||||
```
|
||||
|
||||
Les conflits et queries invalides conservent les codes API acquis `store_api.raw_conflict` et `store_api.raw_query_invalid`.
|
||||
Les conflits et queries invalides utilisent les codes backend-neutral `store_api.raw_conflict` et `store_api.raw_query_invalid`.
|
||||
|
||||
## 8. Limite fonctionnelle actuelle
|
||||
## 12. Limites de la façade
|
||||
|
||||
La façade n'implémente pas encore les capabilities `RawAccount*`. Elles appartiennent à la vertical slice `0.3.4`.
|
||||
La façade ne fournit pas d'accès public au SQL, au pool, aux clients ou transactions PostgreSQL. Les capabilities `RawAccount*` réexportées par l'API commune ne sont pas encore dispatchées par `Store`.
|
||||
|
||||
## 9. Preuve finale `0.3.3`
|
||||
|
||||
La conformance de la façade est verrouillée par les tests publics/hardening avec et sans feature PostgreSQL. Le gate technique final `0.3.3-pre.011` a également rejoué le workspace complet et la preuve PostgreSQL réelle `RawTransaction` sur PostgreSQL 17.
|
||||
|
||||
Références durables :
|
||||
|
||||
- [`../../docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md`](../../docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md) ;
|
||||
- [`../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md`](../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md).
|
||||
La taille de page est une primitive de navigation. Les décisions de batch, priorité, backlog et scheduling appartiennent aux workers/jobs, pas à Store.
|
||||
|
||||
Reference in New Issue
Block a user