v0.3.3-pre.008

This commit is contained in:
2026-08-30 14:10:10 +02:00
parent 1f0b202135
commit 84ab2b3651
21 changed files with 816 additions and 62 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-store-lib/README.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# ksp-store-lib
@@ -19,6 +19,8 @@ Elle expose aux consumers une surface backend-neutral, réexporte les contrats R
- `Store::health().await` pour la readiness portable et bornée ;
- `Store::close(self).await` pour la fermeture explicite bornée ;
- le mapping des erreurs backend vers des codes Store stables sans exposer les erreurs physiques ;
- les six capabilities `RawTransaction*` dispatchées vers le backend compilé ;
- une validation réseau backend-neutral avant dispatch pour toutes les opérations qui portent explicitement un réseau ;
- les réexports crate-root de `ksp-store-api` nécessaires aux consumers ordinaires.
## Une instance = un réseau
@@ -73,17 +75,27 @@ Les credentials restent dans les variables `KSP_SECRET_STORE_*_POSTGRES_URI` ou
## Surface actuelle et hors périmètre
La fondation runtime ne fournit encore aucune implémentation PostgreSQL des capabilities métier RAW de `ksp-store-api`.
Depuis `0.3.3-pre.008`, `Store` implémente les six capabilities transactionnelles acquises dans `ksp-store-api` :
```text
RawTransactionRead
RawTransactionWrite
RawTransactionObservationRead
RawTransactionObservationWrite
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`.
Sont volontairement hors de cette surface :
- persistence/query/rétention PostgreSQL de `RawTransaction` ;
- persistence/query/rétention PostgreSQL de `RawAccountState` ;
- batch-size, priorité, backlog ou policy de worker/job ;
- transport d'acquisition, Program decoding et materialization ;
- exposition publique de SQL, pool, client, row, statement ou transaction PostgreSQL.
Les premières vertical slices métier sont ajoutées séparément afin que la façade runtime reste stable et backend-neutral.
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

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-store-lib/USAGE.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Utilisation de ksp-store-lib
@@ -160,7 +160,8 @@ recycle_timeout()
Valeurs par défaut :
```text
auto_migrate true
schema_autocreate true
schema_autoupdate true
migration_timeout 30 s
migration_lock_timeout 10 s
```
@@ -168,11 +169,14 @@ migration_lock_timeout 10 s
Getters :
```text
auto_migrate()
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 :
@@ -212,8 +216,37 @@ runtime snapshot
Aucun snapshot n'expose URI, host, user, database, SQL, handle backend ou texte d'erreur PostgreSQL.
## 7. Limite fonctionnelle actuelle
## 7. Utiliser les capabilities RawTransaction
`ksp-store-lib` réexporte les modèles et traits RAW de `ksp-store-api`, mais le backend PostgreSQL de la fondation n'implémente encore aucune capability `RawTransaction*` ou `RawAccount*`.
Depuis `0.3.3-pre.008`, `Store` implémente directement les six traits `RawTransaction*`. Le consumer importe le trait correspondant puis appelle la méthode sur la façade :
Les consumers ne doivent donc pas interpréter la disponibilité du runtime PostgreSQL comme une persistence métier déjà présente.
```rust
use ksp_store_lib::RawTransactionRead;
async fn read_transaction(
store: &ksp_store_lib::Store,
reference: &ksp_store_lib::RawTransactionReference,
) -> ksp_store_lib::Result<std::option::Option<ksp_store_lib::RawTransaction>> {
return store.get_raw_transaction(reference).await;
}
```
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.
Codes runtime principaux :
```text
store.wrong_network
store.raw_reference_not_found
store.postgres_read_failed
store.postgres_write_failed
store.postgres_data_invalid
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`.
## 8. Limite fonctionnelle actuelle
La façade n'implémente pas encore les capabilities `RawAccount*`. Elles appartiennent à la vertical slice `0.3.4`.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-lib/src/error.rs
// version: 5
// version: 6
/// Error code reserved for operations attempted after a Store backend has entered its closed state.
pub const ERROR_CODE_BACKEND_CLOSED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "backend_closed");
@@ -11,14 +11,20 @@ pub const ERROR_CODE_BACKEND_OPEN_FAILED: ksp_store_api::ErrorCode = ksp_store_a
pub const ERROR_CODE_POSTGRES_CONFIG_INVALID: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_config_invalid");
/// Error code used when PostgreSQL physical connection establishment fails without exposing remote or credential details.
pub const ERROR_CODE_POSTGRES_CONNECT_FAILED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_connect_failed");
/// Error code used when PostgreSQL returns persisted RAW data incompatible with the stable Store contract.
pub const ERROR_CODE_POSTGRES_DATA_INVALID: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_data_invalid");
/// Error code used when a lightweight PostgreSQL health/readiness probe fails safely.
pub const ERROR_CODE_POSTGRES_HEALTH_FAILED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_health_failed");
/// Error code used when PostgreSQL migration/bootstrap execution fails without exposing server text or SQL.
pub const ERROR_CODE_POSTGRES_MIGRATION_FAILED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_migration_failed");
/// Error code used when persisted PostgreSQL migration history diverges from the embedded immutable KSP history.
pub const ERROR_CODE_POSTGRES_MIGRATION_MISMATCH: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_migration_mismatch");
/// Error code used when the requested RAW page size exceeds the exact PostgreSQL LIMIT representation boundary.
pub const ERROR_CODE_POSTGRES_PAGE_LIMIT_UNSUPPORTED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_page_limit_unsupported");
/// Error code used when a bounded PostgreSQL pool wait, create or recycle operation reaches its deadline.
pub const ERROR_CODE_POSTGRES_POOL_TIMEOUT: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_pool_timeout");
/// Error code used when a PostgreSQL RAW read fails without exposing SQL, bind values or server text.
pub const ERROR_CODE_POSTGRES_READ_FAILED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_read_failed");
/// Error code used when PostgreSQL cannot represent a requested RAW retention compaction state.
pub const ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED: ksp_store_api::ErrorCode =
ksp_store_api::ErrorCode::new("store", "postgres_retention_compaction_unsupported");
@@ -26,7 +32,13 @@ pub const ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED: ksp_store_api::E
pub const ERROR_CODE_POSTGRES_SCHEMA_NEWER: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_schema_newer");
/// Error code used when verified PostgreSQL TLS setup or negotiation cannot be completed safely.
pub const ERROR_CODE_POSTGRES_TLS_FAILED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_tls_failed");
/// Error code used when a PostgreSQL RAW write fails without exposing SQL, bind values or server text.
pub const ERROR_CODE_POSTGRES_WRITE_FAILED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_write_failed");
/// Error code used when a RAW write requires a canonical reference that is not durable.
pub const ERROR_CODE_RAW_REFERENCE_NOT_FOUND: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "raw_reference_not_found");
/// Error code used when backend-neutral Store settings violate runtime bounds or invariants.
pub const ERROR_CODE_SETTINGS_INVALID: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "settings_invalid");
/// Error code used when a Store cannot complete its explicit shutdown inside the configured bound.
pub const ERROR_CODE_SHUTDOWN_TIMEOUT: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "shutdown_timeout");
/// Error code used when a network-scoped Store operation targets a network different from the opened Store binding.
pub const ERROR_CODE_WRONG_NETWORK: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "wrong_network");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-lib/src/lib.rs
// version: 7
// version: 8
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -7,10 +7,10 @@
//! Common backend-neutral Store runtime facade for KSP.
//!
//! `0.3.2-pre.007` closes the physical PostgreSQL runtime composition with a
//! portable safe runtime snapshot and lightweight health/readiness projection,
//! while retaining the private migration/bootstrap foundation and no business
//! persistence schema.
//! The runtime facade owns backend selection, lifecycle, safe diagnostics and
//! backend-neutral capability dispatch. `0.3.3-pre.008` completes the PostgreSQL
//! `RawTransaction` vertical slice by implementing the six transaction capabilities
//! on both the physical backend and this common facade without exposing physical types.
//!
//! The default `postgres` feature compiles the official PostgreSQL backend as
//! an optional implementation dependency. No backend implementation type is
@@ -32,24 +32,36 @@ pub use self::error::ERROR_CODE_BACKEND_OPEN_FAILED;
pub use self::error::ERROR_CODE_POSTGRES_CONFIG_INVALID;
/// Error code used when PostgreSQL physical connection establishment fails.
pub use self::error::ERROR_CODE_POSTGRES_CONNECT_FAILED;
/// Error code used when PostgreSQL returns persisted RAW data incompatible with the Store contract.
pub use self::error::ERROR_CODE_POSTGRES_DATA_INVALID;
/// Error code used when a lightweight PostgreSQL health/readiness probe fails safely.
pub use self::error::ERROR_CODE_POSTGRES_HEALTH_FAILED;
/// Error code used when PostgreSQL migration/bootstrap execution fails safely.
pub use self::error::ERROR_CODE_POSTGRES_MIGRATION_FAILED;
/// Error code used when PostgreSQL migration history diverges from the embedded immutable KSP history.
pub use self::error::ERROR_CODE_POSTGRES_MIGRATION_MISMATCH;
/// Error code used when a requested RAW page size exceeds PostgreSQL's exact physical LIMIT boundary.
pub use self::error::ERROR_CODE_POSTGRES_PAGE_LIMIT_UNSUPPORTED;
/// Error code used when a bounded PostgreSQL pool operation reaches its deadline.
pub use self::error::ERROR_CODE_POSTGRES_POOL_TIMEOUT;
/// Error code used when a PostgreSQL RAW read statement fails safely.
pub use self::error::ERROR_CODE_POSTGRES_READ_FAILED;
/// Error code used when PostgreSQL cannot represent a requested RAW retention compaction state.
pub use self::error::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED;
/// Error code used when PostgreSQL schema history is newer than this Store runtime.
pub use self::error::ERROR_CODE_POSTGRES_SCHEMA_NEWER;
/// Error code used when PostgreSQL verified TLS setup or negotiation fails.
pub use self::error::ERROR_CODE_POSTGRES_TLS_FAILED;
/// Error code used when a PostgreSQL RAW write statement or transaction fails safely.
pub use self::error::ERROR_CODE_POSTGRES_WRITE_FAILED;
/// Error code used when a RAW write requires a canonical reference that is not durable.
pub use self::error::ERROR_CODE_RAW_REFERENCE_NOT_FOUND;
/// Error code used when Store settings violate backend-neutral bounds or invariants.
pub use self::error::ERROR_CODE_SETTINGS_INVALID;
/// Error code used when explicit Store shutdown exceeds its configured deadline.
pub use self::error::ERROR_CODE_SHUTDOWN_TIMEOUT;
/// Error code used when a network-scoped operation targets a network different from the Store binding.
pub use self::error::ERROR_CODE_WRONG_NETWORK;
/// Portable Store health/readiness projection containing only safe diagnostics.
pub use self::health::StoreHealthSnapshot;
/// Portable Store health state independent from physical backend types.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-lib/src/store.rs
// version: 5
// version: 6
/// Opaque common Store runtime facade.
///
@@ -116,6 +116,261 @@ impl std::fmt::Debug for Store {
}
}
impl ksp_store_api::RawTransactionRead for Store {
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>>> {
let network_check = validate_operation_network(&self.network, reference.network(), self.backend_kind);
if let std::result::Result::Err(error) = network_check {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
return std::boxed::Box::pin(async move {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.get_raw_transaction(reference).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = reference;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
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>>> {
let network_check = validate_operation_network(&self.network, query.network(), self.backend_kind);
if let std::result::Result::Err(error) = network_check {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
return std::boxed::Box::pin(async move {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.list_raw_transactions(query).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = query;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
}
impl ksp_store_api::RawTransactionWrite for Store {
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>> {
let transaction_network = validate_operation_network(&self.network, transaction.reference().network(), self.backend_kind);
if let std::result::Result::Err(error) = transaction_network {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
let observation_network = validate_operation_network(&self.network, observation.transaction().network(), self.backend_kind);
if let std::result::Result::Err(error) = observation_network {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
return std::boxed::Box::pin(async move {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.persist_raw_transaction_acquisition(transaction, observation, mode).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = transaction;
let _ = observation;
let _ = mode;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
}
impl ksp_store_api::RawTransactionObservationRead for Store {
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 {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.get_raw_transaction_observation(observation_key).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = observation_key;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
}
impl ksp_store_api::RawTransactionObservationWrite for Store {
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>> {
let network_check = validate_operation_network(&self.network, observation.transaction().network(), self.backend_kind);
if let std::result::Result::Err(error) = network_check {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
return std::boxed::Box::pin(async move {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.record_raw_transaction_observation(observation).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = observation;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
}
impl ksp_store_api::RawTransactionRetentionRead for Store {
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>>> {
let network_check = validate_operation_network(&self.network, reference.network(), self.backend_kind);
if let std::result::Result::Err(error) = network_check {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
return std::boxed::Box::pin(async move {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.get_raw_transaction_retention_state(reference).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = reference;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
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>>> {
let network_check = validate_operation_network(&self.network, reference.network(), self.backend_kind);
if let std::result::Result::Err(error) = network_check {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
return std::boxed::Box::pin(async move {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.get_raw_transaction_tombstone(reference).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = reference;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
}
impl ksp_store_api::RawTransactionRetentionWrite for Store {
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>> {
let network_check = validate_operation_network(&self.network, transition.reference().network(), self.backend_kind);
if let std::result::Result::Err(error) = network_check {
return std::boxed::Box::pin(async move {
return std::result::Result::Err(error);
});
}
return std::boxed::Box::pin(async move {
#[cfg(feature = "postgres")]
{
return match &self.runtime {
StoreRuntime::Postgres(backend) => {
let result = backend.transition_raw_transaction_retention(transition).await;
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
},
};
}
#[cfg(not(feature = "postgres"))]
{
let _ = transition;
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
}
});
}
}
fn validate_operation_network(
store_network: &ksp_store_api::RawNetworkId,
operation_network: &ksp_store_api::RawNetworkId,
backend_kind: crate::StoreBackendKind,
) -> ksp_store_api::Result<()> {
if store_network != operation_network {
return std::result::Result::Err(
ksp_store_api::Error::new(crate::ERROR_CODE_WRONG_NETWORK, "Store operation targeted a different logical network")
.with_context("backend", backend_kind.code())
.with_context("network", store_network.as_str()),
);
}
return std::result::Result::Ok(());
}
#[cfg(feature = "postgres")]
enum StoreRuntime {
Postgres(ksp_store_postgres_lib::PostgresBackend),
@@ -176,7 +431,7 @@ async fn open_postgres(
#[cfg(feature = "postgres")]
fn map_postgres_error(error: ksp_store_postgres_lib::PostgresBackendError, backend_kind: crate::StoreBackendKind, network: &str) -> ksp_store_api::Error {
let code = postgres_error_code(error.kind());
return ksp_store_api::Error::new(code, "PostgreSQL Store backend lifecycle operation failed")
return ksp_store_api::Error::new(code, "PostgreSQL Store backend operation failed")
.with_context("backend", backend_kind.code())
.with_context("network", network)
.with_context("phase", error.phase());
@@ -215,13 +470,22 @@ fn postgres_error_code(kind: ksp_store_postgres_lib::PostgresBackendErrorKind) -
return match kind {
ksp_store_postgres_lib::PostgresBackendErrorKind::ConfigInvalid => crate::ERROR_CODE_POSTGRES_CONFIG_INVALID,
ksp_store_postgres_lib::PostgresBackendErrorKind::ConnectFailed => crate::ERROR_CODE_POSTGRES_CONNECT_FAILED,
ksp_store_postgres_lib::PostgresBackendErrorKind::Conflict => ksp_store_api::ERROR_CODE_RAW_CONFLICT,
ksp_store_postgres_lib::PostgresBackendErrorKind::DataInvalid => crate::ERROR_CODE_POSTGRES_DATA_INVALID,
ksp_store_postgres_lib::PostgresBackendErrorKind::HealthFailed => crate::ERROR_CODE_POSTGRES_HEALTH_FAILED,
ksp_store_postgres_lib::PostgresBackendErrorKind::PoolTimeout => crate::ERROR_CODE_POSTGRES_POOL_TIMEOUT,
ksp_store_postgres_lib::PostgresBackendErrorKind::MigrationFailed => crate::ERROR_CODE_POSTGRES_MIGRATION_FAILED,
ksp_store_postgres_lib::PostgresBackendErrorKind::MigrationMismatch => crate::ERROR_CODE_POSTGRES_MIGRATION_MISMATCH,
ksp_store_postgres_lib::PostgresBackendErrorKind::PageLimitUnsupported => crate::ERROR_CODE_POSTGRES_PAGE_LIMIT_UNSUPPORTED,
ksp_store_postgres_lib::PostgresBackendErrorKind::QueryInvalid => ksp_store_api::ERROR_CODE_RAW_QUERY_INVALID,
ksp_store_postgres_lib::PostgresBackendErrorKind::ReadFailed => crate::ERROR_CODE_POSTGRES_READ_FAILED,
ksp_store_postgres_lib::PostgresBackendErrorKind::ReferenceNotFound => crate::ERROR_CODE_RAW_REFERENCE_NOT_FOUND,
ksp_store_postgres_lib::PostgresBackendErrorKind::RetentionCompactionUnsupported => crate::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED,
ksp_store_postgres_lib::PostgresBackendErrorKind::SchemaNewer => crate::ERROR_CODE_POSTGRES_SCHEMA_NEWER,
ksp_store_postgres_lib::PostgresBackendErrorKind::ShutdownTimeout => crate::ERROR_CODE_SHUTDOWN_TIMEOUT,
ksp_store_postgres_lib::PostgresBackendErrorKind::TlsFailed => crate::ERROR_CODE_POSTGRES_TLS_FAILED,
ksp_store_postgres_lib::PostgresBackendErrorKind::WriteFailed => crate::ERROR_CODE_POSTGRES_WRITE_FAILED,
ksp_store_postgres_lib::PostgresBackendErrorKind::WrongNetwork => crate::ERROR_CODE_WRONG_NETWORK,
_ => crate::ERROR_CODE_BACKEND_OPEN_FAILED,
};
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-lib/tests/dependency_boundary.rs
// version: 6
// version: 7
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -58,3 +58,36 @@ fn pre_005_facade_exposes_no_physical_postgres_types_or_environment_bypass() {
}
return;
}
#[test]
fn pre_008_facade_dispatches_six_raw_transaction_capabilities_without_physical_leak() {
let store = include_str!("../src/store.rs");
for required in [
"impl ksp_store_api::RawTransactionRead for Store",
"impl ksp_store_api::RawTransactionWrite for Store",
"impl ksp_store_api::RawTransactionObservationRead for Store",
"impl ksp_store_api::RawTransactionObservationWrite for Store",
"impl ksp_store_api::RawTransactionRetentionRead for Store",
"impl ksp_store_api::RawTransactionRetentionWrite for Store",
"validate_operation_network",
"StoreRuntime::Postgres(backend)",
"map_postgres_error",
] {
assert!(store.contains(required), "missing pre.008 Store capability dispatch contract: {required}");
}
for forbidden in [
"impl ksp_store_api::RawAccountStateRead for Store",
"impl ksp_store_api::RawAccountStateWrite for Store",
"impl ksp_store_api::RawAccountObservationRead for Store",
"impl ksp_store_api::RawAccountObservationWrite for Store",
"tokio_postgres::",
"deadpool_postgres::",
"CREATE TABLE",
"INSERT INTO",
"UPDATE ksp_",
"DELETE FROM",
] {
assert!(!store.contains(forbidden), "pre.008 facade leaked physical or RawAccount scope: {forbidden}");
}
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-lib/tests/hardening_completeness.rs
// version: 2
// version: 3
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -98,21 +98,27 @@ fn pre_009_facade_modules_and_crate_root_exports_are_exact() {
"ERROR_CODE_BACKEND_OPEN_FAILED",
"ERROR_CODE_POSTGRES_CONFIG_INVALID",
"ERROR_CODE_POSTGRES_CONNECT_FAILED",
"ERROR_CODE_POSTGRES_DATA_INVALID",
"ERROR_CODE_POSTGRES_HEALTH_FAILED",
"ERROR_CODE_POSTGRES_MIGRATION_FAILED",
"ERROR_CODE_POSTGRES_MIGRATION_MISMATCH",
"ERROR_CODE_POSTGRES_PAGE_LIMIT_UNSUPPORTED",
"ERROR_CODE_POSTGRES_POOL_TIMEOUT",
"ERROR_CODE_POSTGRES_READ_FAILED",
"ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED",
"ERROR_CODE_POSTGRES_SCHEMA_NEWER",
"ERROR_CODE_POSTGRES_TLS_FAILED",
"ERROR_CODE_POSTGRES_WRITE_FAILED",
"ERROR_CODE_RAW_CONFLICT",
"ERROR_CODE_RAW_MODEL_INVALID",
"ERROR_CODE_RAW_PAYLOAD_INVALID",
"ERROR_CODE_RAW_PROVENANCE_INVALID",
"ERROR_CODE_RAW_QUERY_INVALID",
"ERROR_CODE_RAW_REFERENCE_NOT_FOUND",
"ERROR_CODE_RAW_RETENTION_INVALID",
"ERROR_CODE_SETTINGS_INVALID",
"ERROR_CODE_SHUTDOWN_TIMEOUT",
"ERROR_CODE_WRONG_NETWORK",
"Error",
"ErrorCode",
"ErrorContext",
@@ -181,7 +187,7 @@ fn pre_009_facade_modules_and_crate_root_exports_are_exact() {
];
expected.sort_unstable();
assert_eq!(actual.as_slice(), expected.as_slice());
assert_eq!(actual.len(), 85);
assert_eq!(actual.len(), 91);
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-lib/tests/public_api.rs
// version: 6
// version: 7
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -38,15 +38,21 @@ fn pre_005_common_and_postgres_error_codes_are_stable_and_store_owned() {
assert_eq!(ksp_store_lib::ERROR_CODE_BACKEND_OPEN_FAILED.code(), "backend_open_failed");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_CONFIG_INVALID.code(), "postgres_config_invalid");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_CONNECT_FAILED.code(), "postgres_connect_failed");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_DATA_INVALID.code(), "postgres_data_invalid");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_HEALTH_FAILED.code(), "postgres_health_failed");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_POOL_TIMEOUT.code(), "postgres_pool_timeout");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_READ_FAILED.code(), "postgres_read_failed");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED.code(), "postgres_retention_compaction_unsupported");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_MIGRATION_FAILED.code(), "postgres_migration_failed");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_MIGRATION_MISMATCH.code(), "postgres_migration_mismatch");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_PAGE_LIMIT_UNSUPPORTED.code(), "postgres_page_limit_unsupported");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_SCHEMA_NEWER.code(), "postgres_schema_newer");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_TLS_FAILED.code(), "postgres_tls_failed");
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_WRITE_FAILED.code(), "postgres_write_failed");
assert_eq!(ksp_store_lib::ERROR_CODE_RAW_REFERENCE_NOT_FOUND.code(), "raw_reference_not_found");
assert_eq!(ksp_store_lib::ERROR_CODE_BACKEND_CLOSED.code(), "backend_closed");
assert_eq!(ksp_store_lib::ERROR_CODE_SHUTDOWN_TIMEOUT.code(), "shutdown_timeout");
assert_eq!(ksp_store_lib::ERROR_CODE_WRONG_NETWORK.code(), "wrong_network");
return;
}
@@ -67,3 +73,21 @@ fn pre_007_health_and_runtime_snapshot_types_are_portable_crate_root_contracts()
let _runtime = std::mem::size_of::<std::option::Option<ksp_store_lib::StoreRuntimeSnapshot>>();
return;
}
fn assert_raw_transaction_capabilities<T>()
where
T: ksp_store_lib::RawTransactionRead
+ ksp_store_lib::RawTransactionWrite
+ ksp_store_lib::RawTransactionObservationRead
+ ksp_store_lib::RawTransactionObservationWrite
+ ksp_store_lib::RawTransactionRetentionRead
+ ksp_store_lib::RawTransactionRetentionWrite,
{
return;
}
#[test]
fn pre_008_store_facade_implements_all_six_raw_transaction_capabilities() {
assert_raw_transaction_capabilities::<ksp_store_lib::Store>();
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-lib/unit_tests/store.rs
// version: 4
// version: 5
fn poll_ready<T>(future: impl std::future::Future<Output = T>) -> T {
let mut future = std::boxed::Box::pin(future);
@@ -55,3 +55,20 @@ fn known_postgres_without_feature_is_rejected_before_io() {
assert_eq!(error.map(|value| return value.code()), std::option::Option::Some(crate::ERROR_CODE_BACKEND_NOT_COMPILED));
return;
}
#[test]
fn pre_008_operation_network_guard_rejects_mismatch_without_echoing_requested_network() {
let store_network = valid_network();
let hostile = match crate::RawNetworkId::new("other-network") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid alternate network rejected: {error:?}"),
};
let result = super::validate_operation_network(&store_network, &hostile, crate::StoreBackendKind::Postgres);
let error = match result {
std::result::Result::Err(value) => value,
std::result::Result::Ok(()) => panic!("wrong operation network unexpectedly accepted"),
};
assert_eq!(error.code(), crate::ERROR_CODE_WRONG_NETWORK);
assert!(!std::format!("{error:?}").contains("other-network"));
return;
}