v0.3.1-pre.005

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

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 326
# version: 327
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-wallet-lib"]
[workspace.package]
version = "0.3.1-pre.4"
version = "0.3.1-pre.5"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

165
deltas/0.3.1/pre.005.md Normal file
View File

@@ -0,0 +1,165 @@
<!-- file: deltas/0.3.1/pre.005.md -->
<!-- version: 1 -->
# Delta `0.3.1-pre.005` — capabilities backend extensibles
## Base requise
```text
0.3.1-pre.4
```
Le gate opérateur de `pre.004` est vert : audits Rust/Markdown, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test -p ksp-store-api` passent.
## Objectif
Matérialiser les premiers contracts d'opérations backend-agnostic de `ksp-store-api` sans introduire la façade runtime `ksp-store-lib`, un backend concret ou une dépendance async supplémentaire.
La tranche doit démontrer qu'un backend externe peut implémenter séparément les capabilities réellement supportées et être utilisé derrière des trait objects.
## Fichiers ajoutés
```text
crates/ksp-store-api/src/capability/raw_account.rs
crates/ksp-store-api/src/capability/raw_transaction.rs
crates/ksp-store-api/tests/external_backend.rs
deltas/0.3.1/pre.005.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-store-api/src/capability.rs
crates/ksp-store-api/src/lib.rs
crates/ksp-store-api/tests/dependency_boundary.rs
crates/ksp-store-api/tests/public_api.rs
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
docs/validation/018-V0_3_1_STORE_RAW.md
```
## Fichiers supprimés
```text
aucun
```
## Version
```text
workspace.package.version = 0.3.1-pre.5
```
## Surface ajoutée
```text
StoreApiFuture<'a, T>
RawTransactionRead
RawTransactionWrite
RawTransactionObservationRead
RawTransactionObservationWrite
RawAccountStateRead
RawAccountStateWrite
RawAccountObservationRead
RawAccountObservationWrite
```
Aucun trait global `StoreBackend` n'est ajouté. Les backends peuvent implémenter une combinaison de capabilities sans prétendre supporter toutes les familles N1.
## Contrat async/object-safe
`StoreApiFuture<'a, T>` est un alias KSP utilisant uniquement la bibliothèque standard :
```text
Pin<Box<dyn Future<Output = T> + Send + 'a>>
```
Les capabilities sont `Send + Sync`, dyn-compatible et n'ajoutent aucune dépendance `async-trait`, `tokio` ou `futures-util` à `ksp-store-api`.
## Contrats d'écriture
Les opérations suivantes sont atomiques au niveau métier :
```text
persist_raw_transaction_acquisition(transaction, observation)
persist_raw_account_acquisition(state, observation)
```
Le backend doit persister le RAW et son observation ensemble ou ne laisser aucun des deux durable.
Les opérations :
```text
record_raw_transaction_observation(observation)
record_raw_account_observation(observation)
```
servent aux acquisitions supplémentaires d'un RAW déjà présent et évitent de retransmettre le payload transactionnel ou les bytes de compte.
`pre.005` retourne uniquement `Result<()>` sur les écritures. `pre.006` remplace/finalise cette surface avec les outcomes d'idempotence/conflit, les queries et le lifecycle RAW avant stabilisation de la release.
## Canari backend externe
`tests/external_backend.rs` définit un backend mémoire externe à l'implémentation Store officielle et implémente les huit capabilities uniquement via la façade crate-root de `ksp-store-api`.
Le canari vérifie notamment :
```text
impl externe sans ksp-store-lib
impl externe sans ksp-store-postgres-lib
aucun SQL/PostgreSQL
aucun async runtime requis pour implémenter les signatures
conversion en &dyn capability possible
```
## Validations exécutées dans l'environnement de génération
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.1
Markdown table audit: clean (186 table(s), 129 file(s))
```
## Validations non exécutées dans l'environnement de génération
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement de génération. L'opérateur doit donc exécuter :
```text
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-store-api
```
Une commande non exécutée n'est pas déclarée PASS.
## Hors scope confirmé
```text
façade runtime ksp-store-lib
trait StoreBackend monolithique
ksp-store-postgres-lib
PostgreSQL/tokio-postgres
Config/std.store
queries/list/pagination
outcomes d'idempotence/conflit finaux
retention/tombstone
health runtime
TransactionStatusObservation commun
models event-only logs/slot/vote
RawBlock persistence
Yellowstone Entry persistence
N2 STRUCTURAL
N3 DECODED
N4 DOMAIN
```
## Suite
`0.3.1-pre.006` finalise les queries/pages bornées, outcomes d'écriture/idempotence, contrat de backlog utile, lifecycle `RawRetentionState`, tombstone minimal et sémantiques normal-skip/force-rehydrate sans implémenter de compression/archive physique.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/022-V0_3_1_STORE_RAW_PLAN.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Plan `0.3.1` — Store API RAW foundation
@@ -803,7 +803,7 @@ La stratégie candidate est :
```text
capabilities Send + Sync
méthodes object-safe
futures boxed KSP-owned via un alias StoreFuture<'a, T>
futures boxed KSP-owned via un alias StoreApiFuture<'a, T>
future ksp-store-lib::Store compose les capabilities disponibles
```
@@ -813,21 +813,36 @@ Cette stratégie évite une dépendance `async-trait` uniquement pour masquer la
Éviter un trait monolithique exigeant tous les types de données à chaque backend.
La candidate est une composition fine :
`pre.005` matérialise une composition fine par famille et par direction :
```text
StoreHealth
RawTransactionRead
RawTransactionWrite
RawTransactionObservationRead
RawTransactionObservationWrite
future RawAccountStateRead
future RawAccountStateWrite
future TransactionStatus* si persistence réellement retenue
RawAccountStateRead
RawAccountStateWrite
RawAccountObservationRead
RawAccountObservationWrite
```
Un modèle event-only ne crée aucune capability Store par défaut.
Les capabilities d'écriture distinguent deux usages :
```text
persist_raw_*_acquisition(raw, observation)
= création/admission atomique du RAW + observation
record_raw_*_observation(observation)
= acquisition supplémentaire d'un RAW déjà persistant
= ne retransmet pas le payload volumineux
```
Les traits sont `Send + Sync`, dyn-compatible et retournent `StoreApiFuture<'a, T>`, alias KSP basé uniquement sur `Pin<Box<dyn Future + Send>>`. Un backend externe peut donc les implémenter sans `async-trait`, `tokio`, `ksp-store-lib` ou crate backend officielle.
`StoreHealth`, les listes/queries et les outcomes détaillés ne sont pas artificiellement introduits dans cette tranche. `pre.006` finalise les résultats d'écriture et le lifecycle logique avant fermeture de l'API.
Un modèle event-only ne crée aucune capability Store par défaut. Aucun trait monolithique `StoreBackend` n'est introduit : un backend peut implémenter uniquement les familles réellement supportées.
La future façade `ksp-store-lib::Store` peut exposer seulement les capabilities réellement compilées/supportées et produire une erreur stable lorsqu'une opération demandée n'est pas disponible.
@@ -835,22 +850,25 @@ La future façade `ksp-store-lib::Store` peut exposer seulement les capabilities
Aucun handle de transaction SQL/public n'est exposé.
Les invariants multi-écritures sont exprimés par des opérations logiques communes. Candidate initiale :
La surface matérialisée par `pre.005` commence par les opérations unitaires nécessaires :
```text
persist_transaction_acquisition(transaction, observation)
record_transaction_observation(observation)
get_raw_transaction(reference)
list_raw_transactions(query, page)
list_transaction_observations(query, page)
persist_raw_transaction_acquisition(transaction, observation)
get_raw_transaction_observation(observation_key)
record_raw_transaction_observation(observation)
future persist_account_acquisition(state, observation)
future account reads/queries
health()
get_raw_account_state(reference)
persist_raw_account_acquisition(state, observation)
get_raw_account_observation(observation_key)
record_raw_account_observation(observation)
```
`persist_transaction_acquisition` signifie au contrat que le RAW et son observation réussissent atomiquement ou échouent ensemble. PostgreSQL réalisera cela avec une transaction privée en `0.3.2`; un autre backend utilisera son mécanisme natif.
Les opérations `persist_raw_*_acquisition` signifient au contrat que le RAW et son observation réussissent atomiquement ou échouent ensemble. PostgreSQL réalisera cela avec une transaction privée en `0.3.2`; un autre backend utilisera son mécanisme natif.
Les opérations `record_raw_*_observation` supposent que la référence RAW ciblée existe déjà et permettent de retenir une acquisition supplémentaire sans retransmettre la donnée RAW complète.
Les listes, queries, backlog, health commun et outcomes détaillés restent à finaliser dans `pre.006`; aucune transaction backend publique n'est nécessaire pour les exprimer.
### 11.5 Outcomes
@@ -1375,7 +1393,7 @@ Audit HTTP/WS/gRPC matérialisé. `RawAccountState`/observation sont admis avec
### `pre.005` — Capabilities backend extensibles
Implémenter les contracts read/write object-safe et l'external backend canary, sans façade runtime `Store` et sans runtime DB. Un modèle prévu n'oblige pas chaque backend à implémenter sa capability.
Matérialiser les contracts read/write object-safe pour transaction/account et leurs observations, l'alias `StoreApiFuture`, ainsi qu'un canari backend externe. Les écritures d'acquisition imposent RAW + observation atomiques et les observations supplémentaires peuvent être enregistrées sans retransmettre le RAW. Aucune façade runtime `Store`, aucun trait monolithique backend et aucun runtime DB.
### `pre.006` — Queries, outcomes et lifecycle RAW

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/018-V0_3_1_STORE_RAW.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Validation `0.3.1` — Store API RAW foundation
@@ -181,12 +181,12 @@ crate/test backend externe
| status surfaces artificiellement fusionnées | aucun modèle commun prématuré | `pre.004` PASS |
| duplicate same content | `AlreadyPresent` | `pre.006` |
| duplicate divergent content | Conflict stable | `pre.006` |
| partial transaction+observation | interdit par atomic acquisition | `pre.005` |
| partial transaction+observation | interdit par atomic acquisition | `pre.005` PASS |
| event-only -> Store capability | absent par défaut | `pre.004/007` |
| Interface/Store duplicate model | absent | `pre.007` |
| page limit 0/>500 | rejet | `pre.006` |
| SQL/backend cursor leak | absent | `pre.006/007` |
| external backend | implémente API sans Store lib | `pre.005` |
| external backend | implémente API sans Store lib | `pre.005` PASS |
| processing `bool` comme vérité | absent ; future preuve version-aware documentée | `pre.006/007` |
| purge sans policy/evidence | impossible par contrat | `pre.006/007` |
| tombstone supprimé avec payload | interdit | `pre.006` |
@@ -268,6 +268,45 @@ jsonParsed/dataSlice/bare program -> non
La conversion source -> modèle reste hors `ksp-store-api`; la crate ne dépend toujours que de `ksp-core-lib`.
### 8.3 Matérialisation `pre.005`
La tranche ajoute uniquement des contracts de capability backend-agnostic :
```text
StoreApiFuture<'a, T>
RawTransactionRead
RawTransactionWrite
RawTransactionObservationRead
RawTransactionObservationWrite
RawAccountStateRead
RawAccountStateWrite
RawAccountObservationRead
RawAccountObservationWrite
```
Gates matérialisés :
```text
traits Send + Sync et dyn-compatible
aucune dépendance async-trait/tokio/futures-util ajoutée
backend externe implémentable avec std + ksp-store-api seulement
aucun trait StoreBackend monolithique
aucune façade runtime Store
aucun type Config/backend/SQL public
persist_raw_transaction_acquisition = transaction + observation atomiques
persist_raw_account_acquisition = account state + observation atomiques
record_*_observation = acquisition supplémentaire sans retransmettre le RAW
get_* = référence/observation key backend-independent
write success/failure seulement en pre.005
outcomes/idempotence/conflict détaillés réservés à pre.006
```
Le canari `tests/external_backend.rs` définit un backend mémoire externe qui implémente les huit traits sans dépendre de `ksp-store-lib`, PostgreSQL ou d'un runtime async. Il prouve également que chaque capability est utilisable derrière `dyn Trait`.
Aucune implémentation de persistence n'est fournie par `ksp-store-api`; les futures concrètes du canari ne servent qu'à vérifier le contrat d'extension.
## 9. Gates de fermeture prévus
### Gate technique final `pre.008`
@@ -359,7 +398,7 @@ sécurité
| `pre.002` | scaffold + taxonomie Store API | À FAIRE |
| `pre.003` | primitives + RawTransaction | À FAIRE |
| `pre.004` | admission matrix + account/status models | PRÊT après gate local |
| `pre.005` | backend contracts/capabilities | À FAIRE |
| `pre.005` | backend contracts/capabilities | PRÊT après gate local |
| `pre.006` | queries/outcomes/retention/tombstone | À FAIRE |
| `pre.007` | boundary/adversarial/completeness | À FAIRE |
| `pre.008` | gate technique final | À FAIRE |