0.3.17-pre.002
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 652
|
# version: 653
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"]
|
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.17-pre.1"
|
version = "0.3.17-pre.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/src/capability.rs
|
// file: crates/ksp-store-api/src/capability.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
//! Private home for backend-agnostic Store capability contracts.
|
//! Private home for backend-agnostic Store capability contracts.
|
||||||
//!
|
//!
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
//! outside `ksp-store-api`.
|
//! outside `ksp-store-api`.
|
||||||
|
|
||||||
pub(crate) mod raw_account;
|
pub(crate) mod raw_account;
|
||||||
|
pub(crate) mod raw_conflict;
|
||||||
pub(crate) mod raw_retention;
|
pub(crate) mod raw_retention;
|
||||||
pub(crate) mod raw_transaction;
|
pub(crate) mod raw_transaction;
|
||||||
|
|
||||||
@@ -17,3 +18,12 @@ pub(crate) mod raw_transaction;
|
|||||||
/// The alias uses only standard-library primitives so backend implementations
|
/// The alias uses only standard-library primitives so backend implementations
|
||||||
/// need no async helper dependency merely to implement `ksp-store-api`.
|
/// 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>>;
|
pub type StoreApiFuture<'a, T> = std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = T> + std::marker::Send + 'a>>;
|
||||||
|
|
||||||
|
/// Backend-neutral classifier for errors crossing a Store capability boundary.
|
||||||
|
///
|
||||||
|
/// Implementations must classify from structured KSP error state. Consumers such
|
||||||
|
/// as Workers must not infer retryability by matching backend-specific strings.
|
||||||
|
pub trait StoreErrorClassifier: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Classifies one already-redacted Store error as retryable/transient or terminal.
|
||||||
|
fn classify_store_error(&self, error: &crate::Error) -> crate::StoreErrorClass;
|
||||||
|
}
|
||||||
|
|||||||
50
crates/ksp-store-api/src/capability/raw_conflict.rs
Normal file
50
crates/ksp-store-api/src/capability/raw_conflict.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// file: crates/ksp-store-api/src/capability/raw_conflict.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Read capability for bounded operator inspection of persisted RAW transaction variants.
|
||||||
|
pub trait RawTransactionVariantInspectionRead: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Inspects one random-access variant window with exact logical counts.
|
||||||
|
fn inspect_raw_transaction_variants<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a crate::RawTransactionVariantInspectionQuery,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawInspectionPage<crate::RawTransactionVariantSummary>>>;
|
||||||
|
|
||||||
|
/// Reads explicit detail for one durable variant reference.
|
||||||
|
fn get_raw_transaction_variant<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a crate::RawTransactionVariantReference,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransactionVariantDetail>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read capability for bounded operator inspection of RAW transaction conflict cases.
|
||||||
|
pub trait RawTransactionConflictInspectionRead: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Inspects one random-access conflict-case window with exact logical counts.
|
||||||
|
fn inspect_raw_transaction_conflicts<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a crate::RawTransactionConflictInspectionQuery,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawInspectionPage<crate::RawTransactionConflictSummary>>>;
|
||||||
|
|
||||||
|
/// Reads one payload-free conflict case including its monotone participant ledger.
|
||||||
|
fn get_raw_transaction_conflict<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a crate::RawTransactionConflictReference,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransactionConflictDetail>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read capability for one RAW transaction conflict case append-only history.
|
||||||
|
pub trait RawTransactionConflictHistoryRead: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Inspects one random-access conflict-history window ordered by durable revision.
|
||||||
|
fn inspect_raw_transaction_conflict_history<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a crate::RawTransactionConflictHistoryQuery,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawInspectionPage<crate::RawTransactionConflictEventSummary>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write capability for explicit compare-and-set RAW transaction conflict actions.
|
||||||
|
pub trait RawTransactionConflictActionWrite: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Applies one revision-guarded conflict action atomically.
|
||||||
|
fn apply_raw_transaction_conflict_action<'a>(
|
||||||
|
&'a self,
|
||||||
|
request: crate::RawTransactionConflictActionRequest,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawTransactionConflictActionOutcome>>;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/src/error.rs
|
// file: crates/ksp-store-api/src/error.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
/// Error code used when a RAW write collides with divergent content for the same logical identity.
|
/// 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");
|
pub const ERROR_CODE_RAW_CONFLICT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_conflict");
|
||||||
@@ -13,3 +13,13 @@ pub const ERROR_CODE_RAW_PROVENANCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_
|
|||||||
pub const ERROR_CODE_RAW_QUERY_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_query_invalid");
|
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.
|
/// 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");
|
pub const ERROR_CODE_RAW_RETENTION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_retention_invalid");
|
||||||
|
|
||||||
|
/// Backend-neutral retryability class for errors crossing a Store capability boundary.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum StoreErrorClass {
|
||||||
|
/// The operation may be retried by an owning runtime under an explicit bounded policy.
|
||||||
|
Transient,
|
||||||
|
/// Retrying without an external state/configuration change is not safe or not justified.
|
||||||
|
Terminal,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/src/lib.rs
|
// file: crates/ksp-store-api/src/lib.rs
|
||||||
// version: 9
|
// version: 10
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
@@ -18,6 +18,8 @@ mod error;
|
|||||||
mod model;
|
mod model;
|
||||||
/// Boxed async operation returned by object-safe Store capability contracts.
|
/// Boxed async operation returned by object-safe Store capability contracts.
|
||||||
pub use self::capability::StoreApiFuture;
|
pub use self::capability::StoreApiFuture;
|
||||||
|
/// Backend-neutral classifier for Store errors crossing capability boundaries.
|
||||||
|
pub use self::capability::StoreErrorClassifier;
|
||||||
/// Read capability for random-access RAW account-observation inspection.
|
/// Read capability for random-access RAW account-observation inspection.
|
||||||
pub use self::capability::raw_account::RawAccountObservationInspectionRead;
|
pub use self::capability::raw_account::RawAccountObservationInspectionRead;
|
||||||
/// Read capability for persisted RAW account-state observations.
|
/// Read capability for persisted RAW account-state observations.
|
||||||
@@ -30,6 +32,14 @@ pub use self::capability::raw_account::RawAccountStateInspectionRead;
|
|||||||
pub use self::capability::raw_account::RawAccountStateRead;
|
pub use self::capability::raw_account::RawAccountStateRead;
|
||||||
/// Write capability for complete canonical RAW account-state acquisitions.
|
/// Write capability for complete canonical RAW account-state acquisitions.
|
||||||
pub use self::capability::raw_account::RawAccountStateWrite;
|
pub use self::capability::raw_account::RawAccountStateWrite;
|
||||||
|
/// Write capability for explicit revision-guarded RAW transaction conflict actions.
|
||||||
|
pub use self::capability::raw_conflict::RawTransactionConflictActionWrite;
|
||||||
|
/// Read capability for one RAW transaction conflict case append-only history.
|
||||||
|
pub use self::capability::raw_conflict::RawTransactionConflictHistoryRead;
|
||||||
|
/// Read capability for bounded operator inspection of RAW transaction conflict cases.
|
||||||
|
pub use self::capability::raw_conflict::RawTransactionConflictInspectionRead;
|
||||||
|
/// Read capability for bounded operator inspection of persisted RAW transaction variants.
|
||||||
|
pub use self::capability::raw_conflict::RawTransactionVariantInspectionRead;
|
||||||
/// Read capability for canonical RAW transaction retention metadata.
|
/// Read capability for canonical RAW transaction retention metadata.
|
||||||
pub use self::capability::raw_retention::RawTransactionRetentionRead;
|
pub use self::capability::raw_retention::RawTransactionRetentionRead;
|
||||||
/// Write capability for policy-authorized RAW transaction retention transitions.
|
/// Write capability for policy-authorized RAW transaction retention transitions.
|
||||||
@@ -58,12 +68,48 @@ pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;
|
|||||||
pub use self::error::ERROR_CODE_RAW_QUERY_INVALID;
|
pub use self::error::ERROR_CODE_RAW_QUERY_INVALID;
|
||||||
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
||||||
pub use self::error::ERROR_CODE_RAW_RETENTION_INVALID;
|
pub use self::error::ERROR_CODE_RAW_RETENTION_INVALID;
|
||||||
|
/// Backend-neutral retryability class for errors crossing Store capability boundaries.
|
||||||
|
pub use self::error::StoreErrorClass;
|
||||||
/// Persistable acquisition observation linked to one complete canonical RAW account state.
|
/// Persistable acquisition observation linked to one complete canonical RAW account state.
|
||||||
pub use self::model::raw_account::RawAccountObservation;
|
pub use self::model::raw_account::RawAccountObservation;
|
||||||
/// Canonical complete N1 RAW account state independent from acquisition transport.
|
/// Canonical complete N1 RAW account state independent from acquisition transport.
|
||||||
pub use self::model::raw_account::RawAccountState;
|
pub use self::model::raw_account::RawAccountState;
|
||||||
/// Durable backend-independent identity of one canonical RAW account state.
|
/// Durable backend-independent identity of one canonical RAW account state.
|
||||||
pub use self::model::raw_account::RawAccountStateReference;
|
pub use self::model::raw_account::RawAccountStateReference;
|
||||||
|
/// Explicit mutable operation over one RAW transaction conflict case.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictAction;
|
||||||
|
/// Durable backend-neutral outcome of one conflict-case action request.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictActionOutcome;
|
||||||
|
/// Compare-and-set request for one mutable RAW transaction conflict action.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictActionRequest;
|
||||||
|
/// Detailed payload-free conflict projection including the participant ledger.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictDetail;
|
||||||
|
/// Stable append-only event kind for RAW transaction conflict history.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictEventKind;
|
||||||
|
/// Safe semantic origin of one conflict-history transition.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictEventOrigin;
|
||||||
|
/// Payload-free append-only history event for one RAW transaction conflict case.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictEventSummary;
|
||||||
|
/// Backend-independent random-access query for one conflict case history.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictHistoryQuery;
|
||||||
|
/// Backend-independent random-access inspection query for conflict cases.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictInspectionQuery;
|
||||||
|
/// Payload-free durable participant of one RAW transaction conflict case.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictParticipantSummary;
|
||||||
|
/// Durable backend-independent identity of one RAW transaction conflict case.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictReference;
|
||||||
|
/// Explicit canonical resolution strategy used when closing one conflict case.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictResolution;
|
||||||
|
/// Durable action classification recorded for a conflict resolution.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictResolutionAction;
|
||||||
|
/// Payload-free current projection of one RAW transaction conflict case.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionConflictSummary;
|
||||||
|
/// Explicit detail for one RAW transaction variant.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionVariantDetail;
|
||||||
|
/// Backend-independent random-access inspection query for RAW transaction variants.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionVariantInspectionQuery;
|
||||||
|
/// Payload-free summary of one persisted RAW transaction variant.
|
||||||
|
pub use self::model::raw_conflict::RawTransactionVariantSummary;
|
||||||
/// Backend-independent random-access inspection query for RAW account observations.
|
/// Backend-independent random-access inspection query for RAW account observations.
|
||||||
pub use self::model::raw_inspection::RawAccountObservationInspectionQuery;
|
pub use self::model::raw_inspection::RawAccountObservationInspectionQuery;
|
||||||
/// Safe observation summary for one canonical RAW account-state acquisition.
|
/// Safe observation summary for one canonical RAW account-state acquisition.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/src/model.rs
|
// file: crates/ksp-store-api/src/model.rs
|
||||||
// version: 5
|
// version: 6
|
||||||
|
|
||||||
//! Private home for persistent Store models.
|
//! Private home for persistent Store models.
|
||||||
//!
|
//!
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
//! one or more models.
|
//! one or more models.
|
||||||
|
|
||||||
pub(crate) mod raw_account;
|
pub(crate) mod raw_account;
|
||||||
|
pub(crate) mod raw_conflict;
|
||||||
pub(crate) mod raw_inspection;
|
pub(crate) mod raw_inspection;
|
||||||
pub(crate) mod raw_outcome;
|
pub(crate) mod raw_outcome;
|
||||||
pub(crate) mod raw_pagination;
|
pub(crate) mod raw_pagination;
|
||||||
|
|||||||
951
crates/ksp-store-api/src/model/raw_conflict.rs
Normal file
951
crates/ksp-store-api/src/model/raw_conflict.rs
Normal file
@@ -0,0 +1,951 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model/raw_conflict.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Durable backend-independent identity of one RAW transaction conflict case.
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct RawTransactionConflictReference {
|
||||||
|
transaction: crate::RawTransactionReference,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictReference {
|
||||||
|
/// Creates one conflict-case identity from the owning transaction identity.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(transaction: crate::RawTransactionReference) -> Self {
|
||||||
|
return Self { transaction };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the logical transaction identity owning this conflict case.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transaction(&self) -> &crate::RawTransactionReference {
|
||||||
|
return &self.transaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backend-independent random-access inspection query for RAW transaction variants.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionVariantInspectionQuery {
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
page: crate::RawInspectionPageRequest,
|
||||||
|
transaction: std::option::Option<crate::RawTransactionReference>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionVariantInspectionQuery {
|
||||||
|
/// Creates one variant-inspection query after enforcing network coherence.
|
||||||
|
pub fn try_new(
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
transaction: std::option::Option<crate::RawTransactionReference>,
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
page: crate::RawInspectionPageRequest,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
if let std::option::Option::Some(reference) = transaction.as_ref()
|
||||||
|
&& reference.network() != &network
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_query_error("transaction"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { direction, network, page, transaction });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested deterministic traversal direction.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||||
|
return self.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the mandatory logical network scope.
|
||||||
|
#[must_use]
|
||||||
|
pub fn network(&self) -> &crate::RawNetworkId {
|
||||||
|
return &self.network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the random-access inspection page request.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn page(&self) -> crate::RawInspectionPageRequest {
|
||||||
|
return self.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional exact transaction identity used to filter variants.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transaction(&self) -> std::option::Option<&crate::RawTransactionReference> {
|
||||||
|
return self.transaction.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload-free summary of one persisted RAW transaction variant.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionVariantSummary {
|
||||||
|
block_time: std::option::Option<crate::RawTimestamp>,
|
||||||
|
created_at: crate::RawTimestamp,
|
||||||
|
format_id: crate::RawFormatId,
|
||||||
|
format_version: u32,
|
||||||
|
is_current_canonical: bool,
|
||||||
|
observation_count: u64,
|
||||||
|
origin: crate::RawTransactionVariantOrigin,
|
||||||
|
payload_size_bytes: std::option::Option<u64>,
|
||||||
|
reference: crate::RawTransactionVariantReference,
|
||||||
|
retention_state: crate::RawRetentionState,
|
||||||
|
slot: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionVariantSummary {
|
||||||
|
/// Creates one payload-free variant summary after validating bounded metadata.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn try_new(
|
||||||
|
reference: crate::RawTransactionVariantReference,
|
||||||
|
origin: crate::RawTransactionVariantOrigin,
|
||||||
|
slot: u64,
|
||||||
|
block_time: std::option::Option<crate::RawTimestamp>,
|
||||||
|
format_id: crate::RawFormatId,
|
||||||
|
format_version: u32,
|
||||||
|
retention_state: crate::RawRetentionState,
|
||||||
|
payload_size_bytes: std::option::Option<u64>,
|
||||||
|
is_current_canonical: bool,
|
||||||
|
observation_count: u64,
|
||||||
|
created_at: crate::RawTimestamp,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
if format_version == 0 {
|
||||||
|
return std::result::Result::Err(raw_model_error("format_version"));
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(size) = payload_size_bytes {
|
||||||
|
let size = match usize::try_from(size) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::result::Result::Err(raw_model_error("payload_size_bytes")),
|
||||||
|
};
|
||||||
|
if size == 0 || size > crate::MAX_RAW_PAYLOAD_BYTES {
|
||||||
|
return std::result::Result::Err(raw_model_error("payload_size_bytes"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if retention_state == crate::RawRetentionState::Purged && payload_size_bytes.is_some() {
|
||||||
|
return std::result::Result::Err(raw_model_error("payload_size_bytes"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self {
|
||||||
|
block_time,
|
||||||
|
created_at,
|
||||||
|
format_id,
|
||||||
|
format_version,
|
||||||
|
is_current_canonical,
|
||||||
|
observation_count,
|
||||||
|
origin,
|
||||||
|
payload_size_bytes,
|
||||||
|
reference,
|
||||||
|
retention_state,
|
||||||
|
slot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional block timestamp carried by this variant.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn block_time(&self) -> std::option::Option<crate::RawTimestamp> {
|
||||||
|
return self.block_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns when this variant identity was first persisted.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn created_at(&self) -> crate::RawTimestamp {
|
||||||
|
return self.created_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the KSP-owned RAW format identifier.
|
||||||
|
#[must_use]
|
||||||
|
pub fn format_id(&self) -> &crate::RawFormatId {
|
||||||
|
return &self.format_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the KSP-owned RAW format version.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn format_version(&self) -> u32 {
|
||||||
|
return self.format_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this variant is selected as the current canonical representation.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_current_canonical(&self) -> bool {
|
||||||
|
return self.is_current_canonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of durable acquisition observations linked to this variant.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn observation_count(&self) -> u64 {
|
||||||
|
return self.observation_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable origin classification of this variant.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn origin(&self) -> crate::RawTransactionVariantOrigin {
|
||||||
|
return self.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the payload size when that metadata remains available.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn payload_size_bytes(&self) -> std::option::Option<u64> {
|
||||||
|
return self.payload_size_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable variant reference.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawTransactionVariantReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the logical payload-retention state of this variant.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn retention_state(&self) -> crate::RawRetentionState {
|
||||||
|
return self.retention_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Solana slot carried by this variant.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn slot(&self) -> u64 {
|
||||||
|
return self.slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit detail for one RAW transaction variant.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RawTransactionVariantDetail {
|
||||||
|
summary: crate::RawTransactionVariantSummary,
|
||||||
|
transaction: std::option::Option<crate::RawTransaction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionVariantDetail {
|
||||||
|
/// Creates one variant detail and validates any attached complete RAW transaction.
|
||||||
|
pub fn try_new(summary: crate::RawTransactionVariantSummary, transaction: std::option::Option<crate::RawTransaction>) -> crate::Result<Self> {
|
||||||
|
if let std::option::Option::Some(value) = transaction.as_ref() {
|
||||||
|
if value.reference() != summary.reference().transaction()
|
||||||
|
|| value.slot() != summary.slot()
|
||||||
|
|| value.block_time() != summary.block_time()
|
||||||
|
|| value.payload().format_id() != summary.format_id()
|
||||||
|
|| value.payload().format_version() != summary.format_version()
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_model_error("transaction"));
|
||||||
|
}
|
||||||
|
let payload_size = match u64::try_from(value.payload().bytes().len()) {
|
||||||
|
std::result::Result::Ok(size) => size,
|
||||||
|
std::result::Result::Err(_) => return std::result::Result::Err(raw_model_error("transaction")),
|
||||||
|
};
|
||||||
|
if let std::option::Option::Some(expected) = summary.payload_size_bytes()
|
||||||
|
&& payload_size != expected
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_model_error("transaction"));
|
||||||
|
}
|
||||||
|
if summary.retention_state() == crate::RawRetentionState::Purged {
|
||||||
|
return std::result::Result::Err(raw_model_error("transaction"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { summary, transaction });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the payload-free variant summary.
|
||||||
|
#[must_use]
|
||||||
|
pub fn summary(&self) -> &crate::RawTransactionVariantSummary {
|
||||||
|
return &self.summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the complete RAW transaction only when this inspection response includes local payload material.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transaction(&self) -> std::option::Option<&crate::RawTransaction> {
|
||||||
|
return self.transaction.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backend-independent random-access inspection query for RAW transaction conflict cases.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionConflictInspectionQuery {
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
page: crate::RawInspectionPageRequest,
|
||||||
|
status: std::option::Option<crate::RawTransactionConflictStatus>,
|
||||||
|
transaction: std::option::Option<crate::RawTransactionReference>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictInspectionQuery {
|
||||||
|
/// Creates one conflict-inspection query after enforcing network coherence.
|
||||||
|
pub fn try_new(
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
status: std::option::Option<crate::RawTransactionConflictStatus>,
|
||||||
|
transaction: std::option::Option<crate::RawTransactionReference>,
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
page: crate::RawInspectionPageRequest,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
if let std::option::Option::Some(reference) = transaction.as_ref()
|
||||||
|
&& reference.network() != &network
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_query_error("transaction"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { direction, network, page, status, transaction });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested deterministic traversal direction.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||||
|
return self.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the mandatory logical network scope.
|
||||||
|
#[must_use]
|
||||||
|
pub fn network(&self) -> &crate::RawNetworkId {
|
||||||
|
return &self.network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the random-access inspection page request.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn page(&self) -> crate::RawInspectionPageRequest {
|
||||||
|
return self.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional lifecycle-status filter.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn status(&self) -> std::option::Option<crate::RawTransactionConflictStatus> {
|
||||||
|
return self.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional exact transaction identity used to filter conflict cases.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transaction(&self) -> std::option::Option<&crate::RawTransactionReference> {
|
||||||
|
return self.transaction.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Durable action recorded as the resolution decision of one conflict case.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawTransactionConflictResolutionAction {
|
||||||
|
/// Keep the current canonical selector unchanged while resolving the case.
|
||||||
|
KeepCurrentCanonical,
|
||||||
|
/// Select a preserved participant as the canonical representation.
|
||||||
|
PromoteVariant,
|
||||||
|
/// Restore and select a preserved local participant as the canonical representation.
|
||||||
|
RestoreVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictResolutionAction {
|
||||||
|
/// Returns the stable persistence code for this resolution action.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn code(&self) -> &'static str {
|
||||||
|
return match self {
|
||||||
|
Self::KeepCurrentCanonical => "keep_current_canonical",
|
||||||
|
Self::PromoteVariant => "promote_variant",
|
||||||
|
Self::RestoreVariant => "restore_variant",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload-free current projection of one RAW transaction conflict case.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionConflictSummary {
|
||||||
|
canonical_revision: u64,
|
||||||
|
canonical_variant: crate::RawTransactionVariantReference,
|
||||||
|
created_at: crate::RawTimestamp,
|
||||||
|
latest_incoming_variant: crate::RawTransactionVariantReference,
|
||||||
|
latest_relation: crate::RawTransactionVariantRelation,
|
||||||
|
latest_reason: crate::RawTransactionVariantRelationReason,
|
||||||
|
latest_resolution_action: std::option::Option<crate::RawTransactionConflictResolutionAction>,
|
||||||
|
participant_count: u64,
|
||||||
|
reference: crate::RawTransactionConflictReference,
|
||||||
|
resolved_canonical_variant: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
revision: u64,
|
||||||
|
status: crate::RawTransactionConflictStatus,
|
||||||
|
updated_at: crate::RawTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictSummary {
|
||||||
|
/// Creates one current conflict projection after validating revisions, scopes and status-dependent fields.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn try_new(
|
||||||
|
reference: crate::RawTransactionConflictReference,
|
||||||
|
status: crate::RawTransactionConflictStatus,
|
||||||
|
revision: u64,
|
||||||
|
canonical_variant: crate::RawTransactionVariantReference,
|
||||||
|
canonical_revision: u64,
|
||||||
|
latest_incoming_variant: crate::RawTransactionVariantReference,
|
||||||
|
latest_relation: crate::RawTransactionVariantRelation,
|
||||||
|
latest_reason: crate::RawTransactionVariantRelationReason,
|
||||||
|
resolved_canonical_variant: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
latest_resolution_action: std::option::Option<crate::RawTransactionConflictResolutionAction>,
|
||||||
|
participant_count: u64,
|
||||||
|
created_at: crate::RawTimestamp,
|
||||||
|
updated_at: crate::RawTimestamp,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
if revision == 0 || canonical_revision == 0 || participant_count < 2 || updated_at < created_at {
|
||||||
|
return std::result::Result::Err(raw_model_error("conflict_projection"));
|
||||||
|
}
|
||||||
|
if canonical_variant.transaction() != reference.transaction() || latest_incoming_variant.transaction() != reference.transaction() {
|
||||||
|
return std::result::Result::Err(raw_model_error("variant_reference"));
|
||||||
|
}
|
||||||
|
let comparison = crate::RawTransactionVariantComparison::try_new(latest_relation, latest_reason);
|
||||||
|
if comparison.is_err()
|
||||||
|
|| !matches!(latest_relation, crate::RawTransactionVariantRelation::Conflict | crate::RawTransactionVariantRelation::Incomparable)
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_model_error("latest_relation"));
|
||||||
|
}
|
||||||
|
match status {
|
||||||
|
crate::RawTransactionConflictStatus::Open => {
|
||||||
|
if resolved_canonical_variant.is_some() || latest_resolution_action.is_some() {
|
||||||
|
return std::result::Result::Err(raw_model_error("resolution"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crate::RawTransactionConflictStatus::Resolved => {
|
||||||
|
let resolved = match resolved_canonical_variant.as_ref() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return std::result::Result::Err(raw_model_error("resolved_canonical_variant")),
|
||||||
|
};
|
||||||
|
if resolved.transaction() != reference.transaction() || latest_resolution_action.is_none() {
|
||||||
|
return std::result::Result::Err(raw_model_error("resolution"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self {
|
||||||
|
canonical_revision,
|
||||||
|
canonical_variant,
|
||||||
|
created_at,
|
||||||
|
latest_incoming_variant,
|
||||||
|
latest_relation,
|
||||||
|
latest_reason,
|
||||||
|
latest_resolution_action,
|
||||||
|
participant_count,
|
||||||
|
reference,
|
||||||
|
resolved_canonical_variant,
|
||||||
|
revision,
|
||||||
|
status,
|
||||||
|
updated_at,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current canonical-selector revision.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn canonical_revision(&self) -> u64 {
|
||||||
|
return self.canonical_revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current canonical variant.
|
||||||
|
#[must_use]
|
||||||
|
pub fn canonical_variant(&self) -> &crate::RawTransactionVariantReference {
|
||||||
|
return &self.canonical_variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns when this conflict case was created.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn created_at(&self) -> crate::RawTimestamp {
|
||||||
|
return self.created_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the most recently persisted incoming divergent participant.
|
||||||
|
#[must_use]
|
||||||
|
pub fn latest_incoming_variant(&self) -> &crate::RawTransactionVariantReference {
|
||||||
|
return &self.latest_incoming_variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the latest divergent relation.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn latest_relation(&self) -> crate::RawTransactionVariantRelation {
|
||||||
|
return self.latest_relation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the latest divergent relation reason.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn latest_reason(&self) -> crate::RawTransactionVariantRelationReason {
|
||||||
|
return self.latest_reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the latest durable resolution action when the case is resolved.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn latest_resolution_action(&self) -> std::option::Option<crate::RawTransactionConflictResolutionAction> {
|
||||||
|
return self.latest_resolution_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable participant count for this conflict case.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn participant_count(&self) -> u64 {
|
||||||
|
return self.participant_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable conflict-case reference.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawTransactionConflictReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the explicit resolved canonical variant when the case is resolved.
|
||||||
|
#[must_use]
|
||||||
|
pub fn resolved_canonical_variant(&self) -> std::option::Option<&crate::RawTransactionVariantReference> {
|
||||||
|
return self.resolved_canonical_variant.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the strictly positive current conflict revision.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn revision(&self) -> u64 {
|
||||||
|
return self.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current lifecycle status.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn status(&self) -> crate::RawTransactionConflictStatus {
|
||||||
|
return self.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns when this current projection last changed.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn updated_at(&self) -> crate::RawTimestamp {
|
||||||
|
return self.updated_at;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload-free durable participant of one RAW transaction conflict case.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionConflictParticipantSummary {
|
||||||
|
first_seen_at: crate::RawTimestamp,
|
||||||
|
first_seen_revision: u64,
|
||||||
|
variant: crate::RawTransactionVariantReference,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictParticipantSummary {
|
||||||
|
/// Creates one participant summary with a strictly positive first-seen revision.
|
||||||
|
pub fn try_new(variant: crate::RawTransactionVariantReference, first_seen_revision: u64, first_seen_at: crate::RawTimestamp) -> crate::Result<Self> {
|
||||||
|
if first_seen_revision == 0 {
|
||||||
|
return std::result::Result::Err(raw_model_error("first_seen_revision"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { first_seen_at, first_seen_revision, variant });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns when this participant first entered the conflict case.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn first_seen_at(&self) -> crate::RawTimestamp {
|
||||||
|
return self.first_seen_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the conflict revision at which this participant was first observed.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn first_seen_revision(&self) -> u64 {
|
||||||
|
return self.first_seen_revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable variant reference.
|
||||||
|
#[must_use]
|
||||||
|
pub fn variant(&self) -> &crate::RawTransactionVariantReference {
|
||||||
|
return &self.variant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detailed payload-free conflict projection including the monotone participant ledger.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RawTransactionConflictDetail {
|
||||||
|
participants: std::vec::Vec<crate::RawTransactionConflictParticipantSummary>,
|
||||||
|
summary: crate::RawTransactionConflictSummary,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictDetail {
|
||||||
|
/// Creates one detail after validating participant count, scope and required current references.
|
||||||
|
pub fn try_new(
|
||||||
|
summary: crate::RawTransactionConflictSummary,
|
||||||
|
participants: std::vec::Vec<crate::RawTransactionConflictParticipantSummary>,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
let participant_count = match u64::try_from(participants.len()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::result::Result::Err(raw_model_error("participants")),
|
||||||
|
};
|
||||||
|
if participant_count != summary.participant_count() {
|
||||||
|
return std::result::Result::Err(raw_model_error("participants"));
|
||||||
|
}
|
||||||
|
let mut ids = std::collections::BTreeSet::new();
|
||||||
|
for participant in &participants {
|
||||||
|
if participant.variant().transaction() != summary.reference().transaction()
|
||||||
|
|| participant.first_seen_revision() > summary.revision()
|
||||||
|
|| !ids.insert(participant.variant().variant_id())
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_model_error("participants"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for required in [
|
||||||
|
std::option::Option::Some(summary.canonical_variant()),
|
||||||
|
std::option::Option::Some(summary.latest_incoming_variant()),
|
||||||
|
summary.resolved_canonical_variant(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
if !ids.contains(&required.variant_id()) {
|
||||||
|
return std::result::Result::Err(raw_model_error("participants"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { participants, summary });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the monotone participant ledger projection.
|
||||||
|
#[must_use]
|
||||||
|
pub fn participants(&self) -> &[crate::RawTransactionConflictParticipantSummary] {
|
||||||
|
return self.participants.as_slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current conflict projection.
|
||||||
|
#[must_use]
|
||||||
|
pub fn summary(&self) -> &crate::RawTransactionConflictSummary {
|
||||||
|
return &self.summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stable append-only event kind for RAW transaction conflict history.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawTransactionConflictEventKind {
|
||||||
|
/// A new conflict case was opened from a proved divergence.
|
||||||
|
Opened,
|
||||||
|
/// A previously unseen variant participant was added to the conflict case.
|
||||||
|
ParticipantAdded,
|
||||||
|
/// The case was resolved while retaining the current canonical selector.
|
||||||
|
ResolvedKeepCanonical,
|
||||||
|
/// The case was resolved by selecting another participant as canonical.
|
||||||
|
ResolvedPromoteVariant,
|
||||||
|
/// The case was resolved by restoring and selecting a preserved local participant.
|
||||||
|
ResolvedRestoreVariant,
|
||||||
|
/// A resolved case became open again.
|
||||||
|
Reopened,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictEventKind {
|
||||||
|
/// Returns the stable persistence code for this history event kind.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn code(&self) -> &'static str {
|
||||||
|
return match self {
|
||||||
|
Self::Opened => "opened",
|
||||||
|
Self::ParticipantAdded => "participant_added",
|
||||||
|
Self::ResolvedKeepCanonical => "resolved_keep_canonical",
|
||||||
|
Self::ResolvedPromoteVariant => "resolved_promote_variant",
|
||||||
|
Self::ResolvedRestoreVariant => "resolved_restore_variant",
|
||||||
|
Self::Reopened => "reopened",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safe semantic origin of one conflict-history transition.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawTransactionConflictEventOrigin {
|
||||||
|
/// The transition was caused by deterministic Store/runtime behavior.
|
||||||
|
Automatic,
|
||||||
|
/// The transition was requested through an operator-facing action contract.
|
||||||
|
Operator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictEventOrigin {
|
||||||
|
/// Returns the stable persistence code for this event origin.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn code(&self) -> &'static str {
|
||||||
|
return match self {
|
||||||
|
Self::Automatic => "automatic",
|
||||||
|
Self::Operator => "operator",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload-free append-only history event for one RAW transaction conflict case.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionConflictEventSummary {
|
||||||
|
candidate: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
canonical_after: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
canonical_before: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
comparison: std::option::Option<crate::RawTransactionVariantComparison>,
|
||||||
|
kind: crate::RawTransactionConflictEventKind,
|
||||||
|
origin: crate::RawTransactionConflictEventOrigin,
|
||||||
|
reference: crate::RawTransactionConflictReference,
|
||||||
|
resolution_action: std::option::Option<crate::RawTransactionConflictResolutionAction>,
|
||||||
|
revision: u64,
|
||||||
|
timestamp: crate::RawTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictEventSummary {
|
||||||
|
/// Creates one payload-free history event after validating revision and variant scopes.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn try_new(
|
||||||
|
reference: crate::RawTransactionConflictReference,
|
||||||
|
revision: u64,
|
||||||
|
kind: crate::RawTransactionConflictEventKind,
|
||||||
|
origin: crate::RawTransactionConflictEventOrigin,
|
||||||
|
canonical_before: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
candidate: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
canonical_after: std::option::Option<crate::RawTransactionVariantReference>,
|
||||||
|
comparison: std::option::Option<crate::RawTransactionVariantComparison>,
|
||||||
|
resolution_action: std::option::Option<crate::RawTransactionConflictResolutionAction>,
|
||||||
|
timestamp: crate::RawTimestamp,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
if revision == 0 {
|
||||||
|
return std::result::Result::Err(raw_model_error("revision"));
|
||||||
|
}
|
||||||
|
for variant in [canonical_before.as_ref(), candidate.as_ref(), canonical_after.as_ref()].into_iter().flatten() {
|
||||||
|
if variant.transaction() != reference.transaction() {
|
||||||
|
return std::result::Result::Err(raw_model_error("variant_reference"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let expected_resolution = match kind {
|
||||||
|
crate::RawTransactionConflictEventKind::ResolvedKeepCanonical => {
|
||||||
|
std::option::Option::Some(crate::RawTransactionConflictResolutionAction::KeepCurrentCanonical)
|
||||||
|
},
|
||||||
|
crate::RawTransactionConflictEventKind::ResolvedPromoteVariant => {
|
||||||
|
std::option::Option::Some(crate::RawTransactionConflictResolutionAction::PromoteVariant)
|
||||||
|
},
|
||||||
|
crate::RawTransactionConflictEventKind::ResolvedRestoreVariant => {
|
||||||
|
std::option::Option::Some(crate::RawTransactionConflictResolutionAction::RestoreVariant)
|
||||||
|
},
|
||||||
|
crate::RawTransactionConflictEventKind::Opened
|
||||||
|
| crate::RawTransactionConflictEventKind::ParticipantAdded
|
||||||
|
| crate::RawTransactionConflictEventKind::Reopened => std::option::Option::None,
|
||||||
|
};
|
||||||
|
if resolution_action != expected_resolution {
|
||||||
|
return std::result::Result::Err(raw_model_error("resolution_action"));
|
||||||
|
}
|
||||||
|
if matches!(kind, crate::RawTransactionConflictEventKind::Opened | crate::RawTransactionConflictEventKind::ParticipantAdded) {
|
||||||
|
let comparison = match comparison {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return std::result::Result::Err(raw_model_error("comparison")),
|
||||||
|
};
|
||||||
|
if !matches!(comparison.relation(), crate::RawTransactionVariantRelation::Conflict | crate::RawTransactionVariantRelation::Incomparable) {
|
||||||
|
return std::result::Result::Err(raw_model_error("comparison"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self {
|
||||||
|
candidate,
|
||||||
|
canonical_after,
|
||||||
|
canonical_before,
|
||||||
|
comparison,
|
||||||
|
kind,
|
||||||
|
origin,
|
||||||
|
reference,
|
||||||
|
resolution_action,
|
||||||
|
revision,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional candidate/participant involved in this event.
|
||||||
|
#[must_use]
|
||||||
|
pub fn candidate(&self) -> std::option::Option<&crate::RawTransactionVariantReference> {
|
||||||
|
return self.candidate.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional canonical variant after this transition.
|
||||||
|
#[must_use]
|
||||||
|
pub fn canonical_after(&self) -> std::option::Option<&crate::RawTransactionVariantReference> {
|
||||||
|
return self.canonical_after.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional canonical variant before this transition.
|
||||||
|
#[must_use]
|
||||||
|
pub fn canonical_before(&self) -> std::option::Option<&crate::RawTransactionVariantReference> {
|
||||||
|
return self.canonical_before.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns optional comparison evidence attached to this history transition.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn comparison(&self) -> std::option::Option<crate::RawTransactionVariantComparison> {
|
||||||
|
return self.comparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the append-only history event kind.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn kind(&self) -> crate::RawTransactionConflictEventKind {
|
||||||
|
return self.kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the safe semantic origin of this transition.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn origin(&self) -> crate::RawTransactionConflictEventOrigin {
|
||||||
|
return self.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the owning conflict-case reference.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawTransactionConflictReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional durable resolution action.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn resolution_action(&self) -> std::option::Option<crate::RawTransactionConflictResolutionAction> {
|
||||||
|
return self.resolution_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the strictly positive conflict revision produced by this event.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn revision(&self) -> u64 {
|
||||||
|
return self.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bounded KSP timestamp of this transition.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn timestamp(&self) -> crate::RawTimestamp {
|
||||||
|
return self.timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backend-independent random-access query for one conflict case append-only history.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionConflictHistoryQuery {
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
page: crate::RawInspectionPageRequest,
|
||||||
|
reference: crate::RawTransactionConflictReference,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictHistoryQuery {
|
||||||
|
/// Creates one history-inspection query scoped to exactly one conflict case.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(reference: crate::RawTransactionConflictReference, direction: crate::RawSortDirection, page: crate::RawInspectionPageRequest) -> Self {
|
||||||
|
return Self { direction, page, reference };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested deterministic traversal direction.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||||
|
return self.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the random-access inspection page request.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn page(&self) -> crate::RawInspectionPageRequest {
|
||||||
|
return self.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the exact conflict-case scope.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawTransactionConflictReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolution strategy used when explicitly closing one RAW transaction conflict case.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum RawTransactionConflictResolution {
|
||||||
|
/// Resolve while retaining the current canonical variant.
|
||||||
|
KeepCurrentCanonical(crate::RawTransactionVariantReference),
|
||||||
|
/// Resolve by selecting another durable participant as canonical.
|
||||||
|
PromoteVariant(crate::RawTransactionVariantReference),
|
||||||
|
/// Resolve by restoring and selecting a locally preserved participant.
|
||||||
|
RestoreVariant(crate::RawTransactionVariantReference),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictResolution {
|
||||||
|
/// Returns the stable resolution-action classification.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn action(&self) -> crate::RawTransactionConflictResolutionAction {
|
||||||
|
return match self {
|
||||||
|
Self::KeepCurrentCanonical(_) => crate::RawTransactionConflictResolutionAction::KeepCurrentCanonical,
|
||||||
|
Self::PromoteVariant(_) => crate::RawTransactionConflictResolutionAction::PromoteVariant,
|
||||||
|
Self::RestoreVariant(_) => crate::RawTransactionConflictResolutionAction::RestoreVariant,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the explicit canonical target selected by this resolution.
|
||||||
|
#[must_use]
|
||||||
|
pub fn target_variant(&self) -> &crate::RawTransactionVariantReference {
|
||||||
|
return match self {
|
||||||
|
Self::KeepCurrentCanonical(value) | Self::PromoteVariant(value) | Self::RestoreVariant(value) => value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit mutable operation over one RAW transaction conflict case.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum RawTransactionConflictAction {
|
||||||
|
/// Explicitly keep the current canonical selector without resolving the case.
|
||||||
|
KeepCurrentCanonical,
|
||||||
|
/// Select another durable participant as the current canonical variant.
|
||||||
|
PromoteVariant(crate::RawTransactionVariantReference),
|
||||||
|
/// Restore and select a locally preserved participant as the current canonical variant.
|
||||||
|
RestoreVariant(crate::RawTransactionVariantReference),
|
||||||
|
/// Close the case with one explicit canonical resolution.
|
||||||
|
Resolve(crate::RawTransactionConflictResolution),
|
||||||
|
/// Reopen one previously resolved conflict case.
|
||||||
|
Reopen,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare-and-set request for one mutable RAW transaction conflict action.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionConflictActionRequest {
|
||||||
|
action: crate::RawTransactionConflictAction,
|
||||||
|
expected_canonical_revision: u64,
|
||||||
|
expected_conflict_revision: u64,
|
||||||
|
reference: crate::RawTransactionConflictReference,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionConflictActionRequest {
|
||||||
|
/// Creates one action request with strictly positive expected revisions and scope-safe target variants.
|
||||||
|
pub fn try_new(
|
||||||
|
reference: crate::RawTransactionConflictReference,
|
||||||
|
expected_conflict_revision: u64,
|
||||||
|
expected_canonical_revision: u64,
|
||||||
|
action: crate::RawTransactionConflictAction,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
if expected_conflict_revision == 0 || expected_canonical_revision == 0 {
|
||||||
|
return std::result::Result::Err(raw_model_error("expected_revision"));
|
||||||
|
}
|
||||||
|
let target = match &action {
|
||||||
|
crate::RawTransactionConflictAction::PromoteVariant(value) | crate::RawTransactionConflictAction::RestoreVariant(value) => {
|
||||||
|
std::option::Option::Some(value)
|
||||||
|
},
|
||||||
|
crate::RawTransactionConflictAction::Resolve(value) => std::option::Option::Some(value.target_variant()),
|
||||||
|
crate::RawTransactionConflictAction::KeepCurrentCanonical | crate::RawTransactionConflictAction::Reopen => std::option::Option::None,
|
||||||
|
};
|
||||||
|
if let std::option::Option::Some(target) = target
|
||||||
|
&& target.transaction() != reference.transaction()
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_model_error("target_variant"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { action, expected_canonical_revision, expected_conflict_revision, reference });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested conflict mutation.
|
||||||
|
#[must_use]
|
||||||
|
pub fn action(&self) -> &crate::RawTransactionConflictAction {
|
||||||
|
return &self.action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the expected current canonical-selector revision.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn expected_canonical_revision(&self) -> u64 {
|
||||||
|
return self.expected_canonical_revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the expected current conflict revision.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn expected_conflict_revision(&self) -> u64 {
|
||||||
|
return self.expected_conflict_revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the targeted conflict-case reference.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawTransactionConflictReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Durable backend-neutral outcome of one conflict-case action request.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawTransactionConflictActionOutcome {
|
||||||
|
/// The requested durable mutation was applied and revisioned.
|
||||||
|
Applied,
|
||||||
|
/// The requested target already exactly matches durable state; no new revision was consumed.
|
||||||
|
AlreadyAtTarget,
|
||||||
|
/// At least one expected revision no longer matches current durable state.
|
||||||
|
StaleRevision,
|
||||||
|
/// The requested operation requires an open case but the current case is not open.
|
||||||
|
ConflictNotOpen,
|
||||||
|
/// The requested operation requires a resolved case but the current case is not resolved.
|
||||||
|
ConflictNotResolved,
|
||||||
|
/// The requested variant is not a durable participant of this conflict case.
|
||||||
|
VariantNotParticipant,
|
||||||
|
/// The requested operation requires local variant payload material that is unavailable.
|
||||||
|
VariantPayloadUnavailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_model_error(field: &'static str) -> crate::Error {
|
||||||
|
return crate::Error::new(crate::ERROR_CODE_RAW_MODEL_INVALID, "invalid backend-agnostic RAW Store conflict model").with_context("field", field);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_query_error(field: &'static str) -> crate::Error {
|
||||||
|
return crate::Error::new(crate::ERROR_CODE_RAW_QUERY_INVALID, "invalid backend-agnostic RAW Store conflict query").with_context("field", field);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../../unit_tests/model/raw_conflict.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/tests/dependency_boundary.rs
|
// file: crates/ksp-store-api/tests/dependency_boundary.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
//! Dependency canaries for the Store API RAW foundation.
|
//! Dependency canaries for the Store API RAW foundation.
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ fn pre_006_source_boundary_keeps_models_and_capabilities_backend_free() {
|
|||||||
let crate_root = include_str!("../src/lib.rs");
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
let model_home = include_str!("../src/model.rs");
|
let model_home = include_str!("../src/model.rs");
|
||||||
let raw_account = include_str!("../src/model/raw_account.rs");
|
let raw_account = include_str!("../src/model/raw_account.rs");
|
||||||
|
let raw_conflict = include_str!("../src/model/raw_conflict.rs");
|
||||||
let raw_inspection = include_str!("../src/model/raw_inspection.rs");
|
let raw_inspection = include_str!("../src/model/raw_inspection.rs");
|
||||||
let raw_outcome = include_str!("../src/model/raw_outcome.rs");
|
let raw_outcome = include_str!("../src/model/raw_outcome.rs");
|
||||||
let raw_pagination = include_str!("../src/model/raw_pagination.rs");
|
let raw_pagination = include_str!("../src/model/raw_pagination.rs");
|
||||||
@@ -61,12 +62,14 @@ fn pre_006_source_boundary_keeps_models_and_capabilities_backend_free() {
|
|||||||
let raw_transaction = include_str!("../src/model/raw_transaction.rs");
|
let raw_transaction = include_str!("../src/model/raw_transaction.rs");
|
||||||
let capability_home = include_str!("../src/capability.rs");
|
let capability_home = include_str!("../src/capability.rs");
|
||||||
let raw_account_capability = include_str!("../src/capability/raw_account.rs");
|
let raw_account_capability = include_str!("../src/capability/raw_account.rs");
|
||||||
|
let raw_conflict_capability = include_str!("../src/capability/raw_conflict.rs");
|
||||||
let raw_retention_capability = include_str!("../src/capability/raw_retention.rs");
|
let raw_retention_capability = include_str!("../src/capability/raw_retention.rs");
|
||||||
let raw_transaction_capability = include_str!("../src/capability/raw_transaction.rs");
|
let raw_transaction_capability = include_str!("../src/capability/raw_transaction.rs");
|
||||||
assert!(crate_root.contains("mod capability;"));
|
assert!(crate_root.contains("mod capability;"));
|
||||||
assert!(crate_root.contains("mod error;"));
|
assert!(crate_root.contains("mod error;"));
|
||||||
assert!(crate_root.contains("mod model;"));
|
assert!(crate_root.contains("mod model;"));
|
||||||
assert!(model_home.contains("raw_account"));
|
assert!(model_home.contains("raw_account"));
|
||||||
|
assert!(model_home.contains("raw_conflict"));
|
||||||
assert!(model_home.contains("raw_inspection"));
|
assert!(model_home.contains("raw_inspection"));
|
||||||
assert!(model_home.contains("raw_outcome"));
|
assert!(model_home.contains("raw_outcome"));
|
||||||
assert!(model_home.contains("raw_pagination"));
|
assert!(model_home.contains("raw_pagination"));
|
||||||
@@ -74,12 +77,14 @@ fn pre_006_source_boundary_keeps_models_and_capabilities_backend_free() {
|
|||||||
assert!(model_home.contains("raw_retention"));
|
assert!(model_home.contains("raw_retention"));
|
||||||
assert!(model_home.contains("raw_transaction"));
|
assert!(model_home.contains("raw_transaction"));
|
||||||
assert!(capability_home.contains("raw_account"));
|
assert!(capability_home.contains("raw_account"));
|
||||||
|
assert!(capability_home.contains("raw_conflict"));
|
||||||
assert!(capability_home.contains("raw_retention"));
|
assert!(capability_home.contains("raw_retention"));
|
||||||
assert!(capability_home.contains("raw_transaction"));
|
assert!(capability_home.contains("raw_transaction"));
|
||||||
for source in [
|
for source in [
|
||||||
crate_root,
|
crate_root,
|
||||||
model_home,
|
model_home,
|
||||||
raw_account,
|
raw_account,
|
||||||
|
raw_conflict,
|
||||||
raw_inspection,
|
raw_inspection,
|
||||||
raw_outcome,
|
raw_outcome,
|
||||||
raw_pagination,
|
raw_pagination,
|
||||||
@@ -88,6 +93,7 @@ fn pre_006_source_boundary_keeps_models_and_capabilities_backend_free() {
|
|||||||
raw_transaction,
|
raw_transaction,
|
||||||
capability_home,
|
capability_home,
|
||||||
raw_account_capability,
|
raw_account_capability,
|
||||||
|
raw_conflict_capability,
|
||||||
raw_retention_capability,
|
raw_retention_capability,
|
||||||
raw_transaction_capability,
|
raw_transaction_capability,
|
||||||
] {
|
] {
|
||||||
@@ -112,6 +118,11 @@ fn pre_006_source_boundary_keeps_models_and_capabilities_backend_free() {
|
|||||||
for forbidden in ["TransactionStatusObservation", "RawLogNotification", "RawSlotEvent", "RawVoteEvent", "RawBlock", "YellowstoneEntry"] {
|
for forbidden in ["TransactionStatusObservation", "RawLogNotification", "RawSlotEvent", "RawVoteEvent", "RawBlock", "YellowstoneEntry"] {
|
||||||
assert!(!crate_root.contains(forbidden), "deferred pre.006 model leaked into Store API surface: {forbidden}");
|
assert!(!crate_root.contains(forbidden), "deferred pre.006 model leaked into Store API surface: {forbidden}");
|
||||||
}
|
}
|
||||||
|
assert!(raw_conflict_capability.contains("trait RawTransactionVariantInspectionRead"));
|
||||||
|
assert!(raw_conflict_capability.contains("trait RawTransactionConflictInspectionRead"));
|
||||||
|
assert!(raw_conflict_capability.contains("trait RawTransactionConflictHistoryRead"));
|
||||||
|
assert!(raw_conflict_capability.contains("trait RawTransactionConflictActionWrite"));
|
||||||
|
assert!(capability_home.contains("trait StoreErrorClassifier"));
|
||||||
assert!(raw_transaction_capability.contains("trait RawTransactionRead"));
|
assert!(raw_transaction_capability.contains("trait RawTransactionRead"));
|
||||||
assert!(raw_transaction_capability.contains("trait RawTransactionInspectionRead"));
|
assert!(raw_transaction_capability.contains("trait RawTransactionInspectionRead"));
|
||||||
assert!(raw_transaction_capability.contains("trait RawTransactionObservationInspectionRead"));
|
assert!(raw_transaction_capability.contains("trait RawTransactionObservationInspectionRead"));
|
||||||
@@ -129,6 +140,7 @@ fn pre_006_source_boundary_keeps_models_and_capabilities_backend_free() {
|
|||||||
for forbidden in ["trait StoreBackend", "trait Store", "PostgresStore", "MySqlStore", "Arc<dyn"] {
|
for forbidden in ["trait StoreBackend", "trait Store", "PostgresStore", "MySqlStore", "Arc<dyn"] {
|
||||||
assert!(!capability_home.contains(forbidden));
|
assert!(!capability_home.contains(forbidden));
|
||||||
assert!(!raw_account_capability.contains(forbidden));
|
assert!(!raw_account_capability.contains(forbidden));
|
||||||
|
assert!(!raw_conflict_capability.contains(forbidden));
|
||||||
assert!(!raw_retention_capability.contains(forbidden));
|
assert!(!raw_retention_capability.contains(forbidden));
|
||||||
assert!(!raw_transaction_capability.contains(forbidden));
|
assert!(!raw_transaction_capability.contains(forbidden));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/tests/external_backend.rs
|
// file: crates/ksp-store-api/tests/external_backend.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
//! External-implementation canary for object-safe Store API capabilities.
|
//! External-implementation canary for object-safe Store API capabilities.
|
||||||
|
|
||||||
@@ -215,6 +215,81 @@ impl ksp_store_api::RawTransactionRetentionWrite for ExternalMemoryBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ksp_store_api::RawTransactionVariantInspectionRead for ExternalMemoryBackend {
|
||||||
|
fn inspect_raw_transaction_variants<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a ksp_store_api::RawTransactionVariantInspectionQuery,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawInspectionPage<ksp_store_api::RawTransactionVariantSummary>>> {
|
||||||
|
let _ = query;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return ksp_store_api::RawInspectionPage::try_new(std::vec::Vec::new(), 0, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw_transaction_variant<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a ksp_store_api::RawTransactionVariantReference,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawTransactionVariantDetail>>> {
|
||||||
|
let _ = reference;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_store_api::RawTransactionConflictInspectionRead for ExternalMemoryBackend {
|
||||||
|
fn inspect_raw_transaction_conflicts<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a ksp_store_api::RawTransactionConflictInspectionQuery,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawInspectionPage<ksp_store_api::RawTransactionConflictSummary>>> {
|
||||||
|
let _ = query;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return ksp_store_api::RawInspectionPage::try_new(std::vec::Vec::new(), 0, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw_transaction_conflict<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a ksp_store_api::RawTransactionConflictReference,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawTransactionConflictDetail>>> {
|
||||||
|
let _ = reference;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_store_api::RawTransactionConflictHistoryRead for ExternalMemoryBackend {
|
||||||
|
fn inspect_raw_transaction_conflict_history<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a ksp_store_api::RawTransactionConflictHistoryQuery,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawInspectionPage<ksp_store_api::RawTransactionConflictEventSummary>>> {
|
||||||
|
let _ = query;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return ksp_store_api::RawInspectionPage::try_new(std::vec::Vec::new(), 0, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_store_api::RawTransactionConflictActionWrite for ExternalMemoryBackend {
|
||||||
|
fn apply_raw_transaction_conflict_action<'a>(
|
||||||
|
&'a self,
|
||||||
|
request: ksp_store_api::RawTransactionConflictActionRequest,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawTransactionConflictActionOutcome>> {
|
||||||
|
let _ = request;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawTransactionConflictActionOutcome::Applied);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_store_api::StoreErrorClassifier for ExternalMemoryBackend {
|
||||||
|
fn classify_store_error(&self, error: &ksp_store_api::Error) -> ksp_store_api::StoreErrorClass {
|
||||||
|
let _ = error;
|
||||||
|
return ksp_store_api::StoreErrorClass::Terminal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v0_3_8_pre_003_external_backend_implements_canonical_and_inspection_capabilities_without_runtime_crate() {
|
fn v0_3_8_pre_003_external_backend_implements_canonical_and_inspection_capabilities_without_runtime_crate() {
|
||||||
let backend = ExternalMemoryBackend;
|
let backend = ExternalMemoryBackend;
|
||||||
@@ -232,6 +307,11 @@ fn v0_3_8_pre_003_external_backend_implements_canonical_and_inspection_capabilit
|
|||||||
let account_observation_write: &dyn ksp_store_api::RawAccountObservationWrite = &backend;
|
let account_observation_write: &dyn ksp_store_api::RawAccountObservationWrite = &backend;
|
||||||
let retention_read: &dyn ksp_store_api::RawTransactionRetentionRead = &backend;
|
let retention_read: &dyn ksp_store_api::RawTransactionRetentionRead = &backend;
|
||||||
let retention_write: &dyn ksp_store_api::RawTransactionRetentionWrite = &backend;
|
let retention_write: &dyn ksp_store_api::RawTransactionRetentionWrite = &backend;
|
||||||
|
let variant_inspection: &dyn ksp_store_api::RawTransactionVariantInspectionRead = &backend;
|
||||||
|
let conflict_inspection: &dyn ksp_store_api::RawTransactionConflictInspectionRead = &backend;
|
||||||
|
let conflict_history: &dyn ksp_store_api::RawTransactionConflictHistoryRead = &backend;
|
||||||
|
let conflict_action: &dyn ksp_store_api::RawTransactionConflictActionWrite = &backend;
|
||||||
|
let error_classifier: &dyn ksp_store_api::StoreErrorClassifier = &backend;
|
||||||
let _ = transaction_read;
|
let _ = transaction_read;
|
||||||
let _ = transaction_write;
|
let _ = transaction_write;
|
||||||
let _ = transaction_inspection;
|
let _ = transaction_inspection;
|
||||||
@@ -246,5 +326,10 @@ fn v0_3_8_pre_003_external_backend_implements_canonical_and_inspection_capabilit
|
|||||||
let _ = account_observation_write;
|
let _ = account_observation_write;
|
||||||
let _ = retention_read;
|
let _ = retention_read;
|
||||||
let _ = retention_write;
|
let _ = retention_write;
|
||||||
|
let _ = variant_inspection;
|
||||||
|
let _ = conflict_inspection;
|
||||||
|
let _ = conflict_history;
|
||||||
|
let _ = conflict_action;
|
||||||
|
let _ = error_classifier;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/tests/public_api.rs
|
// file: crates/ksp-store-api/tests/public_api.rs
|
||||||
// version: 11
|
// version: 12
|
||||||
|
|
||||||
//! Integration canaries for the public `ksp-store-api` surface.
|
//! Integration canaries for the public `ksp-store-api` surface.
|
||||||
|
|
||||||
@@ -222,3 +222,20 @@ fn public_v0_3_16_pre_005_variant_comparator_is_available_from_crate_root() {
|
|||||||
let _ = comparator;
|
let _ = comparator;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_v0_3_17_pre_002_conflict_lifecycle_contracts_are_root_exported_and_dyn_compatible() {
|
||||||
|
let variant_inspection: std::option::Option<&dyn ksp_store_api::RawTransactionVariantInspectionRead> = std::option::Option::None;
|
||||||
|
let conflict_inspection: std::option::Option<&dyn ksp_store_api::RawTransactionConflictInspectionRead> = std::option::Option::None;
|
||||||
|
let conflict_history: std::option::Option<&dyn ksp_store_api::RawTransactionConflictHistoryRead> = std::option::Option::None;
|
||||||
|
let conflict_action: std::option::Option<&dyn ksp_store_api::RawTransactionConflictActionWrite> = std::option::Option::None;
|
||||||
|
let error_classifier: std::option::Option<&dyn ksp_store_api::StoreErrorClassifier> = std::option::Option::None;
|
||||||
|
assert!(variant_inspection.is_none());
|
||||||
|
assert!(conflict_inspection.is_none());
|
||||||
|
assert!(conflict_history.is_none());
|
||||||
|
assert!(conflict_action.is_none());
|
||||||
|
assert!(error_classifier.is_none());
|
||||||
|
assert_ne!(ksp_store_api::StoreErrorClass::Transient, ksp_store_api::StoreErrorClass::Terminal);
|
||||||
|
assert_ne!(ksp_store_api::RawTransactionConflictActionOutcome::AlreadyAtTarget, ksp_store_api::RawTransactionConflictActionOutcome::StaleRevision);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/tests/release_completeness.rs
|
// file: crates/ksp-store-api/tests/release_completeness.rs
|
||||||
// version: 6
|
// version: 7
|
||||||
|
|
||||||
//! Release-level boundary and completeness canaries for the backend-neutral Store API RAW surface.
|
//! Release-level boundary and completeness canaries for the backend-neutral Store API RAW surface.
|
||||||
#[test]
|
#[test]
|
||||||
@@ -20,19 +20,24 @@ fn v0_3_8_pre_003_exact_crate_root_export_inventory_is_stable() {
|
|||||||
"pub use ksp_core_lib::Pubkey;",
|
"pub use ksp_core_lib::Pubkey;",
|
||||||
"pub use ksp_core_lib::Result;",
|
"pub use ksp_core_lib::Result;",
|
||||||
"pub use self::capability::StoreApiFuture;",
|
"pub use self::capability::StoreApiFuture;",
|
||||||
|
"pub use self::capability::StoreErrorClassifier;",
|
||||||
"pub use self::capability::raw_account::RawAccountObservationInspectionRead;",
|
"pub use self::capability::raw_account::RawAccountObservationInspectionRead;",
|
||||||
"pub use self::capability::raw_account::RawAccountObservationRead;",
|
"pub use self::capability::raw_account::RawAccountObservationRead;",
|
||||||
"pub use self::capability::raw_account::RawAccountObservationWrite;",
|
"pub use self::capability::raw_account::RawAccountObservationWrite;",
|
||||||
"pub use self::capability::raw_account::RawAccountStateRead;",
|
|
||||||
"pub use self::capability::raw_account::RawAccountStateInspectionRead;",
|
"pub use self::capability::raw_account::RawAccountStateInspectionRead;",
|
||||||
|
"pub use self::capability::raw_account::RawAccountStateRead;",
|
||||||
"pub use self::capability::raw_account::RawAccountStateWrite;",
|
"pub use self::capability::raw_account::RawAccountStateWrite;",
|
||||||
|
"pub use self::capability::raw_conflict::RawTransactionConflictActionWrite;",
|
||||||
|
"pub use self::capability::raw_conflict::RawTransactionConflictHistoryRead;",
|
||||||
|
"pub use self::capability::raw_conflict::RawTransactionConflictInspectionRead;",
|
||||||
|
"pub use self::capability::raw_conflict::RawTransactionVariantInspectionRead;",
|
||||||
"pub use self::capability::raw_retention::RawTransactionRetentionRead;",
|
"pub use self::capability::raw_retention::RawTransactionRetentionRead;",
|
||||||
"pub use self::capability::raw_retention::RawTransactionRetentionWrite;",
|
"pub use self::capability::raw_retention::RawTransactionRetentionWrite;",
|
||||||
|
"pub use self::capability::raw_transaction::RawTransactionInspectionRead;",
|
||||||
"pub use self::capability::raw_transaction::RawTransactionObservationInspectionRead;",
|
"pub use self::capability::raw_transaction::RawTransactionObservationInspectionRead;",
|
||||||
"pub use self::capability::raw_transaction::RawTransactionObservationRead;",
|
"pub use self::capability::raw_transaction::RawTransactionObservationRead;",
|
||||||
"pub use self::capability::raw_transaction::RawTransactionObservationWrite;",
|
"pub use self::capability::raw_transaction::RawTransactionObservationWrite;",
|
||||||
"pub use self::capability::raw_transaction::RawTransactionRead;",
|
"pub use self::capability::raw_transaction::RawTransactionRead;",
|
||||||
"pub use self::capability::raw_transaction::RawTransactionInspectionRead;",
|
|
||||||
"pub use self::capability::raw_transaction::RawTransactionWrite;",
|
"pub use self::capability::raw_transaction::RawTransactionWrite;",
|
||||||
"pub use self::error::ERROR_CODE_RAW_CONFLICT;",
|
"pub use self::error::ERROR_CODE_RAW_CONFLICT;",
|
||||||
"pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;",
|
"pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;",
|
||||||
@@ -40,9 +45,27 @@ fn v0_3_8_pre_003_exact_crate_root_export_inventory_is_stable() {
|
|||||||
"pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;",
|
"pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;",
|
||||||
"pub use self::error::ERROR_CODE_RAW_QUERY_INVALID;",
|
"pub use self::error::ERROR_CODE_RAW_QUERY_INVALID;",
|
||||||
"pub use self::error::ERROR_CODE_RAW_RETENTION_INVALID;",
|
"pub use self::error::ERROR_CODE_RAW_RETENTION_INVALID;",
|
||||||
|
"pub use self::error::StoreErrorClass;",
|
||||||
"pub use self::model::raw_account::RawAccountObservation;",
|
"pub use self::model::raw_account::RawAccountObservation;",
|
||||||
"pub use self::model::raw_account::RawAccountState;",
|
"pub use self::model::raw_account::RawAccountState;",
|
||||||
"pub use self::model::raw_account::RawAccountStateReference;",
|
"pub use self::model::raw_account::RawAccountStateReference;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictAction;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictActionOutcome;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictActionRequest;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictDetail;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictEventKind;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictEventOrigin;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictEventSummary;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictHistoryQuery;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictInspectionQuery;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictParticipantSummary;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictReference;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictResolution;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictResolutionAction;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionConflictSummary;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionVariantDetail;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionVariantInspectionQuery;",
|
||||||
|
"pub use self::model::raw_conflict::RawTransactionVariantSummary;",
|
||||||
"pub use self::model::raw_inspection::RawAccountObservationInspectionQuery;",
|
"pub use self::model::raw_inspection::RawAccountObservationInspectionQuery;",
|
||||||
"pub use self::model::raw_inspection::RawAccountObservationSummary;",
|
"pub use self::model::raw_inspection::RawAccountObservationSummary;",
|
||||||
"pub use self::model::raw_inspection::RawAccountStateInspectionQuery;",
|
"pub use self::model::raw_inspection::RawAccountStateInspectionQuery;",
|
||||||
@@ -87,7 +110,6 @@ fn v0_3_8_pre_003_exact_crate_root_export_inventory_is_stable() {
|
|||||||
"pub use self::model::raw_retention::RawTransactionRetentionTransition;",
|
"pub use self::model::raw_retention::RawTransactionRetentionTransition;",
|
||||||
"pub use self::model::raw_retention::RawTransactionTombstone;",
|
"pub use self::model::raw_retention::RawTransactionTombstone;",
|
||||||
"pub use self::model::raw_transaction::RawTransaction;",
|
"pub use self::model::raw_transaction::RawTransaction;",
|
||||||
"pub use self::model::raw_transaction::compare_raw_transaction_variants;",
|
|
||||||
"pub use self::model::raw_transaction::RawTransactionConflictStatus;",
|
"pub use self::model::raw_transaction::RawTransactionConflictStatus;",
|
||||||
"pub use self::model::raw_transaction::RawTransactionObservation;",
|
"pub use self::model::raw_transaction::RawTransactionObservation;",
|
||||||
"pub use self::model::raw_transaction::RawTransactionReference;",
|
"pub use self::model::raw_transaction::RawTransactionReference;",
|
||||||
@@ -97,6 +119,7 @@ fn v0_3_8_pre_003_exact_crate_root_export_inventory_is_stable() {
|
|||||||
"pub use self::model::raw_transaction::RawTransactionVariantReference;",
|
"pub use self::model::raw_transaction::RawTransactionVariantReference;",
|
||||||
"pub use self::model::raw_transaction::RawTransactionVariantRelation;",
|
"pub use self::model::raw_transaction::RawTransactionVariantRelation;",
|
||||||
"pub use self::model::raw_transaction::RawTransactionVariantRelationReason;",
|
"pub use self::model::raw_transaction::RawTransactionVariantRelationReason;",
|
||||||
|
"pub use self::model::raw_transaction::compare_raw_transaction_variants;",
|
||||||
];
|
];
|
||||||
expected.sort_unstable();
|
expected.sort_unstable();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@@ -122,7 +145,16 @@ fn v0_3_8_pre_003_exact_production_module_inventory_is_raw_only() {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model_names,
|
model_names,
|
||||||
std::vec!["raw_account.rs", "raw_inspection.rs", "raw_outcome.rs", "raw_pagination.rs", "raw_primitives.rs", "raw_retention.rs", "raw_transaction.rs"]
|
std::vec![
|
||||||
|
"raw_account.rs",
|
||||||
|
"raw_conflict.rs",
|
||||||
|
"raw_inspection.rs",
|
||||||
|
"raw_outcome.rs",
|
||||||
|
"raw_pagination.rs",
|
||||||
|
"raw_primitives.rs",
|
||||||
|
"raw_retention.rs",
|
||||||
|
"raw_transaction.rs"
|
||||||
|
]
|
||||||
);
|
);
|
||||||
let capability_names = rust_file_names(root.join("capability").as_path());
|
let capability_names = rust_file_names(root.join("capability").as_path());
|
||||||
assert!(capability_names.is_ok());
|
assert!(capability_names.is_ok());
|
||||||
@@ -130,13 +162,15 @@ fn v0_3_8_pre_003_exact_production_module_inventory_is_raw_only() {
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => return,
|
std::result::Result::Err(_) => return,
|
||||||
};
|
};
|
||||||
assert_eq!(capability_names, std::vec!["raw_account.rs", "raw_retention.rs", "raw_transaction.rs"]);
|
assert_eq!(capability_names, std::vec!["raw_account.rs", "raw_conflict.rs", "raw_retention.rs", "raw_transaction.rs"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_007_public_evolutive_enums_remain_non_exhaustive() {
|
fn pre_007_public_evolutive_enums_remain_non_exhaustive() {
|
||||||
let sources = [
|
let sources = [
|
||||||
|
include_str!("../src/error.rs"),
|
||||||
|
include_str!("../src/model/raw_conflict.rs"),
|
||||||
include_str!("../src/model/raw_outcome.rs"),
|
include_str!("../src/model/raw_outcome.rs"),
|
||||||
include_str!("../src/model/raw_pagination.rs"),
|
include_str!("../src/model/raw_pagination.rs"),
|
||||||
include_str!("../src/model/raw_primitives.rs"),
|
include_str!("../src/model/raw_primitives.rs"),
|
||||||
@@ -145,6 +179,12 @@ fn pre_007_public_evolutive_enums_remain_non_exhaustive() {
|
|||||||
];
|
];
|
||||||
for enum_name in [
|
for enum_name in [
|
||||||
"RawAcquisitionOrigin",
|
"RawAcquisitionOrigin",
|
||||||
|
"RawTransactionConflictAction",
|
||||||
|
"RawTransactionConflictActionOutcome",
|
||||||
|
"RawTransactionConflictEventKind",
|
||||||
|
"RawTransactionConflictEventOrigin",
|
||||||
|
"RawTransactionConflictResolution",
|
||||||
|
"RawTransactionConflictResolutionAction",
|
||||||
"RawEntityWriteOutcome",
|
"RawEntityWriteOutcome",
|
||||||
"RawObservationWriteOutcome",
|
"RawObservationWriteOutcome",
|
||||||
"RawRetentionState",
|
"RawRetentionState",
|
||||||
@@ -156,6 +196,7 @@ fn pre_007_public_evolutive_enums_remain_non_exhaustive() {
|
|||||||
"RawTransactionVariantRelation",
|
"RawTransactionVariantRelation",
|
||||||
"RawTransactionVariantRelationReason",
|
"RawTransactionVariantRelationReason",
|
||||||
"RawTransactionVariantWriteOutcome",
|
"RawTransactionVariantWriteOutcome",
|
||||||
|
"StoreErrorClass",
|
||||||
] {
|
] {
|
||||||
assert_non_exhaustive(&sources, enum_name);
|
assert_non_exhaustive(&sources, enum_name);
|
||||||
}
|
}
|
||||||
@@ -169,6 +210,7 @@ fn pre_007_interface_store_ownership_and_negative_scope_remain_explicit() {
|
|||||||
include_str!("../src/lib.rs"),
|
include_str!("../src/lib.rs"),
|
||||||
include_str!("../src/model.rs"),
|
include_str!("../src/model.rs"),
|
||||||
include_str!("../src/model/raw_account.rs"),
|
include_str!("../src/model/raw_account.rs"),
|
||||||
|
include_str!("../src/model/raw_conflict.rs"),
|
||||||
include_str!("../src/model/raw_inspection.rs"),
|
include_str!("../src/model/raw_inspection.rs"),
|
||||||
include_str!("../src/model/raw_outcome.rs"),
|
include_str!("../src/model/raw_outcome.rs"),
|
||||||
include_str!("../src/model/raw_pagination.rs"),
|
include_str!("../src/model/raw_pagination.rs"),
|
||||||
@@ -177,6 +219,7 @@ fn pre_007_interface_store_ownership_and_negative_scope_remain_explicit() {
|
|||||||
include_str!("../src/model/raw_transaction.rs"),
|
include_str!("../src/model/raw_transaction.rs"),
|
||||||
include_str!("../src/capability.rs"),
|
include_str!("../src/capability.rs"),
|
||||||
include_str!("../src/capability/raw_account.rs"),
|
include_str!("../src/capability/raw_account.rs"),
|
||||||
|
include_str!("../src/capability/raw_conflict.rs"),
|
||||||
include_str!("../src/capability/raw_retention.rs"),
|
include_str!("../src/capability/raw_retention.rs"),
|
||||||
include_str!("../src/capability/raw_transaction.rs"),
|
include_str!("../src/capability/raw_transaction.rs"),
|
||||||
];
|
];
|
||||||
@@ -207,7 +250,9 @@ fn pre_007_interface_store_ownership_and_negative_scope_remain_explicit() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn v0_3_8_pre_003_capability_inventory_stays_fine_grained_without_runtime_facade() {
|
fn v0_3_8_pre_003_capability_inventory_stays_fine_grained_without_runtime_facade() {
|
||||||
let sources = [
|
let sources = [
|
||||||
|
include_str!("../src/capability.rs"),
|
||||||
include_str!("../src/capability/raw_account.rs"),
|
include_str!("../src/capability/raw_account.rs"),
|
||||||
|
include_str!("../src/capability/raw_conflict.rs"),
|
||||||
include_str!("../src/capability/raw_retention.rs"),
|
include_str!("../src/capability/raw_retention.rs"),
|
||||||
include_str!("../src/capability/raw_transaction.rs"),
|
include_str!("../src/capability/raw_transaction.rs"),
|
||||||
];
|
];
|
||||||
@@ -228,6 +273,11 @@ fn v0_3_8_pre_003_capability_inventory_stays_fine_grained_without_runtime_facade
|
|||||||
"pub trait RawAccountStateRead: std::marker::Send + std::marker::Sync {",
|
"pub trait RawAccountStateRead: std::marker::Send + std::marker::Sync {",
|
||||||
"pub trait RawAccountStateInspectionRead: std::marker::Send + std::marker::Sync {",
|
"pub trait RawAccountStateInspectionRead: std::marker::Send + std::marker::Sync {",
|
||||||
"pub trait RawAccountStateWrite: std::marker::Send + std::marker::Sync {",
|
"pub trait RawAccountStateWrite: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawTransactionConflictActionWrite: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawTransactionConflictHistoryRead: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawTransactionConflictInspectionRead: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawTransactionVariantInspectionRead: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait StoreErrorClassifier: std::marker::Send + std::marker::Sync {",
|
||||||
"pub trait RawTransactionObservationInspectionRead: std::marker::Send + std::marker::Sync {",
|
"pub trait RawTransactionObservationInspectionRead: std::marker::Send + std::marker::Sync {",
|
||||||
"pub trait RawTransactionObservationRead: std::marker::Send + std::marker::Sync {",
|
"pub trait RawTransactionObservationRead: std::marker::Send + std::marker::Sync {",
|
||||||
"pub trait RawTransactionObservationWrite: std::marker::Send + std::marker::Sync {",
|
"pub trait RawTransactionObservationWrite: std::marker::Send + std::marker::Sync {",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/tests/security_hardening.rs
|
// file: crates/ksp-store-api/tests/security_hardening.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
//! Adversarial and retention-race canaries for the Store API RAW foundation.
|
//! Adversarial and retention-race canaries for the Store API RAW foundation.
|
||||||
|
|
||||||
@@ -166,3 +166,62 @@ fn v0_3_8_pre_003_inspection_summaries_and_counts_reject_payload_shaped_or_incon
|
|||||||
assert!(ksp_store_api::RawInspectionPage::<u64>::try_new(std::vec::Vec::new(), 1, 2).is_err());
|
assert!(ksp_store_api::RawInspectionPage::<u64>::try_new(std::vec::Vec::new(), 1, 2).is_err());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_17_pre_002_variant_detail_debug_keeps_complete_payload_material_redacted() {
|
||||||
|
let network = match ksp_store_api::RawNetworkId::new("mainnet".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let transaction_reference = ksp_store_api::RawTransactionReference::new(network, ksp_store_api::RawTransactionSignature::new([0x71_u8; 64]));
|
||||||
|
let variant_id = match ksp_store_api::RawTransactionVariantId::try_new(1) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let variant_reference = ksp_store_api::RawTransactionVariantReference::new(transaction_reference.clone(), variant_id);
|
||||||
|
let format = match ksp_store_api::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let created_at = match ksp_store_api::RawTimestamp::from_unix_millis(1_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let payload_bytes = HOSTILE_MARKER.as_bytes().to_vec().into_boxed_slice();
|
||||||
|
let payload_size = match u64::try_from(payload_bytes.len()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let summary = ksp_store_api::RawTransactionVariantSummary::try_new(
|
||||||
|
variant_reference,
|
||||||
|
ksp_store_api::RawTransactionVariantOrigin::Native,
|
||||||
|
77,
|
||||||
|
std::option::Option::None,
|
||||||
|
format.clone(),
|
||||||
|
1,
|
||||||
|
ksp_store_api::RawRetentionState::Full,
|
||||||
|
std::option::Option::Some(payload_size),
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
created_at,
|
||||||
|
);
|
||||||
|
let summary = match summary {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let payload = ksp_store_api::RawPayload::try_new(format, 1, payload_bytes, ksp_store_api::RawContentHash::new([0x72_u8; 32]));
|
||||||
|
let payload = match payload {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let transaction = ksp_store_api::RawTransaction::new(transaction_reference, 77, std::option::Option::None, payload);
|
||||||
|
let detail = ksp_store_api::RawTransactionVariantDetail::try_new(summary, std::option::Option::Some(transaction));
|
||||||
|
let detail = match detail {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let debug = std::format!("{detail:?}");
|
||||||
|
assert!(!debug.contains(HOSTILE_MARKER));
|
||||||
|
assert!(!debug.contains("payload: ["));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
293
crates/ksp-store-api/unit_tests/model/raw_conflict.rs
Normal file
293
crates/ksp-store-api/unit_tests/model/raw_conflict.rs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
// file: crates/ksp-store-api/unit_tests/model/raw_conflict.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
fn network(value: &str) -> std::option::Option<crate::RawNetworkId> {
|
||||||
|
return crate::RawNetworkId::new(value.to_owned()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transaction_reference(network: crate::RawNetworkId, signature_byte: u8) -> crate::RawTransactionReference {
|
||||||
|
return crate::RawTransactionReference::new(network, crate::RawTransactionSignature::new([signature_byte; 64]));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn variant_reference(transaction: crate::RawTransactionReference, variant_id: u64) -> std::option::Option<crate::RawTransactionVariantReference> {
|
||||||
|
let variant_id = match crate::RawTransactionVariantId::try_new(variant_id) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
return std::option::Option::Some(crate::RawTransactionVariantReference::new(transaction, variant_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp(value: u64) -> std::option::Option<crate::RawTimestamp> {
|
||||||
|
return crate::RawTimestamp::from_unix_millis(value).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page() -> std::option::Option<crate::RawInspectionPageRequest> {
|
||||||
|
let limit = match crate::RawPageLimit::new(25) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
return std::option::Option::Some(crate::RawInspectionPageRequest::new(0, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_17_pre_002_conflict_and_variant_queries_enforce_network_scope() {
|
||||||
|
let mainnet = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let devnet = match network("devnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let transaction = transaction_reference(devnet, 1);
|
||||||
|
let page = match page() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
crate::RawTransactionVariantInspectionQuery::try_new(
|
||||||
|
mainnet.clone(),
|
||||||
|
std::option::Option::Some(transaction.clone()),
|
||||||
|
crate::RawSortDirection::Ascending,
|
||||||
|
page,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
crate::RawTransactionConflictInspectionQuery::try_new(
|
||||||
|
mainnet,
|
||||||
|
std::option::Option::Some(crate::RawTransactionConflictStatus::Open),
|
||||||
|
std::option::Option::Some(transaction),
|
||||||
|
crate::RawSortDirection::Ascending,
|
||||||
|
page,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_17_pre_002_action_request_requires_positive_double_revision_and_same_transaction_target() {
|
||||||
|
let mainnet = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let transaction = transaction_reference(mainnet.clone(), 2);
|
||||||
|
let conflict = crate::RawTransactionConflictReference::new(transaction.clone());
|
||||||
|
let target = match variant_reference(transaction, 2) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
crate::RawTransactionConflictActionRequest::try_new(conflict.clone(), 0, 1, crate::RawTransactionConflictAction::PromoteVariant(target.clone()),)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
let other = transaction_reference(mainnet, 3);
|
||||||
|
let other_target = match variant_reference(other, 3) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
crate::RawTransactionConflictActionRequest::try_new(conflict.clone(), 1, 1, crate::RawTransactionConflictAction::PromoteVariant(other_target),)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
let request = crate::RawTransactionConflictActionRequest::try_new(
|
||||||
|
conflict,
|
||||||
|
5,
|
||||||
|
7,
|
||||||
|
crate::RawTransactionConflictAction::Resolve(crate::RawTransactionConflictResolution::PromoteVariant(target)),
|
||||||
|
);
|
||||||
|
assert!(request.is_ok());
|
||||||
|
let request = match request {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(request.expected_conflict_revision(), 5);
|
||||||
|
assert_eq!(request.expected_canonical_revision(), 7);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_17_pre_002_conflict_projection_enforces_status_resolution_and_conflict_relation() {
|
||||||
|
let mainnet = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let transaction = transaction_reference(mainnet, 4);
|
||||||
|
let conflict = crate::RawTransactionConflictReference::new(transaction.clone());
|
||||||
|
let canonical = match variant_reference(transaction.clone(), 1) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let incoming = match variant_reference(transaction, 2) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let created_at = match timestamp(1_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let updated_at = match timestamp(2_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let open = crate::RawTransactionConflictSummary::try_new(
|
||||||
|
conflict.clone(),
|
||||||
|
crate::RawTransactionConflictStatus::Open,
|
||||||
|
3,
|
||||||
|
canonical.clone(),
|
||||||
|
2,
|
||||||
|
incoming.clone(),
|
||||||
|
crate::RawTransactionVariantRelation::Conflict,
|
||||||
|
crate::RawTransactionVariantRelationReason::CanonicalPayloadConflict,
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::None,
|
||||||
|
2,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
);
|
||||||
|
assert!(open.is_ok());
|
||||||
|
let invalid_resolved = crate::RawTransactionConflictSummary::try_new(
|
||||||
|
conflict,
|
||||||
|
crate::RawTransactionConflictStatus::Resolved,
|
||||||
|
4,
|
||||||
|
canonical,
|
||||||
|
3,
|
||||||
|
incoming,
|
||||||
|
crate::RawTransactionVariantRelation::Exact,
|
||||||
|
crate::RawTransactionVariantRelationReason::ExactCanonicalContent,
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::None,
|
||||||
|
2,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
);
|
||||||
|
assert!(invalid_resolved.is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_17_pre_002_conflict_detail_requires_unique_complete_participant_projection() {
|
||||||
|
let mainnet = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let transaction = transaction_reference(mainnet, 5);
|
||||||
|
let conflict = crate::RawTransactionConflictReference::new(transaction.clone());
|
||||||
|
let canonical = match variant_reference(transaction.clone(), 1) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let incoming = match variant_reference(transaction, 2) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let created_at = match timestamp(1_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let updated_at = match timestamp(2_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let summary = crate::RawTransactionConflictSummary::try_new(
|
||||||
|
conflict,
|
||||||
|
crate::RawTransactionConflictStatus::Open,
|
||||||
|
2,
|
||||||
|
canonical.clone(),
|
||||||
|
1,
|
||||||
|
incoming.clone(),
|
||||||
|
crate::RawTransactionVariantRelation::Incomparable,
|
||||||
|
crate::RawTransactionVariantRelationReason::UnsupportedCanonicalDifference,
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::None,
|
||||||
|
2,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
);
|
||||||
|
let summary = match summary {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let first = match crate::RawTransactionConflictParticipantSummary::try_new(canonical, 1, created_at) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let second = match crate::RawTransactionConflictParticipantSummary::try_new(incoming, 2, updated_at) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(crate::RawTransactionConflictDetail::try_new(summary, std::vec![first, second]).is_ok());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_17_pre_002_history_event_requires_resolution_action_only_for_resolution_events() {
|
||||||
|
let mainnet = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let transaction = transaction_reference(mainnet, 6);
|
||||||
|
let conflict = crate::RawTransactionConflictReference::new(transaction.clone());
|
||||||
|
let canonical = match variant_reference(transaction.clone(), 1) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let incoming = match variant_reference(transaction, 2) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let comparison = crate::RawTransactionVariantComparison::try_new(
|
||||||
|
crate::RawTransactionVariantRelation::Conflict,
|
||||||
|
crate::RawTransactionVariantRelationReason::CanonicalPayloadConflict,
|
||||||
|
);
|
||||||
|
let comparison = match comparison {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let at = match timestamp(1_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
crate::RawTransactionConflictEventSummary::try_new(
|
||||||
|
conflict.clone(),
|
||||||
|
1,
|
||||||
|
crate::RawTransactionConflictEventKind::Opened,
|
||||||
|
crate::RawTransactionConflictEventOrigin::Automatic,
|
||||||
|
std::option::Option::Some(canonical.clone()),
|
||||||
|
std::option::Option::Some(incoming),
|
||||||
|
std::option::Option::Some(canonical.clone()),
|
||||||
|
std::option::Option::Some(comparison),
|
||||||
|
std::option::Option::None,
|
||||||
|
at,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
crate::RawTransactionConflictEventSummary::try_new(
|
||||||
|
conflict,
|
||||||
|
2,
|
||||||
|
crate::RawTransactionConflictEventKind::ResolvedKeepCanonical,
|
||||||
|
crate::RawTransactionConflictEventOrigin::Operator,
|
||||||
|
std::option::Option::Some(canonical.clone()),
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::Some(canonical),
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::None,
|
||||||
|
at,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_17_pre_002_action_outcomes_and_codes_are_stable_and_distinct() {
|
||||||
|
assert_eq!(crate::RawTransactionConflictResolutionAction::KeepCurrentCanonical.code(), "keep_current_canonical");
|
||||||
|
assert_eq!(crate::RawTransactionConflictEventKind::ParticipantAdded.code(), "participant_added");
|
||||||
|
assert_eq!(crate::RawTransactionConflictEventOrigin::Operator.code(), "operator");
|
||||||
|
assert_ne!(crate::RawTransactionConflictActionOutcome::Applied, crate::RawTransactionConflictActionOutcome::AlreadyAtTarget);
|
||||||
|
assert_ne!(crate::RawTransactionConflictActionOutcome::AlreadyAtTarget, crate::RawTransactionConflictActionOutcome::StaleRevision);
|
||||||
|
return;
|
||||||
|
}
|
||||||
334
deltas/0.3.17/pre.002.md
Normal file
334
deltas/0.3.17/pre.002.md
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
<!-- file: deltas/0.3.17/pre.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.17-pre.002` — contrats Store API conflict lifecycle et classification d'erreur
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.17-pre.001 appliquée
|
||||||
|
workspace.package.version = 0.3.17-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur reçu le 24 septembre 2026 est propre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all -- --check PASS
|
||||||
|
General Rust rule audit clean
|
||||||
|
Rust export completeness audit 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit clean
|
||||||
|
Markdown table audit clean (352 table(s), 965 file(s))
|
||||||
|
cargo check --workspace PASS, 6.11 s
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
PASS, 25.28 s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Figer la surface backend-neutral requise par le cycle de vie complet des variantes/conflits avant toute migration PostgreSQL V004.
|
||||||
|
|
||||||
|
Cette tranche reste strictement dans `ksp-store-api` et ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
inspection des variantes
|
||||||
|
inspection des conflict cases
|
||||||
|
participant ledger DTO
|
||||||
|
historique append-only DTO/query
|
||||||
|
actions compare-and-set à double revision
|
||||||
|
outcomes stale/idempotent explicites
|
||||||
|
StoreErrorClass Transient/Terminal
|
||||||
|
StoreErrorClassifier backend-neutral
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle ne modifie encore :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune migration SQL
|
||||||
|
aucun backend PostgreSQL
|
||||||
|
aucun ksp-store-lib
|
||||||
|
aucun Worker
|
||||||
|
aucun Transport/Config
|
||||||
|
aucun Job Backfill
|
||||||
|
aucune application Desk
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version workspace
|
||||||
|
|
||||||
|
`Cargo.toml` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
header version : 652 -> 653
|
||||||
|
workspace : 0.3.17-pre.1 -> 0.3.17-pre.2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/src/capability/raw_conflict.rs
|
||||||
|
crates/ksp-store-api/src/model/raw_conflict.rs
|
||||||
|
crates/ksp-store-api/unit_tests/model/raw_conflict.rs
|
||||||
|
deltas/0.3.17/pre.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-store-api/src/capability.rs
|
||||||
|
crates/ksp-store-api/src/error.rs
|
||||||
|
crates/ksp-store-api/src/lib.rs
|
||||||
|
crates/ksp-store-api/src/model.rs
|
||||||
|
crates/ksp-store-api/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-store-api/tests/external_backend.rs
|
||||||
|
crates/ksp-store-api/tests/public_api.rs
|
||||||
|
crates/ksp-store-api/tests/release_completeness.rs
|
||||||
|
crates/ksp-store-api/tests/security_hardening.rs
|
||||||
|
docs/validation/034-V0_3_17_RAW_OPERATIONAL_RESILIENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contrats variantes
|
||||||
|
|
||||||
|
La lecture opérateur des variantes est portée par :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionVariantInspectionQuery
|
||||||
|
RawTransactionVariantSummary
|
||||||
|
RawTransactionVariantDetail
|
||||||
|
RawTransactionVariantInspectionRead
|
||||||
|
```
|
||||||
|
|
||||||
|
La query est random-access bornée via `RawInspectionPageRequest`, exige un network et accepte seulement un filtre transaction exact cohérent avec ce network.
|
||||||
|
|
||||||
|
Le summary reste payload-free et expose uniquement les métadonnées nécessaires :
|
||||||
|
|
||||||
|
```text
|
||||||
|
variant reference
|
||||||
|
origin
|
||||||
|
slot / block_time
|
||||||
|
format id/version
|
||||||
|
retention state
|
||||||
|
payload size si disponible
|
||||||
|
is_current_canonical
|
||||||
|
observation_count
|
||||||
|
created_at
|
||||||
|
```
|
||||||
|
|
||||||
|
`RawTransactionVariantDetail` peut porter la transaction RAW complète uniquement dans la lecture explicite d'un variant. Le constructeur vérifie transaction identity, slot, block time, format et taille déclarée ; un variant `Purged` ne peut pas prétendre contenir encore le payload.
|
||||||
|
|
||||||
|
## Contrats conflict case
|
||||||
|
|
||||||
|
Identité :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionConflictReference
|
||||||
|
-> RawTransactionReference
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspection :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionConflictInspectionQuery
|
||||||
|
RawTransactionConflictSummary
|
||||||
|
RawTransactionConflictParticipantSummary
|
||||||
|
RawTransactionConflictDetail
|
||||||
|
RawTransactionConflictInspectionRead
|
||||||
|
```
|
||||||
|
|
||||||
|
La projection courante conserve explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
status Open/Resolved
|
||||||
|
conflict revision > 0
|
||||||
|
canonical variant
|
||||||
|
canonical selector revision > 0
|
||||||
|
latest incoming variant
|
||||||
|
latest Conflict/Incomparable relation + reason
|
||||||
|
resolved canonical variant si Resolved
|
||||||
|
latest resolution action si Resolved
|
||||||
|
participant_count >= 2
|
||||||
|
created_at / updated_at
|
||||||
|
```
|
||||||
|
|
||||||
|
Les références canonical/incoming/resolved doivent appartenir à la même transaction que le conflict case.
|
||||||
|
|
||||||
|
Le detail valide que le participant ledger :
|
||||||
|
|
||||||
|
```text
|
||||||
|
contient exactement participant_count entrées
|
||||||
|
n'a aucun variant_id dupliqué
|
||||||
|
reste dans le même scope transaction
|
||||||
|
n'annonce aucun first_seen_revision > current conflict revision
|
||||||
|
contient canonical/latest incoming/resolved target requis
|
||||||
|
```
|
||||||
|
|
||||||
|
## Historique append-only
|
||||||
|
|
||||||
|
Types ajoutés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionConflictEventKind
|
||||||
|
RawTransactionConflictEventOrigin
|
||||||
|
RawTransactionConflictEventSummary
|
||||||
|
RawTransactionConflictHistoryQuery
|
||||||
|
RawTransactionConflictHistoryRead
|
||||||
|
```
|
||||||
|
|
||||||
|
Event kinds figés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Opened
|
||||||
|
ParticipantAdded
|
||||||
|
ResolvedKeepCanonical
|
||||||
|
ResolvedPromoteVariant
|
||||||
|
ResolvedRestoreVariant
|
||||||
|
Reopened
|
||||||
|
```
|
||||||
|
|
||||||
|
Origins sûres :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Automatic
|
||||||
|
Operator
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun acteur libre, texte backend, SQL ou payload RAW n'entre dans le DTO d'historique.
|
||||||
|
|
||||||
|
Une transition de résolution exige exactement le `RawTransactionConflictResolutionAction` correspondant. `Opened` et `ParticipantAdded` exigent une comparaison durable `Conflict` ou `Incomparable`.
|
||||||
|
|
||||||
|
## Actions compare-and-set
|
||||||
|
|
||||||
|
Types :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionConflictResolutionAction
|
||||||
|
RawTransactionConflictResolution
|
||||||
|
RawTransactionConflictAction
|
||||||
|
RawTransactionConflictActionRequest
|
||||||
|
RawTransactionConflictActionOutcome
|
||||||
|
RawTransactionConflictActionWrite
|
||||||
|
```
|
||||||
|
|
||||||
|
Actions disponibles :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KeepCurrentCanonical
|
||||||
|
PromoteVariant(target)
|
||||||
|
RestoreVariant(target)
|
||||||
|
Resolve(explicit canonical resolution)
|
||||||
|
Reopen
|
||||||
|
```
|
||||||
|
|
||||||
|
Chaque request transporte obligatoirement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
conflict reference
|
||||||
|
expected_conflict_revision > 0
|
||||||
|
expected_canonical_revision > 0
|
||||||
|
action
|
||||||
|
```
|
||||||
|
|
||||||
|
Tout target variant doit appartenir à la transaction du conflict case.
|
||||||
|
|
||||||
|
Outcomes backend-neutral :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Applied
|
||||||
|
AlreadyAtTarget
|
||||||
|
StaleRevision
|
||||||
|
ConflictNotOpen
|
||||||
|
ConflictNotResolved
|
||||||
|
VariantNotParticipant
|
||||||
|
VariantPayloadUnavailable
|
||||||
|
```
|
||||||
|
|
||||||
|
`AlreadyAtTarget` et `StaleRevision` restent volontairement distincts : une répétition idempotente n'est pas une perte de compare-and-set concurrente.
|
||||||
|
|
||||||
|
## Classification Store error
|
||||||
|
|
||||||
|
`ksp-store-api` expose désormais :
|
||||||
|
|
||||||
|
```text
|
||||||
|
StoreErrorClass::Transient
|
||||||
|
StoreErrorClass::Terminal
|
||||||
|
StoreErrorClassifier
|
||||||
|
```
|
||||||
|
|
||||||
|
Le classifier reçoit une `ksp_core_lib::Error` déjà projetée/redacted. Le Worker futur consommera ce contrat au lieu de comparer des codes PostgreSQL ou des chaînes libres.
|
||||||
|
|
||||||
|
Cette tranche ne classe encore aucun SQLSTATE concret : le raffinement physique reste réservé à `pre.006`. La politique fail-closed définie par le plan reste inchangée : tout code non explicitement reconnu par l'implémentation future sera `Terminal`.
|
||||||
|
|
||||||
|
## Frontières préservées
|
||||||
|
|
||||||
|
Le manifest `ksp-store-api` reste inchangé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime dependency exacte = ksp-core-lib uniquement
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun nouveau crate externe n'est introduit.
|
||||||
|
|
||||||
|
Tous les nouveaux items publics sont réexportés depuis la racine `ksp-store-api`; aucun `pub mod` n'est ajouté. Les enums évolutifs sont `#[non_exhaustive]`.
|
||||||
|
|
||||||
|
Les capabilities restent object-safe et implémentables par un backend externe sans dépendre de `ksp-store-lib` ou PostgreSQL.
|
||||||
|
|
||||||
|
## Tests/canaris ajoutés ou étendus
|
||||||
|
|
||||||
|
La tranche couvre statiquement et par tests Rust :
|
||||||
|
|
||||||
|
```text
|
||||||
|
network coherence des queries variant/conflict
|
||||||
|
double revision strictement positive
|
||||||
|
scope transaction des targets d'action
|
||||||
|
status Open/Resolved et projection resolution cohérente
|
||||||
|
relation conflict/incomparable requise pour le conflict current
|
||||||
|
participant ledger complet, unique et revision-safe
|
||||||
|
history event/action consistency
|
||||||
|
outcomes Applied/AlreadyAtTarget/StaleRevision distincts
|
||||||
|
codes event/origin/resolution stables
|
||||||
|
variant detail payload debug redacted
|
||||||
|
object safety des quatre nouvelles capabilities + classifier
|
||||||
|
inventaire exact crate-root/module/capability
|
||||||
|
frontière dépendances backend-neutral
|
||||||
|
#[non_exhaustive] des nouveaux enums
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation locale de l'assemblage
|
||||||
|
|
||||||
|
L'environnement d'assemblage ne fournit toujours pas `cargo`, `rustc` ou `rustfmt`. Aucun PASS Cargo n'est inventé pour `pre.002`.
|
||||||
|
|
||||||
|
Audits réellement exécutés après les changements Rust :
|
||||||
|
|
||||||
|
```text
|
||||||
|
General Rust rule audit clean
|
||||||
|
Rust export completeness audit 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit clean
|
||||||
|
Markdown table audit clean (352 table(s), 966 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate Cargo utilisateur reste autoritaire pour confirmer compilation, rustfmt, Clippy et tests.
|
||||||
|
|
||||||
|
## Gate requis après application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
|
||||||
|
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-store-api --all-targets --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
Ne pas lancer de test PostgreSQL live pour cette tranche : aucune migration ni implémentation backend n'est encore modifiée.
|
||||||
|
|
||||||
|
## Prochaine tranche
|
||||||
|
|
||||||
|
Après gate propre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.17-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
Objet prévu : migration PostgreSQL V004 additive pour participants, historique append-only, projection de résolution et bootstrap legacy, avec canaris garantissant l'immuabilité byte/checksum de V000/V001/V002/V003.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/034-V0_3_17_RAW_OPERATIONAL_RESILIENCE.md -->
|
<!-- file: docs/validation/034-V0_3_17_RAW_OPERATIONAL_RESILIENCE.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# Validation `0.3.17` — résilience opérationnelle et cycle de vie des variantes RAW
|
# Validation `0.3.17` — résilience opérationnelle et cycle de vie des variantes RAW
|
||||||
|
|
||||||
@@ -568,3 +568,63 @@ cargo clippy --workspace --all-targets --all-features -- -D warnings
|
|||||||
```
|
```
|
||||||
|
|
||||||
Aucun test live n'est requis par cette tranche documentaire de planning.
|
Aucun test live n'est requis par cette tranche documentaire de planning.
|
||||||
|
|
||||||
|
## 23. Gate opérateur `0.3.17-pre.001` reçu avant `pre.002`
|
||||||
|
|
||||||
|
Le 24 septembre 2026, l'opérateur a exécuté sur son checkout après application de `0.3.17-pre.001` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultats fournis :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all -- --check aucun diagnostic
|
||||||
|
General Rust rule audit clean
|
||||||
|
Rust export completeness audit 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit clean
|
||||||
|
Markdown table audit clean (352 table(s), 965 file(s))
|
||||||
|
cargo check --workspace PASS, 0.3.17-pre.1, 6.11 s
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
PASS, 0.3.17-pre.1, 25.28 s
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce gate ferme `pre.001` et autorise `0.3.17-pre.002`. Aucun test runtime/live supplémentaire n'était requis par le delta `pre.001`.
|
||||||
|
|
||||||
|
## 24. Assemblage `0.3.17-pre.002`
|
||||||
|
|
||||||
|
La tranche `pre.002` matérialise uniquement les contrats backend-neutral prévus au sizing :
|
||||||
|
|
||||||
|
```text
|
||||||
|
variant inspection
|
||||||
|
conflict inspection + participants
|
||||||
|
append-only history DTO/query
|
||||||
|
double-revision action request
|
||||||
|
stale/idempotent outcomes
|
||||||
|
StoreErrorClass + StoreErrorClassifier
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune migration V004, implémentation PostgreSQL, façade Store, politique Worker retry ou reconnexion Transport n'est introduite.
|
||||||
|
|
||||||
|
Audits locaux après assemblage :
|
||||||
|
|
||||||
|
```text
|
||||||
|
General Rust rule audit clean
|
||||||
|
Rust export completeness audit 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit clean
|
||||||
|
Markdown table audit clean (352 table(s), 966 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
Limitation inchangée de l'environnement d'assemblage :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo/rustc/rustfmt unavailable
|
||||||
|
cargo fmt/check/clippy/test pre.002 NOT RUN ici
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur requis pour fermer `pre.002` est celui de `deltas/0.3.17/pre.002.md`, incluant `cargo test -p ksp-store-api --all-targets --all-features`.
|
||||||
|
|||||||
Reference in New Issue
Block a user