v0.3.1-pre.005

This commit is contained in:
2026-08-29 08:45:21 +02:00
parent 28ca5bdac5
commit c83e3261d0
11 changed files with 552 additions and 36 deletions

View File

@@ -1,8 +1,18 @@
// file: crates/ksp-store-api/src/capability.rs
// version: 1
// version: 2
//! Private home for backend-agnostic Store capability contracts.
//!
//! `0.3.1-pre.002` establishes the ownership boundary only. Concrete read and
//! write capabilities are introduced after the RAW models they operate on are
//! defined; no backend/runtime contract belongs here.
//! Capabilities are split by persistent family and operation direction so a
//! backend can implement only the contracts it actually supports. The runtime
//! Store facade, backend selection and concrete database implementations remain
//! outside `ksp-store-api`.
pub(crate) mod raw_account;
pub(crate) mod raw_transaction;
/// Boxed async operation returned by object-safe Store capability contracts.
///
/// The alias uses only standard-library primitives so backend implementations
/// need no async helper dependency merely to implement `ksp-store-api`.
pub type StoreApiFuture<'a, T> = std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = T> + std::marker::Send + 'a>>;

View File

@@ -0,0 +1,49 @@
// file: crates/ksp-store-api/src/capability/raw_account.rs
// version: 1
/// Read capability for complete canonical RAW account states.
///
/// Implementations must return the common Store model without leaking backend
/// rows, SQL handles or acquisition transport types. Absence is represented by
/// `None`; backend/runtime failures use the common KSP error contract.
pub trait RawAccountStateRead: std::marker::Send + std::marker::Sync {
/// Reads one complete canonical RAW account state by durable reference.
fn get_raw_account_state<'a>(
&'a self,
reference: &'a crate::RawAccountStateReference,
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawAccountState>>>;
}
/// Write capability for complete RAW account-state acquisitions.
///
/// The account state and its acquisition observation form one logical
/// persistence operation. An implementation must not leave one side durable if
/// the other side fails. Detailed idempotence/conflict outcomes are introduced
/// by `0.3.1-pre.006`; this tranche exposes only success/failure.
pub trait RawAccountStateWrite: std::marker::Send + std::marker::Sync {
/// Persists one complete RAW account state together with one observation atomically.
fn persist_raw_account_acquisition<'a>(
&'a self,
state: crate::RawAccountState,
observation: crate::RawAccountObservation,
) -> crate::StoreApiFuture<'a, crate::Result<()>>;
}
/// Read capability for persisted RAW account-state observations.
pub trait RawAccountObservationRead: std::marker::Send + std::marker::Sync {
/// Reads one account observation by deterministic producer-owned idempotence key.
fn get_raw_account_observation<'a>(
&'a self,
observation_key: &'a crate::RawObservationKey,
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawAccountObservation>>>;
}
/// Write capability for an additional observation of an already persisted RAW account state.
///
/// This capability allows repeated HTTP/WS/gRPC acquisitions to be retained
/// without resubmitting account bytes. The referenced state must already exist;
/// detailed outcomes are deferred to `0.3.1-pre.006`.
pub trait RawAccountObservationWrite: std::marker::Send + std::marker::Sync {
/// Persists one additional acquisition observation for an existing RAW account state.
fn record_raw_account_observation<'a>(&'a self, observation: crate::RawAccountObservation) -> crate::StoreApiFuture<'a, crate::Result<()>>;
}

View File

