v0.3.3-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-store-postgres-lib/README.md -->
|
||||
<!-- version: 6 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# ksp-store-postgres-lib
|
||||
|
||||
@@ -83,7 +83,7 @@ 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.
|
||||
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`.
|
||||
|
||||
## Health et erreurs
|
||||
|
||||
@@ -165,7 +165,7 @@ Toute transition dont `expected` ou `target` vaut `Compacted` est rejetée avant
|
||||
|
||||
La crate ne contient encore :
|
||||
|
||||
- aucune implémentation complète des six traits `RawTransaction*` de `ksp-store-api` ;
|
||||
- aucune capability `RawAccount*` ;
|
||||
- aucun dispatch métier dans `ksp-store-lib` ;
|
||||
- aucune implémentation PostgreSQL des capabilities `RawAccount*` ;
|
||||
- aucune orchestration worker/job ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-store-postgres-lib/USAGE.md -->
|
||||
<!-- version: 6 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Utilisation de ksp-store-postgres-lib
|
||||
|
||||
@@ -219,14 +219,21 @@ Les transitions physiques supportées sont `Full -> Archived` puis `Archived ->
|
||||
|
||||
Une transition impliquant `Compacted` est rejetée avant acquisition du pool avec `PostgresBackendErrorKind::RetentionCompactionUnsupported`. Le code KSP correspondant est `ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED`; aucune compression transparente PostgreSQL n'est présentée comme une représentation compactée KSP.
|
||||
|
||||
## 11. Ce que cette crate ne permet pas encore
|
||||
## 11. Capabilities traitées directement
|
||||
|
||||
La tranche ne fournit pas encore :
|
||||
Depuis `0.3.3-pre.008`, `PostgresBackend` implémente directement :
|
||||
|
||||
```text
|
||||
implémentations complètes des six traits RawTransaction*
|
||||
dispatch métier ksp-store-lib
|
||||
capabilities RawAccount*
|
||||
RawTransactionRead
|
||||
RawTransactionWrite
|
||||
RawTransactionObservationRead
|
||||
RawTransactionObservationWrite
|
||||
RawTransactionRetentionRead
|
||||
RawTransactionRetentionWrite
|
||||
```
|
||||
|
||||
Ces surfaces sont ajoutées dans les prereleases suivantes avant le dispatch `ksp-store-lib`.
|
||||
Cette conformance est principalement utile aux tests backend et à la façade. Le consumer applicatif normal continue à dépendre de `ksp-store-lib`, qui dispatch les mêmes six capabilities sans exposer `PostgresBackend`.
|
||||
|
||||
## 12. Ce que cette crate ne permet pas encore
|
||||
|
||||
La tranche ne fournit pas les capabilities `RawAccount*`. Elles appartiennent à `0.3.4`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/lib.rs
|
||||
// version: 13
|
||||
// version: 14
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -19,6 +19,8 @@
|
||||
//! pagination with a fixed opaque cursor bound to network, range and direction.
|
||||
//! `0.3.3-pre.007` adds atomic `Full -> Archived -> Purged` retention transitions
|
||||
//! with compare-and-transition outcomes and explicit rejection of `Compacted`.
|
||||
//! `0.3.3-pre.008` implements all six `RawTransaction*` capabilities directly on
|
||||
//! `PostgresBackend` while preserving the existing narrow backend bridge.
|
||||
//!
|
||||
//! This crate depends on `ksp-store-api` and never on `ksp-store-lib`. The
|
||||
//! common facade consumes only this crate's narrow backend bridge and never
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/runtime.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
const APPLICATION_NAME: &str = "ksp-store";
|
||||
const MAX_CONNECTION_URI_BYTES: usize = 4_096;
|
||||
@@ -428,6 +428,126 @@ impl std::fmt::Debug for PostgresBackend {
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionRead for PostgresBackend {
|
||||
fn get_raw_transaction<'a>(
|
||||
&'a self,
|
||||
reference: &'a ksp_store_api::RawTransactionReference,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawTransaction>>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::get_raw_transaction(self, reference).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
|
||||
fn list_raw_transactions<'a>(
|
||||
&'a self,
|
||||
query: &'a ksp_store_api::RawTransactionQuery,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawPage<ksp_store_api::RawTransactionReference>>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::list_raw_transactions(self, query).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionWrite for PostgresBackend {
|
||||
fn persist_raw_transaction_acquisition<'a>(
|
||||
&'a self,
|
||||
transaction: ksp_store_api::RawTransaction,
|
||||
observation: ksp_store_api::RawTransactionObservation,
|
||||
mode: ksp_store_api::RawTransactionAcquisitionMode,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawAcquisitionWriteOutcome>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::persist_raw_transaction_acquisition(self, transaction, observation, mode).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionObservationRead for PostgresBackend {
|
||||
fn get_raw_transaction_observation<'a>(
|
||||
&'a self,
|
||||
observation_key: &'a ksp_store_api::RawObservationKey,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawTransactionObservation>>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::get_raw_transaction_observation(self, observation_key).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionObservationWrite for PostgresBackend {
|
||||
fn record_raw_transaction_observation<'a>(
|
||||
&'a self,
|
||||
observation: ksp_store_api::RawTransactionObservation,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawObservationWriteOutcome>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::record_raw_transaction_observation(self, observation).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionRetentionRead for PostgresBackend {
|
||||
fn get_raw_transaction_retention_state<'a>(
|
||||
&'a self,
|
||||
reference: &'a ksp_store_api::RawTransactionReference,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawRetentionState>>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::get_raw_transaction_retention_state(self, reference).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
|
||||
fn get_raw_transaction_tombstone<'a>(
|
||||
&'a self,
|
||||
reference: &'a ksp_store_api::RawTransactionReference,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawTransactionTombstone>>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::get_raw_transaction_tombstone(self, reference).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionRetentionWrite for PostgresBackend {
|
||||
fn transition_raw_transaction_retention<'a>(
|
||||
&'a self,
|
||||
transition: ksp_store_api::RawTransactionRetentionTransition,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawRetentionWriteOutcome>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::transition_raw_transaction_retention(self, transition).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn map_capability_error(error: crate::PostgresBackendError) -> ksp_store_api::Error {
|
||||
let code = match error.kind() {
|
||||
crate::PostgresBackendErrorKind::ConfigInvalid => ksp_store_api::ErrorCode::new("store", "postgres_config_invalid"),
|
||||
crate::PostgresBackendErrorKind::ConnectFailed => ksp_store_api::ErrorCode::new("store", "postgres_connect_failed"),
|
||||
crate::PostgresBackendErrorKind::Conflict => ksp_store_api::ERROR_CODE_RAW_CONFLICT,
|
||||
crate::PostgresBackendErrorKind::DataInvalid => ksp_store_api::ErrorCode::new("store", "postgres_data_invalid"),
|
||||
crate::PostgresBackendErrorKind::HealthFailed => ksp_store_api::ErrorCode::new("store", "postgres_health_failed"),
|
||||
crate::PostgresBackendErrorKind::MigrationFailed => ksp_store_api::ErrorCode::new("store", "postgres_migration_failed"),
|
||||
crate::PostgresBackendErrorKind::MigrationMismatch => ksp_store_api::ErrorCode::new("store", "postgres_migration_mismatch"),
|
||||
crate::PostgresBackendErrorKind::PageLimitUnsupported => ksp_store_api::ErrorCode::new("store", "postgres_page_limit_unsupported"),
|
||||
crate::PostgresBackendErrorKind::PoolTimeout => ksp_store_api::ErrorCode::new("store", "postgres_pool_timeout"),
|
||||
crate::PostgresBackendErrorKind::QueryInvalid => ksp_store_api::ERROR_CODE_RAW_QUERY_INVALID,
|
||||
crate::PostgresBackendErrorKind::ReadFailed => ksp_store_api::ErrorCode::new("store", "postgres_read_failed"),
|
||||
crate::PostgresBackendErrorKind::ReferenceNotFound => ksp_store_api::ErrorCode::new("store", "raw_reference_not_found"),
|
||||
crate::PostgresBackendErrorKind::RetentionCompactionUnsupported => crate::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED,
|
||||
crate::PostgresBackendErrorKind::SchemaNewer => ksp_store_api::ErrorCode::new("store", "postgres_schema_newer"),
|
||||
crate::PostgresBackendErrorKind::ShutdownTimeout => ksp_store_api::ErrorCode::new("store", "shutdown_timeout"),
|
||||
crate::PostgresBackendErrorKind::TlsFailed => ksp_store_api::ErrorCode::new("store", "postgres_tls_failed"),
|
||||
crate::PostgresBackendErrorKind::WriteFailed => ksp_store_api::ErrorCode::new("store", "postgres_write_failed"),
|
||||
crate::PostgresBackendErrorKind::WrongNetwork => ksp_store_api::ErrorCode::new("store", "wrong_network"),
|
||||
};
|
||||
return ksp_store_api::Error::new(code, "PostgreSQL Store capability operation failed")
|
||||
.with_context("backend", "postgres")
|
||||
.with_context("phase", error.phase());
|
||||
}
|
||||
|
||||
impl std::ops::Drop for PostgresBackend {
|
||||
fn drop(&mut self) {
|
||||
self.pool.close();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/dependency_boundary.rs
|
||||
// version: 13
|
||||
// version: 14
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -248,3 +248,27 @@ fn pre_007_raw_retention_is_atomic_compare_and_transition_without_fake_compactio
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_backend_trait_implementations_stay_in_runtime_bridge_and_raw_account_scope_stays_closed() {
|
||||
let runtime = include_str!("../src/runtime.rs");
|
||||
for required in [
|
||||
"impl ksp_store_api::RawTransactionRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawTransactionWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawTransactionObservationRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawTransactionObservationWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawTransactionRetentionRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawTransactionRetentionWrite for PostgresBackend",
|
||||
] {
|
||||
assert!(runtime.contains(required), "missing pre.008 PostgreSQL capability implementation: {required}");
|
||||
}
|
||||
for forbidden in [
|
||||
"impl ksp_store_api::RawAccountStateRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountObservationRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountObservationWrite for PostgresBackend",
|
||||
] {
|
||||
assert!(!runtime.contains(forbidden), "pre.008 opened RawAccount capability scope prematurely: {forbidden}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -183,7 +183,7 @@ fn pre_009_backend_error_bridge_cannot_retain_external_error_or_secret_text() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_009_backend_has_no_env_bypass_or_direct_store_trait_implementation() {
|
||||
fn pre_009_backend_has_no_env_bypass_reverse_facade_edge_or_raw_account_trait_implementation() {
|
||||
let production = std::format!(
|
||||
"{}
|
||||
{}
|
||||
@@ -219,10 +219,9 @@ fn pre_009_backend_has_no_env_bypass_or_direct_store_trait_implementation() {
|
||||
"ksp_store_lib",
|
||||
"ksp_config_lib",
|
||||
"sqlx::",
|
||||
"impl ksp_store_api::RawTransaction",
|
||||
"impl ksp_store_api::RawAccount",
|
||||
] {
|
||||
assert!(!production.contains(forbidden), "forbidden backend ownership/direct-trait material detected: {forbidden}");
|
||||
assert!(!production.contains(forbidden), "forbidden backend ownership/reverse-edge/RawAccount material detected: {forbidden}");
|
||||
}
|
||||
let bootstrap_sql = include_str!("../migrations/v000_bootstrap/tables/001_ksp_store_schema_migrations.sql");
|
||||
assert!(bootstrap_sql.contains("ksp_store_schema_migrations"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/public_api.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -102,3 +102,21 @@ fn pre_007_raw_retention_write_bridge_uses_backend_independent_transition_and_ou
|
||||
let _transition = ksp_store_postgres_lib::PostgresBackend::transition_raw_transaction_retention;
|
||||
return;
|
||||
}
|
||||
|
||||
fn assert_raw_transaction_capabilities<T>()
|
||||
where
|
||||
T: ksp_store_api::RawTransactionRead
|
||||
+ ksp_store_api::RawTransactionWrite
|
||||
+ ksp_store_api::RawTransactionObservationRead
|
||||
+ ksp_store_api::RawTransactionObservationWrite
|
||||
+ ksp_store_api::RawTransactionRetentionRead
|
||||
+ ksp_store_api::RawTransactionRetentionWrite,
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_postgres_backend_implements_all_six_raw_transaction_capabilities() {
|
||||
assert_raw_transaction_capabilities::<ksp_store_postgres_lib::PostgresBackend>();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/unit_tests/runtime.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
fn network() -> ksp_store_api::RawNetworkId {
|
||||
return match ksp_store_api::RawNetworkId::new("devnet") {
|
||||
@@ -100,3 +100,27 @@ fn libpq_server_options_are_rejected_in_foundation_runtime() {
|
||||
assert_eq!(error.map(|value| return value.phase()), std::option::Option::Some("server_options"));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_capability_error_mapping_uses_stable_store_and_store_api_codes() {
|
||||
let cases = [
|
||||
(crate::PostgresBackendErrorKind::Conflict, ksp_store_api::ERROR_CODE_RAW_CONFLICT),
|
||||
(crate::PostgresBackendErrorKind::DataInvalid, ksp_store_api::ErrorCode::new("store", "postgres_data_invalid")),
|
||||
(crate::PostgresBackendErrorKind::PageLimitUnsupported, ksp_store_api::ErrorCode::new("store", "postgres_page_limit_unsupported")),
|
||||
(crate::PostgresBackendErrorKind::QueryInvalid, ksp_store_api::ERROR_CODE_RAW_QUERY_INVALID),
|
||||
(crate::PostgresBackendErrorKind::ReadFailed, ksp_store_api::ErrorCode::new("store", "postgres_read_failed")),
|
||||
(crate::PostgresBackendErrorKind::ReferenceNotFound, ksp_store_api::ErrorCode::new("store", "raw_reference_not_found")),
|
||||
(crate::PostgresBackendErrorKind::RetentionCompactionUnsupported, crate::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED),
|
||||
(crate::PostgresBackendErrorKind::WriteFailed, ksp_store_api::ErrorCode::new("store", "postgres_write_failed")),
|
||||
(crate::PostgresBackendErrorKind::WrongNetwork, ksp_store_api::ErrorCode::new("store", "wrong_network")),
|
||||
];
|
||||
for (kind, expected) in cases {
|
||||
let backend = crate::PostgresBackendError::new(kind, "pre_008_canary");
|
||||
let mapped = super::map_capability_error(backend);
|
||||
assert_eq!(mapped.code(), expected);
|
||||
let rendered = std::format!("{mapped:?}");
|
||||
assert!(!rendered.contains("postgresql://"));
|
||||
assert!(!rendered.contains("SELECT "));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user