v0.3.1-pre.006
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/capability.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Private home for backend-agnostic Store capability contracts.
|
||||
//!
|
||||
@@ -9,6 +9,7 @@
|
||||
//! outside `ksp-store-api`.
|
||||
|
||||
pub(crate) mod raw_account;
|
||||
pub(crate) mod raw_retention;
|
||||
pub(crate) mod raw_transaction;
|
||||
|
||||
/// Boxed async operation returned by object-safe Store capability contracts.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/capability/raw_account.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Read capability for complete canonical RAW account states.
|
||||
///
|
||||
@@ -12,21 +12,31 @@ pub trait RawAccountStateRead: std::marker::Send + std::marker::Sync {
|
||||
&'a self,
|
||||
reference: &'a crate::RawAccountStateReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawAccountState>>>;
|
||||
|
||||
/// Lists deterministic account-state references using an opaque backend cursor.
|
||||
///
|
||||
/// The backend must not invent a KSP policy ceiling below the caller's
|
||||
/// request. A real backend/physical limitation may determine the page that
|
||||
/// can be served and continuation is represented by the opaque cursor.
|
||||
fn list_raw_account_states<'a>(
|
||||
&'a self,
|
||||
query: &'a crate::RawAccountStateQuery,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawPage<crate::RawAccountStateReference>>>;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// the other side fails. Divergent content for one logical identity is a stable
|
||||
/// conflict error, never a silent overwrite.
|
||||
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<()>>;
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawAcquisitionWriteOutcome>>;
|
||||
}
|
||||
|
||||
/// Read capability for persisted RAW account-state observations.
|
||||
@@ -41,9 +51,11 @@ pub trait RawAccountObservationRead: std::marker::Send + std::marker::Sync {
|
||||
/// 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`.
|
||||
/// without resubmitting account bytes. The referenced state must already exist.
|
||||
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<()>>;
|
||||
fn record_raw_account_observation<'a>(
|
||||
&'a self,
|
||||
observation: crate::RawAccountObservation,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawObservationWriteOutcome>>;
|
||||
}
|
||||
|
||||
29
crates/ksp-store-api/src/capability/raw_retention.rs
Normal file
29
crates/ksp-store-api/src/capability/raw_retention.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// file: crates/ksp-store-api/src/capability/raw_retention.rs
|
||||
// version: 1
|
||||
|
||||
/// Read capability for canonical RAW transaction retention metadata.
|
||||
pub trait RawTransactionRetentionRead: std::marker::Send + std::marker::Sync {
|
||||
/// Reads the logical retention state for one known transaction identity.
|
||||
fn get_raw_transaction_retention_state<'a>(
|
||||
&'a self,
|
||||
reference: &'a crate::RawTransactionReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawRetentionState>>>;
|
||||
|
||||
/// Reads the minimal durable tombstone when the transaction payload was purged.
|
||||
fn get_raw_transaction_tombstone<'a>(
|
||||
&'a self,
|
||||
reference: &'a crate::RawTransactionReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransactionTombstone>>>;
|
||||
}
|
||||
|
||||
/// Write capability for policy-authorized RAW transaction retention transitions.
|
||||
///
|
||||
/// This contract applies a transition selected by an external worker/job policy;
|
||||
/// the Store backend never decides by itself that higher processing is complete.
|
||||
pub trait RawTransactionRetentionWrite: std::marker::Send + std::marker::Sync {
|
||||
/// Applies one atomic forward lifecycle transition chosen by the caller.
|
||||
fn transition_raw_transaction_retention<'a>(
|
||||
&'a self,
|
||||
transition: crate::RawTransactionRetentionTransition,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawRetentionWriteOutcome>>;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/capability/raw_transaction.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Read capability for canonical RAW transactions.
|
||||
///
|
||||
@@ -12,21 +12,32 @@ pub trait RawTransactionRead: std::marker::Send + std::marker::Sync {
|
||||
&'a self,
|
||||
reference: &'a crate::RawTransactionReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransaction>>>;
|
||||
|
||||
/// Lists deterministic transaction references using an opaque backend cursor.
|
||||
///
|
||||
/// The backend must honor the caller-requested page limit unless a real
|
||||
/// backend/physical limitation prevents it. `ksp-store-api` imposes no
|
||||
/// arbitrary smaller result ceiling or executor policy.
|
||||
fn list_raw_transactions<'a>(
|
||||
&'a self,
|
||||
query: &'a crate::RawTransactionQuery,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawPage<crate::RawTransactionReference>>>;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// side fails. Divergent content for one logical identity is a stable conflict
|
||||
/// error, never a silent overwrite.
|
||||
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<()>>;
|
||||
mode: crate::RawTransactionAcquisitionMode,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawAcquisitionWriteOutcome>>;
|
||||
}
|
||||
|
||||
/// Read capability for persisted RAW transaction observations.
|
||||
@@ -42,9 +53,11 @@ pub trait RawTransactionObservationRead: std::marker::Send + std::marker::Sync {
|
||||
///
|
||||
/// 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`.
|
||||
/// referenced transaction must already exist.
|
||||
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<()>>;
|
||||
fn record_raw_transaction_observation<'a>(
|
||||
&'a self,
|
||||
observation: crate::RawTransactionObservation,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawObservationWriteOutcome>>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
// file: crates/ksp-store-api/src/error.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Error code used when a RAW write collides with divergent content for the same logical identity.
|
||||
pub const ERROR_CODE_RAW_CONFLICT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_conflict");
|
||||
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
|
||||
pub const ERROR_CODE_RAW_MODEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_model_invalid");
|
||||
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.
|
||||
pub const ERROR_CODE_RAW_PAYLOAD_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_payload_invalid");
|
||||
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
||||
pub const ERROR_CODE_RAW_PROVENANCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_provenance_invalid");
|
||||
/// Error code used when one RAW query or cursor violates backend-agnostic query invariants.
|
||||
pub const ERROR_CODE_RAW_QUERY_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_query_invalid");
|
||||
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
||||
pub const ERROR_CODE_RAW_RETENTION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_retention_invalid");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/lib.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -30,6 +30,10 @@ pub use self::capability::raw_account::RawAccountObservationWrite;
|
||||
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 canonical RAW transaction retention metadata.
|
||||
pub use self::capability::raw_retention::RawTransactionRetentionRead;
|
||||
/// Write capability for policy-authorized RAW transaction retention transitions.
|
||||
pub use self::capability::raw_retention::RawTransactionRetentionWrite;
|
||||
/// Read capability for persisted RAW transaction observations.
|
||||
pub use self::capability::raw_transaction::RawTransactionObservationRead;
|
||||
/// Write capability for additional observations of already persisted RAW transactions.
|
||||
@@ -38,18 +42,48 @@ pub use self::capability::raw_transaction::RawTransactionObservationWrite;
|
||||
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 write collides with divergent content for the same logical identity.
|
||||
pub use self::error::ERROR_CODE_RAW_CONFLICT;
|
||||
/// 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.
|
||||
pub use self::error::ERROR_CODE_RAW_PAYLOAD_INVALID;
|
||||
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
||||
pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;
|
||||
/// Error code used when one RAW query or cursor violates backend-agnostic query invariants.
|
||||
pub use self::error::ERROR_CODE_RAW_QUERY_INVALID;
|
||||
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
||||
pub use self::error::ERROR_CODE_RAW_RETENTION_INVALID;
|
||||
/// Persistable acquisition observation linked to one complete canonical RAW account state.
|
||||
pub use self::model::raw_account::RawAccountObservation;
|
||||
/// Canonical complete N1 RAW account state independent from acquisition transport.
|
||||
pub use self::model::raw_account::RawAccountState;
|
||||
/// Durable backend-independent identity of one canonical RAW account state.
|
||||
pub use self::model::raw_account::RawAccountStateReference;
|
||||
/// Combined outcome of one atomic canonical RAW entity plus observation acquisition.
|
||||
pub use self::model::raw_outcome::RawAcquisitionWriteOutcome;
|
||||
/// Outcome for one canonical RAW entity in an idempotent persistence operation.
|
||||
pub use self::model::raw_outcome::RawEntityWriteOutcome;
|
||||
/// Outcome for one deterministic acquisition observation write.
|
||||
pub use self::model::raw_outcome::RawObservationWriteOutcome;
|
||||
/// Maximum opaque query cursor length admitted by the Store API.
|
||||
pub use self::model::raw_pagination::MAX_RAW_PAGE_CURSOR_BYTES;
|
||||
/// Backend-independent list query for complete canonical RAW account states.
|
||||
pub use self::model::raw_pagination::RawAccountStateQuery;
|
||||
/// One deterministic page of backend-independent Store results.
|
||||
pub use self::model::raw_pagination::RawPage;
|
||||
/// Opaque backend-owned cursor returned by one deterministic Store query.
|
||||
pub use self::model::raw_pagination::RawPageCursor;
|
||||
/// Caller-requested page size without an arbitrary KSP policy ceiling.
|
||||
pub use self::model::raw_pagination::RawPageLimit;
|
||||
/// Opaque-cursor page request used by backend-independent list operations.
|
||||
pub use self::model::raw_pagination::RawPageRequest;
|
||||
/// Optional inclusive Solana slot bounds for one Store query.
|
||||
pub use self::model::raw_pagination::RawSlotRange;
|
||||
/// Deterministic traversal direction for Store list queries.
|
||||
pub use self::model::raw_pagination::RawSortDirection;
|
||||
/// Backend-independent list query for canonical RAW transactions.
|
||||
pub use self::model::raw_pagination::RawTransactionQuery;
|
||||
/// Maximum complete RAW account-data length admitted by the Store API.
|
||||
pub use self::model::raw_primitives::MAX_RAW_ACCOUNT_DATA_BYTES;
|
||||
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
|
||||
@@ -80,6 +114,16 @@ pub use self::model::raw_primitives::RawProvenanceCode;
|
||||
pub use self::model::raw_primitives::RawTimestamp;
|
||||
/// Canonical 64-byte Solana transaction signature used by Store identities.
|
||||
pub use self::model::raw_primitives::RawTransactionSignature;
|
||||
/// Logical availability state of one canonical RAW payload.
|
||||
pub use self::model::raw_retention::RawRetentionState;
|
||||
/// Outcome of one atomic RAW retention transition.
|
||||
pub use self::model::raw_retention::RawRetentionWriteOutcome;
|
||||
/// Explicit write mode for canonical RAW transaction acquisitions.
|
||||
pub use self::model::raw_retention::RawTransactionAcquisitionMode;
|
||||
/// Requested compare-and-transition operation for one RAW transaction retention state.
|
||||
pub use self::model::raw_retention::RawTransactionRetentionTransition;
|
||||
/// Minimal durable identity retained after a canonical RAW transaction payload is purged.
|
||||
pub use self::model::raw_retention::RawTransactionTombstone;
|
||||
/// Canonical source-independent N1 RAW transaction persisted by Store backends.
|
||||
pub use self::model::raw_transaction::RawTransaction;
|
||||
/// Persistable acquisition observation linked to one canonical RAW transaction.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/model.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Private home for persistent Store models.
|
||||
//!
|
||||
@@ -9,5 +9,8 @@
|
||||
//! one or more models.
|
||||
|
||||
pub(crate) mod raw_account;
|
||||
pub(crate) mod raw_outcome;
|
||||
pub(crate) mod raw_pagination;
|
||||
pub(crate) mod raw_primitives;
|
||||
pub(crate) mod raw_retention;
|
||||
pub(crate) mod raw_transaction;
|
||||
|
||||
55
crates/ksp-store-api/src/model/raw_outcome.rs
Normal file
55
crates/ksp-store-api/src/model/raw_outcome.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
// file: crates/ksp-store-api/src/model/raw_outcome.rs
|
||||
// version: 1
|
||||
|
||||
/// Outcome for one canonical RAW entity in an idempotent persistence operation.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawEntityWriteOutcome {
|
||||
/// The canonical RAW entity was inserted for the first time.
|
||||
Inserted,
|
||||
/// Identical canonical content was already durable.
|
||||
AlreadyPresent,
|
||||
/// A previously purged RAW entity was explicitly rehydrated.
|
||||
Rehydrated,
|
||||
/// Normal persistence skipped a durable purged tombstone.
|
||||
SkippedPurged,
|
||||
}
|
||||
|
||||
/// Outcome for one deterministic acquisition observation write.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawObservationWriteOutcome {
|
||||
/// The observation was inserted for the first time.
|
||||
Inserted,
|
||||
/// The same observation key already identified identical durable content.
|
||||
AlreadyPresent,
|
||||
/// No observation was recorded because the associated RAW entity was intentionally skipped.
|
||||
NotRecorded,
|
||||
}
|
||||
|
||||
/// Combined outcome of one atomic canonical RAW entity plus observation acquisition.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct RawAcquisitionWriteOutcome {
|
||||
entity: crate::RawEntityWriteOutcome,
|
||||
observation: crate::RawObservationWriteOutcome,
|
||||
}
|
||||
|
||||
impl RawAcquisitionWriteOutcome {
|
||||
/// Creates one backend-independent atomic acquisition outcome.
|
||||
#[must_use]
|
||||
pub const fn new(entity: crate::RawEntityWriteOutcome, observation: crate::RawObservationWriteOutcome) -> Self {
|
||||
return Self { entity, observation };
|
||||
}
|
||||
|
||||
/// Returns the canonical RAW entity write outcome.
|
||||
#[must_use]
|
||||
pub const fn entity(&self) -> crate::RawEntityWriteOutcome {
|
||||
return self.entity;
|
||||
}
|
||||
|
||||
/// Returns the acquisition observation write outcome.
|
||||
#[must_use]
|
||||
pub const fn observation(&self) -> crate::RawObservationWriteOutcome {
|
||||
return self.observation;
|
||||
}
|
||||
}
|
||||
267
crates/ksp-store-api/src/model/raw_pagination.rs
Normal file
267
crates/ksp-store-api/src/model/raw_pagination.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
// file: crates/ksp-store-api/src/model/raw_pagination.rs
|
||||
// version: 1
|
||||
|
||||
/// Maximum opaque cursor length accepted by Store API queries.
|
||||
///
|
||||
/// This bounds untrusted token material only. It is not a policy limit on the
|
||||
/// number of rows/results a backend may return.
|
||||
pub const MAX_RAW_PAGE_CURSOR_BYTES: usize = 4 * 1024;
|
||||
|
||||
/// Opaque backend-owned cursor returned by one deterministic Store query.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct RawPageCursor(std::boxed::Box<[u8]>);
|
||||
|
||||
impl RawPageCursor {
|
||||
/// Creates one non-empty bounded opaque cursor.
|
||||
pub fn try_new(bytes: std::boxed::Box<[u8]>) -> crate::Result<Self> {
|
||||
if bytes.is_empty() || bytes.len() > crate::MAX_RAW_PAGE_CURSOR_BYTES {
|
||||
return std::result::Result::Err(raw_query_error("cursor"));
|
||||
}
|
||||
return std::result::Result::Ok(Self(bytes));
|
||||
}
|
||||
|
||||
/// Returns the opaque cursor bytes unchanged.
|
||||
#[must_use]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
return self.0.as_ref();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RawPageCursor {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.debug_struct("RawPageCursor").field("len", &self.0.len()).finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// Caller-requested page size without an arbitrary KSP policy ceiling.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct RawPageLimit(u64);
|
||||
|
||||
impl RawPageLimit {
|
||||
/// Creates one strictly positive requested page size.
|
||||
///
|
||||
/// `ksp-store-api` deliberately imposes no smaller functional maximum.
|
||||
/// Concrete backends may expose or return their real physical limitations.
|
||||
pub fn new(value: u64) -> crate::Result<Self> {
|
||||
if value == 0 {
|
||||
return std::result::Result::Err(raw_query_error("limit"));
|
||||
}
|
||||
return std::result::Result::Ok(Self(value));
|
||||
}
|
||||
|
||||
/// Returns the exact caller-requested item count.
|
||||
#[must_use]
|
||||
pub const fn get(&self) -> u64 {
|
||||
return self.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque-cursor page request used by backend-independent list operations.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawPageRequest {
|
||||
cursor: std::option::Option<crate::RawPageCursor>,
|
||||
limit: crate::RawPageLimit,
|
||||
}
|
||||
|
||||
impl RawPageRequest {
|
||||
/// Creates one first-page request.
|
||||
#[must_use]
|
||||
pub const fn first(limit: crate::RawPageLimit) -> Self {
|
||||
return Self { cursor: std::option::Option::None, limit };
|
||||
}
|
||||
|
||||
/// Creates one continuation-page request with an opaque backend cursor.
|
||||
#[must_use]
|
||||
pub const fn after(limit: crate::RawPageLimit, cursor: crate::RawPageCursor) -> Self {
|
||||
return Self { cursor: std::option::Option::Some(cursor), limit };
|
||||
}
|
||||
|
||||
/// Returns the opaque continuation cursor when present.
|
||||
#[must_use]
|
||||
pub fn cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
||||
return self.cursor.as_ref();
|
||||
}
|
||||
|
||||
/// Returns the exact caller-requested page size.
|
||||
#[must_use]
|
||||
pub const fn limit(&self) -> crate::RawPageLimit {
|
||||
return self.limit;
|
||||
}
|
||||
}
|
||||
|
||||
/// One deterministic page of backend-independent Store results.
|
||||
#[derive(Debug)]
|
||||
pub struct RawPage<T> {
|
||||
items: std::vec::Vec<T>,
|
||||
next_cursor: std::option::Option<crate::RawPageCursor>,
|
||||
}
|
||||
|
||||
impl<T> RawPage<T> {
|
||||
/// Creates one result page from backend-provided items and optional continuation cursor.
|
||||
#[must_use]
|
||||
pub fn new(items: std::vec::Vec<T>, next_cursor: std::option::Option<crate::RawPageCursor>) -> Self {
|
||||
return Self { items, next_cursor };
|
||||
}
|
||||
|
||||
/// Returns the current page items.
|
||||
#[must_use]
|
||||
pub fn items(&self) -> &[T] {
|
||||
return self.items.as_slice();
|
||||
}
|
||||
|
||||
/// Consumes the page and returns its items.
|
||||
#[must_use]
|
||||
pub fn into_items(self) -> std::vec::Vec<T> {
|
||||
return self.items;
|
||||
}
|
||||
|
||||
/// Returns the next opaque cursor when more results are available.
|
||||
#[must_use]
|
||||
pub fn next_cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
||||
return self.next_cursor.as_ref();
|
||||
}
|
||||
}
|
||||
|
||||
/// Deterministic traversal direction for Store list queries.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawSortDirection {
|
||||
/// Oldest/lower ordered keys first.
|
||||
Ascending,
|
||||
/// Newest/higher ordered keys first.
|
||||
Descending,
|
||||
}
|
||||
|
||||
/// Optional inclusive Solana slot bounds for one Store query.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct RawSlotRange {
|
||||
end_inclusive: std::option::Option<u64>,
|
||||
start_inclusive: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
impl RawSlotRange {
|
||||
/// Creates one optional inclusive slot range and rejects reversed bounds.
|
||||
pub fn new(start_inclusive: std::option::Option<u64>, end_inclusive: std::option::Option<u64>) -> crate::Result<Self> {
|
||||
if let (std::option::Option::Some(start), std::option::Option::Some(end)) = (start_inclusive, end_inclusive) {
|
||||
if start > end {
|
||||
return std::result::Result::Err(raw_query_error("slot_range"));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(Self { end_inclusive, start_inclusive });
|
||||
}
|
||||
|
||||
/// Returns the optional inclusive ending slot.
|
||||
#[must_use]
|
||||
pub const fn end_inclusive(&self) -> std::option::Option<u64> {
|
||||
return self.end_inclusive;
|
||||
}
|
||||
|
||||
/// Returns the optional inclusive starting slot.
|
||||
#[must_use]
|
||||
pub const fn start_inclusive(&self) -> std::option::Option<u64> {
|
||||
return self.start_inclusive;
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend-independent list query for canonical RAW transactions.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawTransactionQuery {
|
||||
direction: crate::RawSortDirection,
|
||||
network: crate::RawNetworkId,
|
||||
page: crate::RawPageRequest,
|
||||
slots: crate::RawSlotRange,
|
||||
}
|
||||
|
||||
impl RawTransactionQuery {
|
||||
/// Creates one deterministic transaction-reference query.
|
||||
#[must_use]
|
||||
pub fn new(network: crate::RawNetworkId, slots: crate::RawSlotRange, direction: crate::RawSortDirection, page: crate::RawPageRequest) -> Self {
|
||||
return Self { direction, network, page, slots };
|
||||
}
|
||||
|
||||
/// Returns the requested deterministic traversal direction.
|
||||
#[must_use]
|
||||
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||
return self.direction;
|
||||
}
|
||||
|
||||
/// Returns the required logical network scope.
|
||||
#[must_use]
|
||||
pub fn network(&self) -> &crate::RawNetworkId {
|
||||
return &self.network;
|
||||
}
|
||||
|
||||
/// Returns pagination inputs without exposing backend cursor contents.
|
||||
#[must_use]
|
||||
pub fn page(&self) -> &crate::RawPageRequest {
|
||||
return &self.page;
|
||||
}
|
||||
|
||||
/// Returns optional inclusive slot bounds.
|
||||
#[must_use]
|
||||
pub const fn slots(&self) -> crate::RawSlotRange {
|
||||
return self.slots;
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend-independent list query for complete canonical RAW account states.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawAccountStateQuery {
|
||||
direction: crate::RawSortDirection,
|
||||
network: crate::RawNetworkId,
|
||||
page: crate::RawPageRequest,
|
||||
pubkey: std::option::Option<crate::Pubkey>,
|
||||
slots: crate::RawSlotRange,
|
||||
}
|
||||
|
||||
impl RawAccountStateQuery {
|
||||
/// Creates one deterministic account-state-reference query.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
network: crate::RawNetworkId,
|
||||
pubkey: std::option::Option<crate::Pubkey>,
|
||||
slots: crate::RawSlotRange,
|
||||
direction: crate::RawSortDirection,
|
||||
page: crate::RawPageRequest,
|
||||
) -> Self {
|
||||
return Self { direction, network, page, pubkey, slots };
|
||||
}
|
||||
|
||||
/// Returns the requested deterministic traversal direction.
|
||||
#[must_use]
|
||||
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||
return self.direction;
|
||||
}
|
||||
|
||||
/// Returns the required logical network scope.
|
||||
#[must_use]
|
||||
pub fn network(&self) -> &crate::RawNetworkId {
|
||||
return &self.network;
|
||||
}
|
||||
|
||||
/// Returns pagination inputs without exposing backend cursor contents.
|
||||
#[must_use]
|
||||
pub fn page(&self) -> &crate::RawPageRequest {
|
||||
return &self.page;
|
||||
}
|
||||
|
||||
/// Returns an optional account-address restriction.
|
||||
#[must_use]
|
||||
pub fn pubkey(&self) -> std::option::Option<&crate::Pubkey> {
|
||||
return self.pubkey.as_ref();
|
||||
}
|
||||
|
||||
/// Returns optional inclusive slot bounds.
|
||||
#[must_use]
|
||||
pub const fn slots(&self) -> crate::RawSlotRange {
|
||||
return self.slots;
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_query_error(field: &'static str) -> crate::Error {
|
||||
return crate::Error::new(crate::ERROR_CODE_RAW_QUERY_INVALID, "invalid backend-agnostic RAW Store query").with_context("field", field);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../../unit_tests/model/raw_pagination.rs"]
|
||||
mod tests;
|
||||
152
crates/ksp-store-api/src/model/raw_retention.rs
Normal file
152
crates/ksp-store-api/src/model/raw_retention.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
// file: crates/ksp-store-api/src/model/raw_retention.rs
|
||||
// version: 1
|
||||
|
||||
/// Logical availability state of one canonical RAW payload.
|
||||
///
|
||||
/// These states describe payload retention only. They never prove that a
|
||||
/// STRUCTURAL/DECODED/DOMAIN processor has completed.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawRetentionState {
|
||||
/// Complete canonical RAW payload is retained in ordinary Store availability.
|
||||
Full,
|
||||
/// Complete logical RAW content is retained in a compacted representation.
|
||||
Compacted,
|
||||
/// RAW content has been moved to an archive managed outside the hot Store path.
|
||||
Archived,
|
||||
/// RAW payload is absent while a durable tombstone remains.
|
||||
Purged,
|
||||
}
|
||||
|
||||
/// Explicit write mode for canonical RAW transaction acquisitions.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawTransactionAcquisitionMode {
|
||||
/// Normal acquisition must skip an existing purged tombstone.
|
||||
Normal,
|
||||
/// Explicitly allow an existing purged transaction to be rehydrated.
|
||||
ForceRehydrate,
|
||||
}
|
||||
|
||||
/// Minimal durable identity retained after a canonical RAW transaction payload is purged.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawTransactionTombstone {
|
||||
content_hash: crate::RawContentHash,
|
||||
format_id: crate::RawFormatId,
|
||||
format_version: u32,
|
||||
reference: crate::RawTransactionReference,
|
||||
slot: u64,
|
||||
}
|
||||
|
||||
impl RawTransactionTombstone {
|
||||
/// Creates one minimal transaction tombstone from canonical RAW identity metadata.
|
||||
pub fn try_new(
|
||||
reference: crate::RawTransactionReference,
|
||||
slot: u64,
|
||||
format_id: crate::RawFormatId,
|
||||
format_version: u32,
|
||||
content_hash: crate::RawContentHash,
|
||||
) -> crate::Result<Self> {
|
||||
if format_version == 0 {
|
||||
return std::result::Result::Err(raw_retention_error("format_version"));
|
||||
}
|
||||
return std::result::Result::Ok(Self { content_hash, format_id, format_version, reference, slot });
|
||||
}
|
||||
|
||||
/// Returns the canonical content hash retained after purge.
|
||||
#[must_use]
|
||||
pub const fn content_hash(&self) -> crate::RawContentHash {
|
||||
return self.content_hash;
|
||||
}
|
||||
|
||||
/// Returns the KSP-owned canonical RAW format identifier retained after purge.
|
||||
#[must_use]
|
||||
pub fn format_id(&self) -> &crate::RawFormatId {
|
||||
return &self.format_id;
|
||||
}
|
||||
|
||||
/// Returns the KSP-owned canonical RAW format version retained after purge.
|
||||
#[must_use]
|
||||
pub const fn format_version(&self) -> u32 {
|
||||
return self.format_version;
|
||||
}
|
||||
|
||||
/// Returns the durable backend-independent transaction identity.
|
||||
#[must_use]
|
||||
pub fn reference(&self) -> &crate::RawTransactionReference {
|
||||
return &self.reference;
|
||||
}
|
||||
|
||||
/// Returns the known transaction slot retained after purge.
|
||||
#[must_use]
|
||||
pub const fn slot(&self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
}
|
||||
|
||||
/// Requested compare-and-transition operation for one RAW transaction retention state.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawTransactionRetentionTransition {
|
||||
expected: crate::RawRetentionState,
|
||||
reference: crate::RawTransactionReference,
|
||||
target: crate::RawRetentionState,
|
||||
}
|
||||
|
||||
impl RawTransactionRetentionTransition {
|
||||
/// Creates one allowed forward retention transition.
|
||||
///
|
||||
/// Rehydration from `Purged` is deliberately excluded and uses
|
||||
/// [`crate::RawTransactionAcquisitionMode::ForceRehydrate`] instead.
|
||||
pub fn try_new(reference: crate::RawTransactionReference, expected: crate::RawRetentionState, target: crate::RawRetentionState) -> crate::Result<Self> {
|
||||
if !allowed_transition(expected, target) {
|
||||
return std::result::Result::Err(raw_retention_error("transition"));
|
||||
}
|
||||
return std::result::Result::Ok(Self { expected, reference, target });
|
||||
}
|
||||
|
||||
/// Returns the state the caller expects before applying the transition.
|
||||
#[must_use]
|
||||
pub const fn expected(&self) -> crate::RawRetentionState {
|
||||
return self.expected;
|
||||
}
|
||||
|
||||
/// Returns the durable transaction identity targeted by the transition.
|
||||
#[must_use]
|
||||
pub fn reference(&self) -> &crate::RawTransactionReference {
|
||||
return &self.reference;
|
||||
}
|
||||
|
||||
/// Returns the requested forward retention state.
|
||||
#[must_use]
|
||||
pub const fn target(&self) -> crate::RawRetentionState {
|
||||
return self.target;
|
||||
}
|
||||
}
|
||||
|
||||
/// Outcome of one atomic RAW retention transition.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawRetentionWriteOutcome {
|
||||
/// The requested forward transition was applied.
|
||||
Applied,
|
||||
/// The entity was already at the requested target state.
|
||||
AlreadyAtTarget,
|
||||
}
|
||||
|
||||
fn allowed_transition(expected: crate::RawRetentionState, target: crate::RawRetentionState) -> bool {
|
||||
return matches!(
|
||||
(expected, target),
|
||||
(crate::RawRetentionState::Full, crate::RawRetentionState::Compacted)
|
||||
| (crate::RawRetentionState::Full, crate::RawRetentionState::Archived)
|
||||
| (crate::RawRetentionState::Compacted, crate::RawRetentionState::Archived)
|
||||
| (crate::RawRetentionState::Archived, crate::RawRetentionState::Purged)
|
||||
);
|
||||
}
|
||||
|
||||
fn raw_retention_error(field: &'static str) -> crate::Error {
|
||||
return crate::Error::new(crate::ERROR_CODE_RAW_RETENTION_INVALID, "invalid RAW retention lifecycle request").with_context("field", field);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../../unit_tests/model/raw_retention.rs"]
|
||||
mod tests;
|
||||
Reference in New Issue
Block a user