v0.3.1-pre.003

This commit is contained in:
2026-08-29 07:51:09 +02:00
parent 11e4cde77f
commit ecc5607fd4
13 changed files with 1274 additions and 34 deletions

View File

@@ -0,0 +1,9 @@
// file: crates/ksp-store-api/src/error.rs
// version: 1
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
pub const ERROR_CODE_RAW_MODEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_model_invalid");
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.
pub const ERROR_CODE_RAW_PAYLOAD_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_payload_invalid");
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
pub const ERROR_CODE_RAW_PROVENANCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_provenance_invalid");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-api/src/lib.rs
// version: 1
// version: 2
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -17,8 +17,49 @@
//! dispatch remain outside this crate.
mod capability;
mod error;
mod model;
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.
pub use self::error::ERROR_CODE_RAW_PAYLOAD_INVALID;
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
pub use self::model::raw_primitives::MAX_RAW_CODE_BYTES;
/// Maximum KSP-owned canonical RAW payload admitted by the Store API.
pub use self::model::raw_primitives::MAX_RAW_PAYLOAD_BYTES;
/// Maximum source-wire payload size recorded as acquisition metadata.
pub use self::model::raw_primitives::MAX_RAW_SOURCE_PAYLOAD_BYTES;
/// Maximum supported Unix millisecond timestamp.
pub use self::model::raw_primitives::MAX_RAW_UNIX_MILLIS;
/// Origin category describing why one acquisition was performed.
pub use self::model::raw_primitives::RawAcquisitionOrigin;
/// Safe source-independent acquisition provenance attached to one persisted observation.
pub use self::model::raw_primitives::RawAcquisitionProvenance;
/// Fixed-size digest identifying canonical or source bytes without retaining them.
pub use self::model::raw_primitives::RawContentHash;
/// Bounded identifier of one KSP-owned source-independent RAW persistence format.
pub use self::model::raw_primitives::RawFormatId;
/// Bounded logical network/cluster identifier used in backend-independent Store identities.
pub use self::model::raw_primitives::RawNetworkId;
/// Stable deterministic idempotence key for one persisted acquisition observation.
pub use self::model::raw_primitives::RawObservationKey;
/// Bounded source-independent KSP RAW persistence payload.
pub use self::model::raw_primitives::RawPayload;
/// Bounded logical code used by acquisition provenance fields.
pub use self::model::raw_primitives::RawProvenanceCode;
/// Bounded UTC timestamp represented as whole milliseconds since Unix epoch.
pub use self::model::raw_primitives::RawTimestamp;
/// Canonical 64-byte Solana transaction signature used by Store identities.
pub use self::model::raw_primitives::RawTransactionSignature;
/// Canonical source-independent N1 RAW transaction persisted by Store backends.
pub use self::model::raw_transaction::RawTransaction;
/// Persistable acquisition observation linked to one canonical RAW transaction.
pub use self::model::raw_transaction::RawTransactionObservation;
/// Durable backend-independent identity of one canonical RAW transaction.
pub use self::model::raw_transaction::RawTransactionReference;
/// Common KSP error type used by Store-facing contracts.
pub use ksp_core_lib::Error;
/// Stable structured code identifying a KSP error category and condition.

View File

@@ -1,9 +1,12 @@
// file: crates/ksp-store-api/src/model.rs
// version: 1
// version: 2
//! Private home for persistent Store models.
//!
//! The current release is N1 RAW-only. Future N2 STRUCTURAL models are a
//! separate data layer and are not introduced by this scaffold. Persistent
//! models and backend capabilities remain separate even when one capability
//! operates on one or more models.
//! separate data layer and are not introduced here. Persistent models and
//! backend capabilities remain separate even when one capability operates on
//! one or more models.
pub(crate) mod raw_primitives;
pub(crate) mod raw_transaction;

View File