@@ -0,0 +1,50 @@
// file: crates/ksp-store-api/src/capability/raw_transaction.rs
// version: 1
/// Read capability for canonical RAW transactions.
///
/// Implementations must return the canonical Store model without exposing
/// backend rows, SQL handles or transport-specific DTOs. Absence is represented
/// by `None`; backend/runtime failures use the common KSP error contract.
pub trait RawTransactionRead: std::marker::Send + std::marker::Sync {
/// Reads one canonical RAW transaction by durable backend-independent reference.
fn get_raw_transaction<'a>(
&'a self,
reference: &'a crate::RawTransactionReference,
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransaction>>>;
}
/// Write capability for canonical RAW transaction acquisitions.
///
/// The transaction and its acquisition observation form one logical persistence
/// operation. An implementation must not leave one side durable if the other
/// side fails. Detailed idempotence/conflict outcomes are introduced by
/// `0.3.1-pre.006`; this tranche exposes only success/failure.
pub trait RawTransactionWrite: std::marker::Send + std::marker::Sync {
/// Persists one complete RAW transaction together with one observation atomically.
fn persist_raw_transaction_acquisition<'a>(
&'a self,
transaction: crate::RawTransaction,
observation: crate::RawTransactionObservation,
) -> crate::StoreApiFuture<'a, crate::Result<()>>;
}
/// Read capability for persisted RAW transaction observations.
pub trait RawTransactionObservationRead: std::marker::Send + std::marker::Sync {
/// Reads one transaction observation by deterministic producer-owned idempotence key.
fn get_raw_transaction_observation<'a>(
&'a self,
observation_key: &'a crate::RawObservationKey,
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransactionObservation>>>;
}
/// Write capability for an additional observation of an already persisted RAW transaction.
///
/// This capability exists so repeated acquisitions can be recorded without
/// resubmitting the potentially large canonical transaction payload. The
/// referenced transaction must already exist; detailed outcomes are deferred to
/// `0.3.1-pre.006`.
pub trait RawTransactionObservationWrite: std::marker::Send + std::marker::Sync {
/// Persists one additional acquisition observation for an existing RAW transaction.
fn record_raw_transaction_observation<'a>(&'a self, observation: crate::RawTransactionObservation) -> crate::StoreApiFuture<'a, crate::Result<()>>;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-api/src/lib.rs
// version: 3
// version: 4
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -20,6 +20,24 @@ mod capability;
mod error;
mod model;
/// Boxed async operation returned by object-safe Store capability contracts.
pub use self::capability::StoreApiFuture;
/// Read capability for persisted RAW account-state observations.
pub use self::capability::raw_account::RawAccountObservationRead;
/// Write capability for additional observations of already persisted RAW account states.
pub use self::capability::raw_account::RawAccountObservationWrite;
/// Read capability for complete canonical RAW account states.
pub use self::capability::raw_account::RawAccountStateRead;
/// Write capability for complete canonical RAW account-state acquisitions.
pub use self::capability::raw_account::RawAccountStateWrite;
/// Read capability for persisted RAW transaction observations.
pub use self::capability::raw_transaction::RawTransactionObservationRead;
/// Write capability for additional observations of already persisted RAW transactions.
pub use self::capability::raw_transaction::RawTransactionObservationWrite;
/// Read capability for canonical RAW transactions.
pub use self::capability::raw_transaction::RawTransactionRead;
/// Write capability for canonical RAW transaction acquisitions.
pub use self::capability::raw_transaction::RawTransactionWrite;
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.

View File