@@ -0,0 +1,449 @@
// file: crates/ksp-store-api/src/model/raw_primitives.rs
// version: 1
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
pub const MAX_RAW_CODE_BYTES: usize = 128;
/// Maximum KSP-owned canonical RAW payload admitted by the Store API.
///
/// This is a Store admission guard, not a Solana protocol-size claim.
pub const MAX_RAW_PAYLOAD_BYTES: usize = 16 * 1024 * 1024;
/// Maximum source-wire payload size recorded as acquisition metadata.
///
/// The source payload itself is never retained by this metadata field.
pub const MAX_RAW_SOURCE_PAYLOAD_BYTES: u64 = 64 * 1024 * 1024;
/// Maximum supported Unix millisecond timestamp (`9999-12-31T23:59:59.999Z`).
pub const MAX_RAW_UNIX_MILLIS: u64 = 253_402_300_799_999;
/// Fixed-size digest identifying canonical or source bytes without retaining them.
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct RawContentHash([u8; 32]);
impl RawContentHash {
/// Creates one opaque 32-byte KSP content digest.
#[must_use]
pub const fn new(bytes: [u8; 32]) -> Self {
return Self(bytes);
}
/// Returns the exact digest bytes.
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
return &self.0;
}
}
impl std::fmt::Debug for RawContentHash {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str("RawContentHash(..)");
}
}
/// Stable deterministic idempotence key for one persisted acquisition observation.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct RawObservationKey([u8; 32]);
impl RawObservationKey {
/// Creates one producer-owned deterministic observation key.
#[must_use]
pub const fn new(bytes: [u8; 32]) -> Self {
return Self(bytes);
}
/// Returns the exact observation-key bytes.
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
return &self.0;
}
}
/// Canonical 64-byte Solana transaction signature used by Store identities.
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RawTransactionSignature([u8; 64]);
impl RawTransactionSignature {
/// Creates one canonical signature from already-decoded Solana signature bytes.
#[must_use]
pub const fn new(bytes: [u8; 64]) -> Self {
return Self(bytes);
}
/// Returns the exact signature bytes.
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 64] {
return &self.0;
}
}
impl std::fmt::Debug for RawTransactionSignature {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str("RawTransactionSignature(..)");
}
}
/// Bounded logical network/cluster identifier used in backend-independent Store identities.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RawNetworkId(std::string::String);
impl RawNetworkId {
/// Creates one safe non-empty network/cluster identifier.
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
let value = value.into();
if !valid_raw_code(value.as_str()) {
return std::result::Result::Err(raw_model_error("network"));
}
return std::result::Result::Ok(Self(value));
}
/// Returns the network/cluster identifier.
#[must_use]
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
/// Bounded logical code used by acquisition provenance fields.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RawProvenanceCode(std::string::String);
impl RawProvenanceCode {
/// Creates one safe non-empty provenance code.
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
let value = value.into();
if !valid_raw_code(value.as_str()) {
return std::result::Result::Err(raw_provenance_error("code"));
}
return std::result::Result::Ok(Self(value));
}
/// Returns the validated provenance code.
#[must_use]
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
/// Bounded identifier of one KSP-owned source-independent RAW persistence format.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RawFormatId(std::string::String);
impl RawFormatId {
/// Creates one safe non-empty RAW format identifier.
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
let value = value.into();
if !valid_raw_code(value.as_str()) {
return std::result::Result::Err(raw_payload_error("format_id"));
}
return std::result::Result::Ok(Self(value));
}
/// Returns the KSP-owned RAW format identifier.
#[must_use]
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
/// Bounded UTC timestamp represented as whole milliseconds since Unix epoch.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RawTimestamp {
unix_millis: u64,
}
impl RawTimestamp {
/// Creates a bounded UTC timestamp from Unix milliseconds.
pub fn from_unix_millis(unix_millis: u64) -> ksp_core_lib::Result<Self> {
if unix_millis > crate::MAX_RAW_UNIX_MILLIS {
return std::result::Result::Err(raw_model_error("timestamp"));
}
return std::result::Result::Ok(Self { unix_millis });
}
/// Returns whole milliseconds since Unix epoch.
#[must_use]
pub const fn unix_millis(&self) -> u64 {
return self.unix_millis;
}
}
/// Origin category describing why one acquisition was performed.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawAcquisitionOrigin {
/// Historical acquisition intended to fill missing durable data.
Backfill,
/// Explicit import from a non-live source controlled by the caller.
Import,
/// Live acquisition from a currently active transport/session.
Live,
/// Explicit repair or reconciliation of previously known data.
Repair,
/// Explicit replay of an already-known source or archived acquisition.
Replay,
}
/// Safe source-independent acquisition provenance attached to one persisted observation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RawAcquisitionProvenance {
acquisition_method: crate::RawProvenanceCode,
capture_session_id: std::option::Option<crate::RawProvenanceCode>,
commitment: std::option::Option<crate::RawProvenanceCode>,
endpoint_id: std::option::Option<crate::RawProvenanceCode>,
filter_id: std::option::Option<crate::RawProvenanceCode>,
observed_at: std::option::Option<crate::RawTimestamp>,
origin: crate::RawAcquisitionOrigin,
protocol: crate::RawProvenanceCode,
provider: crate::RawProvenanceCode,
received_at: crate::RawTimestamp,
source_payload_hash: std::option::Option<crate::RawContentHash>,
source_payload_size_bytes: std::option::Option<u64>,
}
impl RawAcquisitionProvenance {
/// Creates one successful acquisition provenance record with only mandatory safe metadata.
#[must_use]
pub fn new(
provider: crate::RawProvenanceCode,
protocol: crate::RawProvenanceCode,
acquisition_method: crate::RawProvenanceCode,
origin: crate::RawAcquisitionOrigin,
received_at: crate::RawTimestamp,
) -> Self {
return Self {
acquisition_method,
capture_session_id: std::option::Option::None,
commitment: std::option::Option::None,
endpoint_id: std::option::Option::None,
filter_id: std::option::Option::None,
observed_at: std::option::Option::None,
origin,
protocol,
provider,
received_at,
source_payload_hash: std::option::Option::None,
source_payload_size_bytes: std::option::Option::None,
};
}
/// Attaches one safe logical capture/session identifier.
#[must_use]
pub fn with_capture_session_id(mut self, value: crate::RawProvenanceCode) -> Self {
self.capture_session_id = std::option::Option::Some(value);
return self;
}
/// Attaches one safe commitment code captured at acquisition.
#[must_use]
pub fn with_commitment(mut self, value: crate::RawProvenanceCode) -> Self {
self.commitment = std::option::Option::Some(value);
return self;
}
/// Attaches one Config-owned logical endpoint identifier.
#[must_use]
pub fn with_endpoint_id(mut self, value: crate::RawProvenanceCode) -> Self {
self.endpoint_id = std::option::Option::Some(value);
return self;
}
/// Attaches one safe logical filter identifier.
#[must_use]
pub fn with_filter_id(mut self, value: crate::RawProvenanceCode) -> Self {
self.filter_id = std::option::Option::Some(value);
return self;
}
/// Attaches the source observation timestamp when it does not follow local receipt.
pub fn try_with_observed_at(mut self, value: crate::RawTimestamp) -> ksp_core_lib::Result<Self> {
if value > self.received_at {
return std::result::Result::Err(raw_provenance_error("observed_at"));
}
self.observed_at = std::option::Option::Some(value);
return std::result::Result::Ok(self);
}
/// Attaches the digest of source-specific bytes without retaining those bytes.
#[must_use]
pub fn with_source_payload_hash(mut self, value: crate::RawContentHash) -> Self {
self.source_payload_hash = std::option::Option::Some(value);
return self;
}
/// Attaches the bounded source-wire payload size.
pub fn try_with_source_payload_size_bytes(mut self, value: u64) -> ksp_core_lib::Result<Self> {
if value > crate::MAX_RAW_SOURCE_PAYLOAD_BYTES {
return std::result::Result::Err(raw_provenance_error("source_payload_size_bytes"));
}
self.source_payload_size_bytes = std::option::Option::Some(value);
return std::result::Result::Ok(self);
}
/// Returns the logical acquisition method code.
#[must_use]
pub const fn acquisition_method(&self) -> &crate::RawProvenanceCode {
return &self.acquisition_method;
}
/// Returns the optional logical capture/session identifier.
#[must_use]
pub fn capture_session_id(&self) -> std::option::Option<&crate::RawProvenanceCode> {
return self.capture_session_id.as_ref();
}
/// Returns the optional commitment code captured at acquisition.
#[must_use]
pub fn commitment(&self) -> std::option::Option<&crate::RawProvenanceCode> {
return self.commitment.as_ref();
}
/// Returns the optional Config-owned logical endpoint identifier.
#[must_use]
pub fn endpoint_id(&self) -> std::option::Option<&crate::RawProvenanceCode> {
return self.endpoint_id.as_ref();
}
/// Returns the optional logical filter identifier.
#[must_use]
pub fn filter_id(&self) -> std::option::Option<&crate::RawProvenanceCode> {
return self.filter_id.as_ref();
}
/// Returns the optional source observation timestamp when the source supplies one.
#[must_use]
pub const fn observed_at(&self) -> std::option::Option<crate::RawTimestamp> {
return self.observed_at;
}
/// Returns the acquisition origin category.
#[must_use]
pub const fn origin(&self) -> crate::RawAcquisitionOrigin {
return self.origin;
}
/// Returns the logical transport/protocol code.
#[must_use]
pub fn protocol(&self) -> &crate::RawProvenanceCode {
return &self.protocol;
}
/// Returns the safe provider code.
#[must_use]
pub fn provider(&self) -> &crate::RawProvenanceCode {
return &self.provider;
}
/// Returns the local receipt timestamp.
#[must_use]
pub const fn received_at(&self) -> crate::RawTimestamp {
return self.received_at;
}
/// Returns the optional digest of source-specific bytes without retaining those bytes.
#[must_use]
pub const fn source_payload_hash(&self) -> std::option::Option<crate::RawContentHash> {
return self.source_payload_hash;
}
/// Returns the optional source-wire payload size.
#[must_use]
pub const fn source_payload_size_bytes(&self) -> std::option::Option<u64> {
return self.source_payload_size_bytes;
}
}
/// Bounded source-independent KSP RAW persistence payload.
pub struct RawPayload {
bytes: std::boxed::Box<[u8]>,
content_hash: crate::RawContentHash,
format_id: crate::RawFormatId,
format_version: u32,
}
impl RawPayload {
/// Creates one canonical RAW payload after enforcing Store-owned admission invariants.
///
/// The supplied bytes must already use the KSP-owned source-independent format identified
/// by `format_id` and `format_version`; this constructor performs no transport conversion.
pub fn try_new(
format_id: crate::RawFormatId,
format_version: u32,
bytes: std::boxed::Box<[u8]>,
content_hash: crate::RawContentHash,
) -> ksp_core_lib::Result<Self> {
if format_version == 0 {
return std::result::Result::Err(raw_payload_error("format_version"));
}
if bytes.is_empty() || bytes.len() > crate::MAX_RAW_PAYLOAD_BYTES {
return std::result::Result::Err(
raw_payload_error("bytes")
.with_context("actual_len", bytes.len().to_string())
.with_context("maximum_len", crate::MAX_RAW_PAYLOAD_BYTES.to_string()),
);
}
return std::result::Result::Ok(Self { bytes, content_hash, format_id, format_version });
}
/// Returns the canonical RAW bytes without transport/provider interpretation.
#[must_use]
pub fn bytes(&self) -> &[u8] {
return self.bytes.as_ref();
}
/// Returns the deterministic content digest supplied for these exact canonical bytes.
#[must_use]
pub const fn content_hash(&self) -> crate::RawContentHash {
return self.content_hash;
}
/// Returns the KSP-owned source-independent format identifier.
#[must_use]
pub fn format_id(&self) -> &crate::RawFormatId {
return &self.format_id;
}
/// Returns the KSP-owned format version.
#[must_use]
pub const fn format_version(&self) -> u32 {
return self.format_version;
}
/// Returns the canonical payload length in bytes.
#[must_use]
pub fn byte_len(&self) -> usize {
return self.bytes.len();
}
}
impl std::fmt::Debug for RawPayload {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("RawPayload")
.field("format_id", &self.format_id)
.field("format_version", &self.format_version)
.field("len", &self.bytes.len())
.field("content_hash", &self.content_hash)
.finish();
}
}
fn raw_model_error(field: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_MODEL_INVALID, "invalid backend-agnostic RAW Store model").with_context("field", field);
}
fn raw_payload_error(field: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_PAYLOAD_INVALID, "invalid KSP RAW persistence payload").with_context("field", field);
}
fn raw_provenance_error(field: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_PROVENANCE_INVALID, "invalid RAW acquisition provenance").with_context("field", field);
}
fn valid_raw_code(value: &str) -> bool {
if value.is_empty() || value.len() > crate::MAX_RAW_CODE_BYTES {
return false;
}
return value.bytes().all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.' | b':'));
}
#[cfg(test)]
#[path = "../../unit_tests/model/raw_primitives.rs"]
mod tests;

View File

@@ -0,0 +1,113 @@
// file: crates/ksp-store-api/src/model/raw_transaction.rs
// version: 1
/// Durable backend-independent identity of one canonical RAW transaction.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct RawTransactionReference {
network: crate::RawNetworkId,
signature: crate::RawTransactionSignature,
}
impl RawTransactionReference {
/// Creates one durable transaction identity from network and canonical Solana signature.
#[must_use]
pub fn new(network: crate::RawNetworkId, signature: crate::RawTransactionSignature) -> Self {
return Self { network, signature };
}
/// Returns the logical Solana network/cluster identifier.
#[must_use]
pub fn network(&self) -> &crate::RawNetworkId {
return &self.network;
}
/// Returns the canonical transaction signature.
#[must_use]
pub const fn signature(&self) -> crate::RawTransactionSignature {
return self.signature;
}
}
/// Canonical source-independent N1 RAW transaction persisted by Store backends.
///
/// The payload must contain the complete KSP canonical transaction representation required
/// for future STRUCTURAL replay, including transaction execution metadata and transaction
/// log messages when the canonical format defines them. Provider/transport provenance is
/// deliberately kept in [`crate::RawTransactionObservation`] instead.
#[derive(Debug)]
pub struct RawTransaction {
block_time: std::option::Option<crate::RawTimestamp>,
payload: crate::RawPayload,
reference: crate::RawTransactionReference,
slot: u64,
}
impl RawTransaction {
/// Creates one complete canonical RAW transaction.
#[must_use]
pub fn new(reference: crate::RawTransactionReference, slot: u64, block_time: std::option::Option<crate::RawTimestamp>, payload: crate::RawPayload) -> Self {
return Self { block_time, payload, reference, slot };
}
/// Returns the optional canonical block timestamp.
#[must_use]
pub const fn block_time(&self) -> std::option::Option<crate::RawTimestamp> {
return self.block_time;
}
/// Returns the complete KSP-owned canonical RAW payload.
#[must_use]
pub fn payload(&self) -> &crate::RawPayload {
return &self.payload;
}
/// Returns the durable backend-independent transaction identity.
#[must_use]
pub fn reference(&self) -> &crate::RawTransactionReference {
return &self.reference;
}
/// Returns the Solana slot containing the transaction.
#[must_use]
pub const fn slot(&self) -> u64 {
return self.slot;
}
}
/// Persistable acquisition observation linked to one canonical RAW transaction.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RawTransactionObservation {
observation_key: crate::RawObservationKey,
provenance: crate::RawAcquisitionProvenance,
transaction: crate::RawTransactionReference,
}
impl RawTransactionObservation {
/// Creates one successful observation of a complete canonical RAW transaction.
#[must_use]
pub fn new(observation_key: crate::RawObservationKey, transaction: crate::RawTransactionReference, provenance: crate::RawAcquisitionProvenance) -> Self {
return Self { observation_key, provenance, transaction };
}
/// Returns the deterministic producer-owned observation idempotence key.
#[must_use]
pub const fn observation_key(&self) -> crate::RawObservationKey {
return self.observation_key;
}
/// Returns safe source-independent acquisition provenance.
#[must_use]
pub fn provenance(&self) -> &crate::RawAcquisitionProvenance {
return &self.provenance;
}
/// Returns the durable transaction identity observed by this acquisition.
#[must_use]
pub fn transaction(&self) -> &crate::RawTransactionReference {
return &self.transaction;
}
}
#[cfg(test)]
#[path = "../../unit_tests/model/raw_transaction.rs"]
mod tests;