@@ -1,10 +1,10 @@
// file: crates/ksp-store-api/tests/dependency_boundary.rs
// version: 3
// version: 4
//! Dependency canaries for the Store API RAW foundation.
#[test]
fn pre_004_manifest_keeps_exact_core_only_runtime_dependency() {
fn pre_005_manifest_keeps_exact_core_only_runtime_dependency() {
let manifest = include_str!("../Cargo.toml");
let dependencies_tail = manifest.split("[dependencies]").nth(1);
assert!(dependencies_tail.is_some(), "Store API dependencies section must exist");
@@ -49,19 +49,24 @@ fn pre_004_manifest_keeps_exact_core_only_runtime_dependency() {
}
#[test]
fn pre_004_source_boundary_keeps_raw_models_passive_and_backend_free() {
fn pre_005_source_boundary_keeps_models_and_capabilities_backend_free() {
let crate_root = include_str!("../src/lib.rs");
let model_home = include_str!("../src/model.rs");
let raw_account = include_str!("../src/model/raw_account.rs");
let raw_primitives = include_str!("../src/model/raw_primitives.rs");
let raw_transaction = include_str!("../src/model/raw_transaction.rs");
let capability_home = include_str!("../src/capability.rs");
let raw_account_capability = include_str!("../src/capability/raw_account.rs");
let raw_transaction_capability = include_str!("../src/capability/raw_transaction.rs");
assert!(crate_root.contains("mod capability;"));
assert!(crate_root.contains("mod error;"));
assert!(crate_root.contains("mod model;"));
assert!(model_home.contains("raw_account"));
assert!(model_home.contains("raw_primitives"));
assert!(model_home.contains("raw_transaction"));
for source in [crate_root, model_home, raw_account, raw_primitives, raw_transaction] {
assert!(capability_home.contains("raw_account"));
assert!(capability_home.contains("raw_transaction"));
for source in [crate_root, model_home, raw_account, raw_primitives, raw_transaction, capability_home, raw_account_capability, raw_transaction_capability] {
for forbidden in [
"ksp_store_lib",
"ksp_store_postgres_lib",
@@ -80,7 +85,20 @@ fn pre_004_source_boundary_keeps_raw_models_passive_and_backend_free() {
}
assert!(!raw_transaction.contains("RawLog"));
for forbidden in ["TransactionStatusObservation", "RawLogNotification", "RawSlotEvent", "RawVoteEvent", "RawBlock", "YellowstoneEntry"] {
assert!(!crate_root.contains(forbidden), "deferred pre.004 model leaked into Store API surface: {forbidden}");
assert!(!crate_root.contains(forbidden), "deferred pre.005 model leaked into Store API surface: {forbidden}");
}
assert!(raw_transaction_capability.contains("trait RawTransactionRead"));
assert!(raw_transaction_capability.contains("trait RawTransactionWrite"));
assert!(raw_transaction_capability.contains("trait RawTransactionObservationRead"));
assert!(raw_transaction_capability.contains("trait RawTransactionObservationWrite"));
assert!(raw_account_capability.contains("trait RawAccountStateRead"));
assert!(raw_account_capability.contains("trait RawAccountStateWrite"));
assert!(raw_account_capability.contains("trait RawAccountObservationRead"));
assert!(raw_account_capability.contains("trait RawAccountObservationWrite"));
for forbidden in ["trait StoreBackend", "trait Store", "PostgresStore", "MySqlStore", "Arc<dyn"] {
assert!(!capability_home.contains(forbidden));
assert!(!raw_account_capability.contains(forbidden));
assert!(!raw_transaction_capability.contains(forbidden));
}
return;
}

View File

@@ -0,0 +1,128 @@
// file: crates/ksp-store-api/tests/external_backend.rs
// version: 1
//! External-implementation canary for object-safe Store API capabilities.
struct ExternalMemoryBackend;
impl ksp_store_api::RawTransactionRead for ExternalMemoryBackend {
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 _ = reference;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(std::option::Option::None);
});
}
}
impl ksp_store_api::RawTransactionWrite for ExternalMemoryBackend {
fn persist_raw_transaction_acquisition<'a>(
&'a self,
transaction: ksp_store_api::RawTransaction,
observation: ksp_store_api::RawTransactionObservation,
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
let _ = transaction;
let _ = observation;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(());
});
}
}
impl ksp_store_api::RawTransactionObservationRead for ExternalMemoryBackend {
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>>> {
let _ = observation_key;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(std::option::Option::None);
});
}
}
impl ksp_store_api::RawTransactionObservationWrite for ExternalMemoryBackend {
fn record_raw_transaction_observation<'a>(
&'a self,
observation: ksp_store_api::RawTransactionObservation,
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
let _ = observation;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(());
});
}
}
impl ksp_store_api::RawAccountStateRead for ExternalMemoryBackend {
fn get_raw_account_state<'a>(
&'a self,
reference: &'a ksp_store_api::RawAccountStateReference,
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawAccountState>>> {
let _ = reference;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(std::option::Option::None);
});
}
}
impl ksp_store_api::RawAccountStateWrite for ExternalMemoryBackend {
fn persist_raw_account_acquisition<'a>(
&'a self,
state: ksp_store_api::RawAccountState,
observation: ksp_store_api::RawAccountObservation,
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
let _ = state;
let _ = observation;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(());
});
}
}
impl ksp_store_api::RawAccountObservationRead for ExternalMemoryBackend {
fn get_raw_account_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::RawAccountObservation>>> {
let _ = observation_key;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(std::option::Option::None);
});
}
}
impl ksp_store_api::RawAccountObservationWrite for ExternalMemoryBackend {
fn record_raw_account_observation<'a>(
&'a self,
observation: ksp_store_api::RawAccountObservation,
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
let _ = observation;
return std::boxed::Box::pin(async {
return std::result::Result::Ok(());
});
}
}
#[test]
fn pre_005_external_backend_implements_each_capability_without_store_runtime_crate() {
let backend = ExternalMemoryBackend;
let transaction_read: &dyn ksp_store_api::RawTransactionRead = &backend;
let transaction_write: &dyn ksp_store_api::RawTransactionWrite = &backend;
let transaction_observation_read: &dyn ksp_store_api::RawTransactionObservationRead = &backend;
let transaction_observation_write: &dyn ksp_store_api::RawTransactionObservationWrite = &backend;
let account_read: &dyn ksp_store_api::RawAccountStateRead = &backend;
let account_write: &dyn ksp_store_api::RawAccountStateWrite = &backend;
let account_observation_read: &dyn ksp_store_api::RawAccountObservationRead = &backend;
let account_observation_write: &dyn ksp_store_api::RawAccountObservationWrite = &backend;
let _ = transaction_read;
let _ = transaction_write;
let _ = transaction_observation_read;
let _ = transaction_observation_write;
let _ = account_read;
let _ = account_write;
let _ = account_observation_read;
let _ = account_observation_write;
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-api/tests/public_api.rs
// version: 3
// version: 4
//! Integration canaries for the public `ksp-store-api` surface.
@@ -125,3 +125,24 @@ fn public_pre_004_raw_account_state_and_observation_are_constructible_from_crate
assert_eq!(ksp_store_api::MAX_RAW_ACCOUNT_DATA_BYTES, 16 * 1024 * 1024);
return;
}
#[test]
fn public_pre_005_capabilities_are_available_from_crate_root_and_dyn_compatible() {
let transaction_read: std::option::Option<&dyn ksp_store_api::RawTransactionRead> = std::option::Option::None;
let transaction_write: std::option::Option<&dyn ksp_store_api::RawTransactionWrite> = std::option::Option::None;
let transaction_observation_read: std::option::Option<&dyn ksp_store_api::RawTransactionObservationRead> = std::option::Option::None;
let transaction_observation_write: std::option::Option<&dyn ksp_store_api::RawTransactionObservationWrite> = std::option::Option::None;
let account_read: std::option::Option<&dyn ksp_store_api::RawAccountStateRead> = std::option::Option::None;
let account_write: std::option::Option<&dyn ksp_store_api::RawAccountStateWrite> = std::option::Option::None;
let account_observation_read: std::option::Option<&dyn ksp_store_api::RawAccountObservationRead> = std::option::Option::None;
let account_observation_write: std::option::Option<&dyn ksp_store_api::RawAccountObservationWrite> = std::option::Option::None;
assert!(transaction_read.is_none());
assert!(transaction_write.is_none());
assert!(transaction_observation_read.is_none());
assert!(transaction_observation_write.is_none());
assert!(account_read.is_none());
assert!(account_write.is_none());
assert!(account_observation_read.is_none());
assert!(account_observation_write.is_none());
return;
}