Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cc94ebb46 | |||
| 8e8f1f0b4a | |||
| 78e015413b | |||
| 2f0eb316f5 | |||
| 55325ffd32 | |||
| ad02b6f13c | |||
| 47b12e14e6 | |||
| c43d4d7c38 | |||
| b76baf6835 | |||
| c83e3261d0 | |||
| 28ca5bdac5 | |||
| afd4c770f9 | |||
| ecc5607fd4 | |||
| 11e4cde77f | |||
| 62ed72f2b5 | |||
| 1a07574bae | |||
| 29a888d1dc |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,8 +1,16 @@
|
|||||||
<!-- file: CHANGELOG.md -->
|
<!-- file: CHANGELOG.md -->
|
||||||
<!-- version: 18 -->
|
<!-- version: 19 -->
|
||||||
|
|
||||||
# Changelog KSP
|
# Changelog KSP
|
||||||
|
|
||||||
|
## 0.3.1 — Store API RAW foundation — 2026-08-29
|
||||||
|
|
||||||
|
`0.3.1` introduit `ksp-store-api` comme contrat backend-agnostic de persistence N1 RAW, sans runtime Store ni backend physique. La release stabilise deux familles réellement convergentes : `RawTransaction` avec payload canonique opaque/versionné, identité réseau+signature et observations d’acquisition séparées, puis `RawAccountState` avec bytes complets, identité réseau+pubkey+slot+hash et observations pouvant conserver les enrichissements Yellowstone sans les confondre avec l’état canonique. `TransactionStatusObservation` reste reporté faute de convergence sémantique suffisante entre snapshot HTTP, transition WebSocket et update Yellowstone ; `logsSubscribe`, slot/root/slotsUpdates et vote restent event-only candidats, `RawBlock` reste une idée conditionnelle et Yellowstone `Entry` reste rejeté de la taxonomie active.
|
||||||
|
|
||||||
|
La façade publique conserve un modèle objet sans SQL ni rows backend, des primitives de provenance/hash/timestamps bornées, des queries cursorisées sans plafond métier KSP arbitraire, des outcomes d’idempotence/conflit, dix capabilities fines object-safe et un lifecycle logique de rétention transactionnelle `Full -> Compacted -> Archived -> Purged`. Le tombstone minimal empêche le rebackfill normal après purge tandis que `ForceRehydrate` reste une intention distincte ; une course de compare-and-transition est représentée par `ExpectedStateMismatch` plutôt que par un overwrite silencieux. Le backlog, le batch-size, la priorité, la policy de processing, la compression/archive physique, les notifications runtime et les couches STRUCTURAL/DECODED/DOMAIN restent hors Store API. Le graphe normal final de `ksp-store-api` reste strictement limité à `ksp-core-lib`; aucun PostgreSQL, Tokio, serde, Config, Transport, Program, Logging ou backend concret n’entre dans la crate.
|
||||||
|
|
||||||
|
Les canaris de clôture verrouillent 60 exports crate-root, 10 capabilities, l’inventaire exact des modules RAW, l’implémentabilité par un backend externe, les bornes adversariales, la redaction des `Debug`, la frontière Interface/Store et l’absence de surface N2/N3/N4. Le gate technique de référence a été exécuté après `cargo clean` et passe audits Rust/Markdown, `cargo check --workspace`, Clippy, tests ciblés des crates, `cargo test --workspace`, builds Tauri des trois Desk et graphes Cargo ; les gates documentaires suivants restent également verts. Le redécoupage final prépare trois releases Store/PostgreSQL où `ksp-store-lib` et `ksp-store-postgres-lib` avancent toujours ensemble : `0.3.2` pour la fondation runtime/backend, `0.3.3` pour la vertical slice `RawTransaction`, puis `0.3.4` pour `RawAccountState` et la complétude RAW. `prompts/021-V0_3_2_START_PROMPT.md` ouvre donc uniquement la fondation conjointe Store/PostgreSQL, avec `tokio-postgres` comme driver retenu mais pooling, TLS, migrations et Config à réauditer avant implémentation lourde.
|
||||||
|
|
||||||
## 0.2.14 — Program API foundation — 2026-08-28
|
## 0.2.14 — Program API foundation — 2026-08-28
|
||||||
|
|
||||||
`0.2.14` introduit `ksp-program-api` comme première API publique extensible du domaine Program, volontairement limitée au décodage d’instructions et indépendante des runtimes supérieurs. La façade réexporte les contrats Core/Interface nécessaires puis possède `ProgramInstructionRecognition` (`NoMatch`, `ProgramMatch`, `ExactMatch`), `ProgramInstructionDecodeOutcome<Decoded>` (`Decoded`, `Unsupported`) et le trait `ProgramInstructionDecoder: Send + Sync`. L’output `Decoded` reste possédé par l’implémentation et ne reçoit aucun bound implicite `Debug`, `Clone`, `Send` ou `Sync`; les erreurs réelles restent dans le `Result` Core. Les Program IDs sont des `Pubkey` opaques : une implémentation externe peut prendre en charge un programme absent du registry Core sans enum centrale fermée, `Any`, JSON, descriptor global ni registry runtime.
|
`0.2.14` introduit `ksp-program-api` comme première API publique extensible du domaine Program, volontairement limitée au décodage d’instructions et indépendante des runtimes supérieurs. La façade réexporte les contrats Core/Interface nécessaires puis possède `ProgramInstructionRecognition` (`NoMatch`, `ProgramMatch`, `ExactMatch`), `ProgramInstructionDecodeOutcome<Decoded>` (`Decoded`, `Unsupported`) et le trait `ProgramInstructionDecoder: Send + Sync`. L’output `Decoded` reste possédé par l’implémentation et ne reçoit aucun bound implicite `Debug`, `Clone`, `Send` ou `Sync`; les erreurs réelles restent dans le `Result` Core. Les Program IDs sont des `Pubkey` opaques : une implémentation externe peut prendre en charge un programme absent du registry Core sans enum centrale fermée, `Any`, JSON, descriptor global ni registry runtime.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 322
|
# version: 331
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
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-wallet-lib"]
|
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]
|
[workspace.package]
|
||||||
version = "0.2.14"
|
version = "0.3.1"
|
||||||
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"
|
||||||
|
|||||||
59
ROADMAP.md
59
ROADMAP.md
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: ROADMAP.md -->
|
<!-- file: ROADMAP.md -->
|
||||||
<!-- version: 92 -->
|
<!-- version: 94 -->
|
||||||
|
|
||||||
# Roadmap KSP
|
# Roadmap KSP
|
||||||
|
|
||||||
@@ -80,33 +80,52 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
|
|||||||
|
|
||||||
## Architecture de données — progression canonique
|
## Architecture de données — progression canonique
|
||||||
|
|
||||||
La chaîne durable cible est :
|
La progression durable cible est :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
RAW -> CORE -> DECODE -> SPECIALIZED
|
RAW -> STRUCTURAL -> DECODED -> DOMAIN
|
||||||
```
|
```
|
||||||
|
|
||||||
- **RAW** et **CORE** ne nécessitent aucun décodage Program.
|
- **RAW** et **STRUCTURAL** ne nécessitent aucun décodage Program.
|
||||||
- À la fin de chaque couche horizontale RAW/CORE, ajouter les jobs/workers/apps nécessaires pour la rendre réellement exploitable avant d'ouvrir la couche suivante.
|
- La progression n'est pas une chaîne obligatoire pour chaque famille : une donnée event-only peut s'arrêter en N1, et un état de compte pourra aller directement vers un futur decoder si aucune décomposition STRUCTURAL utile n'existe.
|
||||||
- À partir de **DECODE**, avancer verticalement groupe par groupe : wire -> decode -> matérialisation -> projection spécialisée si utile -> préparation d'exécution -> policy -> execution -> scénarios Devnet.
|
- À la fin des couches horizontales RAW/STRUCTURAL réellement persistées, ajouter les jobs/workers/apps nécessaires avant d'ouvrir la couche suivante.
|
||||||
|
- À partir de **DECODED**, avancer verticalement groupe par groupe ; **DOMAIN** désigne les projections métier et la matérialisation est le processus qui les produit.
|
||||||
|
|
||||||
## 0.3.x — RAW / acquisition persistée
|
## 0.3.x — RAW / acquisition persistée
|
||||||
|
|
||||||
- [ ] `0.3.1` — Introduire `ksp-store-api` + `ksp-store-lib` avec PostgreSQL de référence et **modèles/persistence RAW uniquement**.
|
- [X] `0.3.1` — `ksp-store-api` stable : modèles N1 RAW backend-agnostic `RawTransaction` et `RawAccountState` avec observations, provenance, payload/hash/timestamps bornés, 10 capabilities object-safe, queries cursorisées sans plafond métier arbitraire, outcomes idempotence/conflit et lifecycle logique rétention/tombstone/force-rehydrate ; aucun backend physique, Config, runtime Store, notification dédiée ni surface STRUCTURAL/DECODED/DOMAIN.
|
||||||
- [ ] `0.3.2` — Étendre `ksp-interface-lib` avec les wires génériques nécessaires aux acquisitions et à la future normalisation CORE.
|
- [ ] `0.3.2` — Introduire ensemble `ksp-store-lib` et `ksp-store-postgres-lib` pour la **fondation runtime/backend PostgreSQL uniquement** : façade Store, feature `postgres` par défaut, dispatch des backends compilés, Config `std.store`/secrets, connexion/pool/TLS à réauditer, bootstrap/migrations privés et health/readiness seulement si un contrat portable est réellement justifié. Aucun schéma `RawTransaction`/`RawAccountState` n’est ajouté dans cette slice.
|
||||||
- [ ] `0.3.3` — Introduire `ksp-job-api` et un job de backfill historique concret.
|
- [ ] `0.3.3` — Étendre le même couple `ksp-store-lib` + `ksp-store-postgres-lib` avec la vertical slice PostgreSQL `RawTransaction` complète : persistence/observation atomiques, get/list cursorisé, idempotence/conflit, rétention/tombstone/force-rehydrate, concurrence et rollback validés sur PostgreSQL réel.
|
||||||
- [ ] `0.3.4` — Introduire une application spécialisée de backfill/inspection RAW.
|
- [ ] `0.3.4` — Étendre le même couple avec `RawAccountState` + observation, puis fermer la complétude/conformance RAW cross-family, les indexes/migrations physiques nécessaires et le hardening PostgreSQL final.
|
||||||
- [ ] Compléter ensuite la couche RAW avec le worker/service live, son contrôle et les outils d'exploitation réellement nécessaires avant de passer à CORE.
|
- [ ] `0.3.5` — Étendre `ksp-interface-lib` uniquement avec les modèles passifs/events réellement partagés par les premiers consumers d’acquisition, sans dupliquer les modèles persistants de `ksp-store-api`.
|
||||||
|
- [ ] `0.3.6` — Introduire `ksp-job-api` et un premier job de backfill historique concret consommant `ksp-store-lib`, avec policy/batch-size/progression possédés par le job et non par Store.
|
||||||
|
- [ ] `0.3.7` — Introduire une application spécialisée de backfill/inspection RAW.
|
||||||
|
- [ ] Compléter ensuite la couche RAW avec le worker/service live, son contrôle et les outils d’exploitation réellement nécessaires avant de passer à la couche de normalisation générique suivante.
|
||||||
|
|
||||||
## Série CORE suivante
|
### TODO/IDEAS — taxonomie N1, processing et rétention
|
||||||
|
|
||||||
- [ ] Définir la persistence CORE canonique Solana générique.
|
- [ ] **TODO** — maintenir la matrice d’admission HTTP/WS/gRPC/provider lors de toute nouvelle famille N1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte.
|
||||||
- [ ] Implémenter `RAW -> CORE` sans decoder Program : blocs, slots, signatures, transactions/messages, comptes, instructions/CPI brutes, logs/meta et relations structurelles.
|
- [X] `RawAccountState` + observation — contrat commun stabilisé en `0.3.1` avec bytes complets + slot, provenance séparée et enrichissements source-specific optionnels ; la persistence PostgreSQL physique reste réservée à `0.3.4`.
|
||||||
- [ ] Ajouter replay/backfill RAW -> CORE.
|
- [ ] **TODO** — `TransactionStatusObservation` : réauditer `signatureSubscribe`, `getSignatureStatuses`, Yellowstone TransactionStatus et extensions provider lorsqu’un consumer réel apparaît ; ne pas fusionner snapshot, transition et update dans un modèle Option-soup.
|
||||||
- [ ] Ajouter worker/service CORE.
|
- [ ] **TODO** — logs realtime : conserver `logMessages` dans `RawTransaction` jusqu’à la décomposition STRUCTURAL ; traiter `logsSubscribe` comme event-only candidat et décider son contrat passif dans `ksp-interface-lib`, sans table Store par défaut. Le format canonique d’un wake-up « donnée persistée disponible » reste distinct et appartient à `ksp-store-api` conformément à `KSP-NOTIFY-*`, mais ne sera matérialisé qu’avec un publisher/consumer réel.
|
||||||
- [ ] Ajouter l'application de contrôle/inspection CORE utile.
|
- [ ] **TODO** — slot/root/slotsUpdates et vote : ne créer un modèle passif commun que si un consumer realtime réel et une sémantique cross-ledger/provider justifient le contrat ; aucune persistence Store par défaut.
|
||||||
|
- [ ] **IDEA** — `RawBlock` : ne rouvrir que si une information block-level non reconstructible devient nécessaire ; `getBlock` doit d’abord être traité comme source de `RawTransaction`, pas comme invitation à recopier le ledger en blocs.
|
||||||
|
- [ ] **REJET ACTUEL** — Yellowstone `Entry` : trop bas niveau et aucune destination replay/decomposition/event métier justifiant un modèle KSP n’est identifiée.
|
||||||
|
- [ ] **TODO** — processing ledger : reprendre l’idée kbot2/kbot3 `stage + processor identity/version + input identity/hash + terminal status`, sans faire d’un `processed: bool` la preuve durable unique ; prévoir force replay/version upgrades lorsque les processors seront ouverts.
|
||||||
|
- [X] lifecycle RAW logique — `RawRetentionState`, tombstone minimal, normal-skip et force-rehydrate sont stabilisés en `0.3.1` pour `RawTransaction`.
|
||||||
|
- [ ] **TODO** — rétention physique : définir plus tard compression/archive backend, critères d’éligibilité fondés sur les preuves de processing et maintenance worker/job ; Store applique une transition demandée mais ne décide pas seul qu’un RAW peut être purgé.
|
||||||
|
- [X] frontière `ksp-interface-lib` / `ksp-store-api` — ownership documenté et canaris de non-duplication stabilisés en `0.3.1`; les events passifs non persistés restent Interface, les modèles persistants/replayables restent Store API.
|
||||||
|
- [ ] **IDEA** — réauditer la structure de processing/decode/materialization historique kbot2/kbot3 lors de l’ouverture de N2/N3 ; conserver l’isolation instruction/CPI et les statuts terminal/versionnés, sans reprendre automatiquement le schéma SQL historique.
|
||||||
|
|
||||||
## Séries DECODE/SPECIALIZED/EXECUTION — progression verticale
|
## Série STRUCTURAL suivante
|
||||||
|
|
||||||
|
- [ ] Définir la persistence STRUCTURAL canonique Solana générique pour les familles réellement décomposables.
|
||||||
|
- [ ] Implémenter en priorité `RawTransaction -> STRUCTURAL` sans decoder Program : transaction/message, comptes/références, instructions top-level, CPI/inner instructions, logs/meta/balances/return data et relations structurelles.
|
||||||
|
- [ ] Vérifier avant extension si d'autres familles N1 possèdent une vraie décomposition STRUCTURAL utile ; ne pas créer de niveau vide par convention.
|
||||||
|
- [ ] Ajouter replay/backfill RAW -> STRUCTURAL avec processing versionné.
|
||||||
|
- [ ] Ajouter worker/service STRUCTURAL et l'application de contrôle/inspection utile.
|
||||||
|
|
||||||
|
## Séries DECODED/DOMAIN/EXECUTION — progression verticale
|
||||||
|
|
||||||
### Priorité 1 — Solana Core Programs
|
### Priorité 1 — Solana Core Programs
|
||||||
|
|
||||||
@@ -119,7 +138,7 @@ RAW -> CORE -> DECODE -> SPECIALIZED
|
|||||||
- [ ] SPL Token.
|
- [ ] SPL Token.
|
||||||
- [ ] Associated Token Account.
|
- [ ] Associated Token Account.
|
||||||
- [ ] Token-2022 et extensions pertinentes.
|
- [ ] Token-2022 et extensions pertinentes.
|
||||||
- [ ] Pour chaque famille : decode -> materialize -> specialized -> prepare -> policy -> execute -> scenarios.
|
- [ ] Pour chaque famille : decode -> materialize -> domain projection -> prepare -> policy -> execute -> scenarios.
|
||||||
|
|
||||||
### Priorité 3 — Metadata token
|
### Priorité 3 — Metadata token
|
||||||
|
|
||||||
@@ -143,7 +162,7 @@ RAW -> CORE -> DECODE -> SPECIALIZED
|
|||||||
|
|
||||||
- [ ] Après les premiers groupes Meteora/Raydium/Pump/Orca, introduire une petite `ksp-app-market-desk` spécialisée.
|
- [ ] Après les premiers groupes Meteora/Raydium/Pump/Orca, introduire une petite `ksp-app-market-desk` spécialisée.
|
||||||
- [ ] Visualiser tokens, pools/markets, liquidité, swaps/trades, prix, volumes, OHLC/candles et activité live/récente lorsque disponible.
|
- [ ] Visualiser tokens, pools/markets, liquidité, swaps/trades, prix, volumes, OHLC/candles et activité live/récente lorsque disponible.
|
||||||
- [ ] Lire les projections SPECIALIZED KSP ; ne pas reconstruire la logique protocolaire dans l'UI.
|
- [ ] Lire les projections DOMAIN KSP ; ne pas reconstruire la logique protocolaire dans l'UI.
|
||||||
|
|
||||||
### Routing
|
### Routing
|
||||||
|
|
||||||
|
|||||||
14
crates/ksp-store-api/Cargo.toml
Normal file
14
crates/ksp-store-api/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# file: crates/ksp-store-api/Cargo.toml
|
||||||
|
# version: 1
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "ksp-store-api"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
19
crates/ksp-store-api/src/capability.rs
Normal file
19
crates/ksp-store-api/src/capability.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// file: crates/ksp-store-api/src/capability.rs
|
||||||
|
// version: 3
|
||||||
|
|
||||||
|
//! Private home for backend-agnostic Store capability contracts.
|
||||||
|
//!
|
||||||
|
//! 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_retention;
|
||||||
|
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>>;
|
||||||
61
crates/ksp-store-api/src/capability/raw_account.rs
Normal file
61
crates/ksp-store-api/src/capability/raw_account.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// file: crates/ksp-store-api/src/capability/raw_account.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
/// 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>>>;
|
||||||
|
|
||||||
|
/// Lists deterministic account-state references using an opaque backend cursor.
|
||||||
|
///
|
||||||
|
/// The backend must not invent a KSP policy ceiling below the caller's
|
||||||
|
/// request. A real backend/physical limitation may determine the page that
|
||||||
|
/// can be served and continuation is represented by the opaque cursor.
|
||||||
|
fn list_raw_account_states<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a crate::RawAccountStateQuery,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawPage<crate::RawAccountStateReference>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write capability for complete RAW account-state acquisitions.
|
||||||
|
///
|
||||||
|
/// The account state and its acquisition observation form one logical
|
||||||
|
/// persistence operation. An implementation must not leave one side durable if
|
||||||
|
/// the other side fails. Divergent content for one logical identity is a stable
|
||||||
|
/// conflict error, never a silent overwrite.
|
||||||
|
pub trait RawAccountStateWrite: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Persists one complete RAW account state together with one observation atomically.
|
||||||
|
fn persist_raw_account_acquisition<'a>(
|
||||||
|
&'a self,
|
||||||
|
state: crate::RawAccountState,
|
||||||
|
observation: crate::RawAccountObservation,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawAcquisitionWriteOutcome>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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<crate::RawObservationWriteOutcome>>;
|
||||||
|
}
|
||||||
33
crates/ksp-store-api/src/capability/raw_retention.rs
Normal file
33
crates/ksp-store-api/src/capability/raw_retention.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// file: crates/ksp-store-api/src/capability/raw_retention.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
/// Read capability for canonical RAW transaction retention metadata.
|
||||||
|
pub trait RawTransactionRetentionRead: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Reads the logical retention state for one known transaction identity.
|
||||||
|
fn get_raw_transaction_retention_state<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a crate::RawTransactionReference,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawRetentionState>>>;
|
||||||
|
|
||||||
|
/// Reads the minimal durable tombstone when the transaction payload was purged.
|
||||||
|
fn get_raw_transaction_tombstone<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a crate::RawTransactionReference,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransactionTombstone>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write capability for policy-authorized RAW transaction retention transitions.
|
||||||
|
///
|
||||||
|
/// This contract applies a transition selected by an external worker/job policy;
|
||||||
|
/// the Store backend never decides by itself that higher processing is complete.
|
||||||
|
/// Implementations must compare the persisted state with `transition.expected()`
|
||||||
|
/// atomically with the update. A concurrent state change must return
|
||||||
|
/// [`crate::RawRetentionWriteOutcome::ExpectedStateMismatch`] unless the entity
|
||||||
|
/// is already at the requested target.
|
||||||
|
pub trait RawTransactionRetentionWrite: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Applies one atomic forward lifecycle transition chosen by the caller.
|
||||||
|
fn transition_raw_transaction_retention<'a>(
|
||||||
|
&'a self,
|
||||||
|
transition: crate::RawTransactionRetentionTransition,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawRetentionWriteOutcome>>;
|
||||||
|
}
|
||||||
63
crates/ksp-store-api/src/capability/raw_transaction.rs
Normal file
63
crates/ksp-store-api/src/capability/raw_transaction.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// file: crates/ksp-store-api/src/capability/raw_transaction.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
/// 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>>>;
|
||||||
|
|
||||||
|
/// Lists deterministic transaction references using an opaque backend cursor.
|
||||||
|
///
|
||||||
|
/// The backend must honor the caller-requested page limit unless a real
|
||||||
|
/// backend/physical limitation prevents it. `ksp-store-api` imposes no
|
||||||
|
/// arbitrary smaller result ceiling or executor policy.
|
||||||
|
fn list_raw_transactions<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a crate::RawTransactionQuery,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawPage<crate::RawTransactionReference>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write capability for canonical RAW transaction acquisitions.
|
||||||
|
///
|
||||||
|
/// The transaction and its acquisition observation form one logical persistence
|
||||||
|
/// operation. An implementation must not leave one side durable if the other
|
||||||
|
/// side fails. Divergent content for one logical identity is a stable conflict
|
||||||
|
/// error, never a silent overwrite.
|
||||||
|
pub trait RawTransactionWrite: std::marker::Send + std::marker::Sync {
|
||||||
|
/// Persists one complete RAW transaction together with one observation atomically.
|
||||||
|
fn persist_raw_transaction_acquisition<'a>(
|
||||||
|
&'a self,
|
||||||
|
transaction: crate::RawTransaction,
|
||||||
|
observation: crate::RawTransactionObservation,
|
||||||
|
mode: crate::RawTransactionAcquisitionMode,
|
||||||
|
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawAcquisitionWriteOutcome>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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<crate::RawObservationWriteOutcome>>;
|
||||||
|
}
|
||||||
15
crates/ksp-store-api/src/error.rs
Normal file
15
crates/ksp-store-api/src/error.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// file: crates/ksp-store-api/src/error.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
/// Error code used when a RAW write collides with divergent content for the same logical identity.
|
||||||
|
pub const ERROR_CODE_RAW_CONFLICT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_conflict");
|
||||||
|
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
|
||||||
|
pub const ERROR_CODE_RAW_MODEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_model_invalid");
|
||||||
|
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.
|
||||||
|
pub const ERROR_CODE_RAW_PAYLOAD_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_payload_invalid");
|
||||||
|
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
||||||
|
pub const ERROR_CODE_RAW_PROVENANCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_provenance_invalid");
|
||||||
|
/// Error code used when one RAW query or cursor violates backend-agnostic query invariants.
|
||||||
|
pub const ERROR_CODE_RAW_QUERY_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_query_invalid");
|
||||||
|
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
||||||
|
pub const ERROR_CODE_RAW_RETENTION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_retention_invalid");
|
||||||
142
crates/ksp-store-api/src/lib.rs
Normal file
142
crates/ksp-store-api/src/lib.rs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
// file: crates/ksp-store-api/src/lib.rs
|
||||||
|
// version: 5
|
||||||
|
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![deny(unreachable_pub)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
//! Backend-agnostic persistence contracts for KSP Store implementations.
|
||||||
|
//!
|
||||||
|
//! `ksp-store-api` owns persistent models plus the contracts that operate on
|
||||||
|
//! them. The `0.3.1` release is limited to N1 RAW/acquisition data. N2
|
||||||
|
//! STRUCTURAL is a later, distinct layer and no Program decode belongs to this
|
||||||
|
//! crate.
|
||||||
|
//!
|
||||||
|
//! Models and capabilities deliberately have separate private module homes.
|
||||||
|
//! Backend implementations, SQL, migrations, Config, Transport and runtime
|
||||||
|
//! dispatch remain outside this crate.
|
||||||
|
|
||||||
|
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 canonical RAW transaction retention metadata.
|
||||||
|
pub use self::capability::raw_retention::RawTransactionRetentionRead;
|
||||||
|
/// Write capability for policy-authorized RAW transaction retention transitions.
|
||||||
|
pub use self::capability::raw_retention::RawTransactionRetentionWrite;
|
||||||
|
/// Read capability for persisted RAW transaction observations.
|
||||||
|
pub use self::capability::raw_transaction::RawTransactionObservationRead;
|
||||||
|
/// Write capability for additional observations of already persisted RAW transactions.
|
||||||
|
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 write collides with divergent content for the same logical identity.
|
||||||
|
pub use self::error::ERROR_CODE_RAW_CONFLICT;
|
||||||
|
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
|
||||||
|
pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;
|
||||||
|
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.
|
||||||
|
pub use self::error::ERROR_CODE_RAW_PAYLOAD_INVALID;
|
||||||
|
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
||||||
|
pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;
|
||||||
|
/// Error code used when one RAW query or cursor violates backend-agnostic query invariants.
|
||||||
|
pub use self::error::ERROR_CODE_RAW_QUERY_INVALID;
|
||||||
|
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
||||||
|
pub use self::error::ERROR_CODE_RAW_RETENTION_INVALID;
|
||||||
|
/// Persistable acquisition observation linked to one complete canonical RAW account state.
|
||||||
|
pub use self::model::raw_account::RawAccountObservation;
|
||||||
|
/// Canonical complete N1 RAW account state independent from acquisition transport.
|
||||||
|
pub use self::model::raw_account::RawAccountState;
|
||||||
|
/// Durable backend-independent identity of one canonical RAW account state.
|
||||||
|
pub use self::model::raw_account::RawAccountStateReference;
|
||||||
|
/// Combined outcome of one atomic canonical RAW entity plus observation acquisition.
|
||||||
|
pub use self::model::raw_outcome::RawAcquisitionWriteOutcome;
|
||||||
|
/// Outcome for one canonical RAW entity in an idempotent persistence operation.
|
||||||
|
pub use self::model::raw_outcome::RawEntityWriteOutcome;
|
||||||
|
/// Outcome for one deterministic acquisition observation write.
|
||||||
|
pub use self::model::raw_outcome::RawObservationWriteOutcome;
|
||||||
|
/// Maximum opaque query cursor length admitted by the Store API.
|
||||||
|
pub use self::model::raw_pagination::MAX_RAW_PAGE_CURSOR_BYTES;
|
||||||
|
/// Backend-independent list query for complete canonical RAW account states.
|
||||||
|
pub use self::model::raw_pagination::RawAccountStateQuery;
|
||||||
|
/// One deterministic page of backend-independent Store results.
|
||||||
|
pub use self::model::raw_pagination::RawPage;
|
||||||
|
/// Opaque backend-owned cursor returned by one deterministic Store query.
|
||||||
|
pub use self::model::raw_pagination::RawPageCursor;
|
||||||
|
/// Caller-requested page size without an arbitrary KSP policy ceiling.
|
||||||
|
pub use self::model::raw_pagination::RawPageLimit;
|
||||||
|
/// Opaque-cursor page request used by backend-independent list operations.
|
||||||
|
pub use self::model::raw_pagination::RawPageRequest;
|
||||||
|
/// Optional inclusive Solana slot bounds for one Store query.
|
||||||
|
pub use self::model::raw_pagination::RawSlotRange;
|
||||||
|
/// Deterministic traversal direction for Store list queries.
|
||||||
|
pub use self::model::raw_pagination::RawSortDirection;
|
||||||
|
/// Backend-independent list query for canonical RAW transactions.
|
||||||
|
pub use self::model::raw_pagination::RawTransactionQuery;
|
||||||
|
/// Maximum complete RAW account-data length admitted by the Store API.
|
||||||
|
pub use self::model::raw_primitives::MAX_RAW_ACCOUNT_DATA_BYTES;
|
||||||
|
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
|
||||||
|
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;
|
||||||
|
/// Logical availability state of one canonical RAW payload.
|
||||||
|
pub use self::model::raw_retention::RawRetentionState;
|
||||||
|
/// Outcome of one atomic RAW retention transition.
|
||||||
|
pub use self::model::raw_retention::RawRetentionWriteOutcome;
|
||||||
|
/// Explicit write mode for canonical RAW transaction acquisitions.
|
||||||
|
pub use self::model::raw_retention::RawTransactionAcquisitionMode;
|
||||||
|
/// Requested compare-and-transition operation for one RAW transaction retention state.
|
||||||
|
pub use self::model::raw_retention::RawTransactionRetentionTransition;
|
||||||
|
/// Minimal durable identity retained after a canonical RAW transaction payload is purged.
|
||||||
|
pub use self::model::raw_retention::RawTransactionTombstone;
|
||||||
|
/// Canonical source-independent N1 RAW transaction persisted by Store backends.
|
||||||
|
pub use self::model::raw_transaction::RawTransaction;
|
||||||
|
/// Persistable acquisition observation linked to one canonical RAW transaction.
|
||||||
|
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.
|
||||||
|
pub use ksp_core_lib::ErrorCode;
|
||||||
|
/// Structured contextual field attached to a KSP error.
|
||||||
|
pub use ksp_core_lib::ErrorContext;
|
||||||
|
/// Canonical Solana account address primitive shared by persistent models.
|
||||||
|
pub use ksp_core_lib::Pubkey;
|
||||||
|
/// Common KSP result alias using [`Error`].
|
||||||
|
pub use ksp_core_lib::Result;
|
||||||
16
crates/ksp-store-api/src/model.rs
Normal file
16
crates/ksp-store-api/src/model.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model.rs
|
||||||
|
// version: 4
|
||||||
|
|
||||||
|
//! 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 here. Persistent models and
|
||||||
|
//! backend capabilities remain separate even when one capability operates on
|
||||||
|
//! one or more models.
|
||||||
|
|
||||||
|
pub(crate) mod raw_account;
|
||||||
|
pub(crate) mod raw_outcome;
|
||||||
|
pub(crate) mod raw_pagination;
|
||||||
|
pub(crate) mod raw_primitives;
|
||||||
|
pub(crate) mod raw_retention;
|
||||||
|
pub(crate) mod raw_transaction;
|
||||||
231
crates/ksp-store-api/src/model/raw_account.rs
Normal file
231
crates/ksp-store-api/src/model/raw_account.rs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model/raw_account.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Durable backend-independent identity of one canonical RAW account state.
|
||||||
|
///
|
||||||
|
/// The content hash is part of the identity because one account can be written more than once
|
||||||
|
/// inside the same slot while standard HTTP/WebSocket surfaces do not expose Yellowstone's
|
||||||
|
/// `write_version`. Multiple observations of the same complete state therefore converge on the
|
||||||
|
/// same reference without making a provider-specific write ordinal part of the common model.
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct RawAccountStateReference {
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
pubkey: ksp_core_lib::Pubkey,
|
||||||
|
slot: u64,
|
||||||
|
state_hash: crate::RawContentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAccountStateReference {
|
||||||
|
/// Creates one durable account-state identity from network, account, slot and canonical state digest.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(network: crate::RawNetworkId, pubkey: ksp_core_lib::Pubkey, slot: u64, state_hash: crate::RawContentHash) -> Self {
|
||||||
|
return Self { network, pubkey, slot, state_hash };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the logical Solana network/cluster identifier.
|
||||||
|
#[must_use]
|
||||||
|
pub fn network(&self) -> &crate::RawNetworkId {
|
||||||
|
return &self.network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the account public key.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
||||||
|
return &self.pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the slot associated with this complete account state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn slot(&self) -> u64 {
|
||||||
|
return self.slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the producer-supplied digest of the complete canonical account state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn state_hash(&self) -> crate::RawContentHash {
|
||||||
|
return self.state_hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Canonical complete N1 RAW account state independent from HTTP, WebSocket or gRPC acquisition.
|
||||||
|
///
|
||||||
|
/// Only complete raw account bytes are admissible. A transport response using `jsonParsed`, a
|
||||||
|
/// request-side data slice, or a response without a durable slot context must be normalized or
|
||||||
|
/// reacquired before this model is constructed.
|
||||||
|
pub struct RawAccountState {
|
||||||
|
data: std::boxed::Box<[u8]>,
|
||||||
|
executable: bool,
|
||||||
|
lamports: u64,
|
||||||
|
owner: ksp_core_lib::Pubkey,
|
||||||
|
reference: crate::RawAccountStateReference,
|
||||||
|
rent_epoch: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAccountState {
|
||||||
|
/// Creates one complete canonical RAW account state after Store-owned admission checks.
|
||||||
|
pub fn try_new(
|
||||||
|
reference: crate::RawAccountStateReference,
|
||||||
|
lamports: u64,
|
||||||
|
owner: ksp_core_lib::Pubkey,
|
||||||
|
executable: bool,
|
||||||
|
rent_epoch: u64,
|
||||||
|
data: std::boxed::Box<[u8]>,
|
||||||
|
) -> ksp_core_lib::Result<Self> {
|
||||||
|
if data.len() > crate::MAX_RAW_ACCOUNT_DATA_BYTES {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_MODEL_INVALID, "invalid backend-agnostic RAW Store model")
|
||||||
|
.with_context("field", "account_data")
|
||||||
|
.with_context("actual_len", data.len().to_string())
|
||||||
|
.with_context("maximum_len", crate::MAX_RAW_ACCOUNT_DATA_BYTES.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { data, executable, lamports, owner, reference, rent_epoch });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the exact complete account bytes used by future decoders.
|
||||||
|
#[must_use]
|
||||||
|
pub fn data(&self) -> &[u8] {
|
||||||
|
return self.data.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the complete account-data length in bytes.
|
||||||
|
#[must_use]
|
||||||
|
pub fn data_len(&self) -> usize {
|
||||||
|
return self.data.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the account is executable.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn executable(&self) -> bool {
|
||||||
|
return self.executable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the account lamport balance.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn lamports(&self) -> u64 {
|
||||||
|
return self.lamports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the account owner program public key.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn owner(&self) -> &ksp_core_lib::Pubkey {
|
||||||
|
return &self.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable source-independent account-state identity.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawAccountStateReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the rent epoch reported for this account state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn rent_epoch(&self) -> u64 {
|
||||||
|
return self.rent_epoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for RawAccountState {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter
|
||||||
|
.debug_struct("RawAccountState")
|
||||||
|
.field("reference", &self.reference)
|
||||||
|
.field("lamports", &self.lamports)
|
||||||
|
.field("owner", &self.owner)
|
||||||
|
.field("executable", &self.executable)
|
||||||
|
.field("rent_epoch", &self.rent_epoch)
|
||||||
|
.field("data_len", &self.data.len())
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persistable acquisition observation linked to one complete canonical RAW account state.
|
||||||
|
///
|
||||||
|
/// Yellowstone-only metadata remains optional observation detail and never changes the canonical
|
||||||
|
/// account state itself. HTTP/WS acquisitions therefore use the same observation type without
|
||||||
|
/// inventing a `write_version`, transaction signature or startup flag.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawAccountObservation {
|
||||||
|
account: crate::RawAccountStateReference,
|
||||||
|
is_startup: std::option::Option<bool>,
|
||||||
|
observation_key: crate::RawObservationKey,
|
||||||
|
provenance: crate::RawAcquisitionProvenance,
|
||||||
|
transaction_signature: std::option::Option<crate::RawTransactionSignature>,
|
||||||
|
write_version: std::option::Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAccountObservation {
|
||||||
|
/// Creates one successful observation of a complete canonical RAW account state.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(observation_key: crate::RawObservationKey, account: crate::RawAccountStateReference, provenance: crate::RawAcquisitionProvenance) -> Self {
|
||||||
|
return Self {
|
||||||
|
account,
|
||||||
|
is_startup: std::option::Option::None,
|
||||||
|
observation_key,
|
||||||
|
provenance,
|
||||||
|
transaction_signature: std::option::Option::None,
|
||||||
|
write_version: std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches a provider-reported startup/replay marker when the source exposes one.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_is_startup(mut self, value: bool) -> Self {
|
||||||
|
self.is_startup = std::option::Option::Some(value);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches the transaction signature associated with the account write when exposed by the source.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_transaction_signature(mut self, value: crate::RawTransactionSignature) -> Self {
|
||||||
|
self.transaction_signature = std::option::Option::Some(value);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches the source-specific account write version when the source exposes one.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_write_version(mut self, value: u64) -> Self {
|
||||||
|
self.write_version = std::option::Option::Some(value);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable account-state identity observed by this acquisition.
|
||||||
|
#[must_use]
|
||||||
|
pub fn account(&self) -> &crate::RawAccountStateReference {
|
||||||
|
return &self.account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional source-reported startup/replay marker.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_startup(&self) -> std::option::Option<bool> {
|
||||||
|
return self.is_startup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 optional transaction signature associated with this account write.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn transaction_signature(&self) -> std::option::Option<crate::RawTransactionSignature> {
|
||||||
|
return self.transaction_signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional source-specific account write version.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn write_version(&self) -> std::option::Option<u64> {
|
||||||
|
return self.write_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../../unit_tests/model/raw_account.rs"]
|
||||||
|
mod tests;
|
||||||
55
crates/ksp-store-api/src/model/raw_outcome.rs
Normal file
55
crates/ksp-store-api/src/model/raw_outcome.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model/raw_outcome.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Outcome for one canonical RAW entity in an idempotent persistence operation.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawEntityWriteOutcome {
|
||||||
|
/// The canonical RAW entity was inserted for the first time.
|
||||||
|
Inserted,
|
||||||
|
/// Identical canonical content was already durable.
|
||||||
|
AlreadyPresent,
|
||||||
|
/// A previously purged RAW entity was explicitly rehydrated.
|
||||||
|
Rehydrated,
|
||||||
|
/// Normal persistence skipped a durable purged tombstone.
|
||||||
|
SkippedPurged,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outcome for one deterministic acquisition observation write.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawObservationWriteOutcome {
|
||||||
|
/// The observation was inserted for the first time.
|
||||||
|
Inserted,
|
||||||
|
/// The same observation key already identified identical durable content.
|
||||||
|
AlreadyPresent,
|
||||||
|
/// No observation was recorded because the associated RAW entity was intentionally skipped.
|
||||||
|
NotRecorded,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combined outcome of one atomic canonical RAW entity plus observation acquisition.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct RawAcquisitionWriteOutcome {
|
||||||
|
entity: crate::RawEntityWriteOutcome,
|
||||||
|
observation: crate::RawObservationWriteOutcome,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAcquisitionWriteOutcome {
|
||||||
|
/// Creates one backend-independent atomic acquisition outcome.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(entity: crate::RawEntityWriteOutcome, observation: crate::RawObservationWriteOutcome) -> Self {
|
||||||
|
return Self { entity, observation };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the canonical RAW entity write outcome.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn entity(&self) -> crate::RawEntityWriteOutcome {
|
||||||
|
return self.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the acquisition observation write outcome.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn observation(&self) -> crate::RawObservationWriteOutcome {
|
||||||
|
return self.observation;
|
||||||
|
}
|
||||||
|
}
|
||||||
267
crates/ksp-store-api/src/model/raw_pagination.rs
Normal file
267
crates/ksp-store-api/src/model/raw_pagination.rs
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model/raw_pagination.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Maximum opaque cursor length accepted by Store API queries.
|
||||||
|
///
|
||||||
|
/// This bounds untrusted token material only. It is not a policy limit on the
|
||||||
|
/// number of rows/results a backend may return.
|
||||||
|
pub const MAX_RAW_PAGE_CURSOR_BYTES: usize = 4 * 1024;
|
||||||
|
|
||||||
|
/// Opaque backend-owned cursor returned by one deterministic Store query.
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub struct RawPageCursor(std::boxed::Box<[u8]>);
|
||||||
|
|
||||||
|
impl RawPageCursor {
|
||||||
|
/// Creates one non-empty bounded opaque cursor.
|
||||||
|
pub fn try_new(bytes: std::boxed::Box<[u8]>) -> crate::Result<Self> {
|
||||||
|
if bytes.is_empty() || bytes.len() > crate::MAX_RAW_PAGE_CURSOR_BYTES {
|
||||||
|
return std::result::Result::Err(raw_query_error("cursor"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the opaque cursor bytes unchanged.
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
return self.0.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for RawPageCursor {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter.debug_struct("RawPageCursor").field("len", &self.0.len()).finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caller-requested page size without an arbitrary KSP policy ceiling.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct RawPageLimit(u64);
|
||||||
|
|
||||||
|
impl RawPageLimit {
|
||||||
|
/// Creates one strictly positive requested page size.
|
||||||
|
///
|
||||||
|
/// `ksp-store-api` deliberately imposes no smaller functional maximum.
|
||||||
|
/// Concrete backends may expose or return their real physical limitations.
|
||||||
|
pub fn new(value: u64) -> crate::Result<Self> {
|
||||||
|
if value == 0 {
|
||||||
|
return std::result::Result::Err(raw_query_error("limit"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the exact caller-requested item count.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn get(&self) -> u64 {
|
||||||
|
return self.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opaque-cursor page request used by backend-independent list operations.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawPageRequest {
|
||||||
|
cursor: std::option::Option<crate::RawPageCursor>,
|
||||||
|
limit: crate::RawPageLimit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawPageRequest {
|
||||||
|
/// Creates one first-page request.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn first(limit: crate::RawPageLimit) -> Self {
|
||||||
|
return Self { cursor: std::option::Option::None, limit };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates one continuation-page request with an opaque backend cursor.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn after(limit: crate::RawPageLimit, cursor: crate::RawPageCursor) -> Self {
|
||||||
|
return Self { cursor: std::option::Option::Some(cursor), limit };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the opaque continuation cursor when present.
|
||||||
|
#[must_use]
|
||||||
|
pub fn cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
||||||
|
return self.cursor.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the exact caller-requested page size.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn limit(&self) -> crate::RawPageLimit {
|
||||||
|
return self.limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One deterministic page of backend-independent Store results.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RawPage<T> {
|
||||||
|
items: std::vec::Vec<T>,
|
||||||
|
next_cursor: std::option::Option<crate::RawPageCursor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RawPage<T> {
|
||||||
|
/// Creates one result page from backend-provided items and optional continuation cursor.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(items: std::vec::Vec<T>, next_cursor: std::option::Option<crate::RawPageCursor>) -> Self {
|
||||||
|
return Self { items, next_cursor };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current page items.
|
||||||
|
#[must_use]
|
||||||
|
pub fn items(&self) -> &[T] {
|
||||||
|
return self.items.as_slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the page and returns its items.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_items(self) -> std::vec::Vec<T> {
|
||||||
|
return self.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next opaque cursor when more results are available.
|
||||||
|
#[must_use]
|
||||||
|
pub fn next_cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
||||||
|
return self.next_cursor.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deterministic traversal direction for Store list queries.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawSortDirection {
|
||||||
|
/// Oldest/lower ordered keys first.
|
||||||
|
Ascending,
|
||||||
|
/// Newest/higher ordered keys first.
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional inclusive Solana slot bounds for one Store query.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct RawSlotRange {
|
||||||
|
end_inclusive: std::option::Option<u64>,
|
||||||
|
start_inclusive: std::option::Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawSlotRange {
|
||||||
|
/// Creates one optional inclusive slot range and rejects reversed bounds.
|
||||||
|
pub fn new(start_inclusive: std::option::Option<u64>, end_inclusive: std::option::Option<u64>) -> crate::Result<Self> {
|
||||||
|
if let (std::option::Option::Some(start), std::option::Option::Some(end)) = (start_inclusive, end_inclusive)
|
||||||
|
&& start > end
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(raw_query_error("slot_range"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { end_inclusive, start_inclusive });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional inclusive ending slot.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn end_inclusive(&self) -> std::option::Option<u64> {
|
||||||
|
return self.end_inclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional inclusive starting slot.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn start_inclusive(&self) -> std::option::Option<u64> {
|
||||||
|
return self.start_inclusive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backend-independent list query for canonical RAW transactions.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionQuery {
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
page: crate::RawPageRequest,
|
||||||
|
slots: crate::RawSlotRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionQuery {
|
||||||
|
/// Creates one deterministic transaction-reference query.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(network: crate::RawNetworkId, slots: crate::RawSlotRange, direction: crate::RawSortDirection, page: crate::RawPageRequest) -> Self {
|
||||||
|
return Self { direction, network, page, slots };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested deterministic traversal direction.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||||
|
return self.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the required logical network scope.
|
||||||
|
#[must_use]
|
||||||
|
pub fn network(&self) -> &crate::RawNetworkId {
|
||||||
|
return &self.network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns pagination inputs without exposing backend cursor contents.
|
||||||
|
#[must_use]
|
||||||
|
pub fn page(&self) -> &crate::RawPageRequest {
|
||||||
|
return &self.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns optional inclusive slot bounds.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn slots(&self) -> crate::RawSlotRange {
|
||||||
|
return self.slots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backend-independent list query for complete canonical RAW account states.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawAccountStateQuery {
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
page: crate::RawPageRequest,
|
||||||
|
pubkey: std::option::Option<crate::Pubkey>,
|
||||||
|
slots: crate::RawSlotRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAccountStateQuery {
|
||||||
|
/// Creates one deterministic account-state-reference query.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
pubkey: std::option::Option<crate::Pubkey>,
|
||||||
|
slots: crate::RawSlotRange,
|
||||||
|
direction: crate::RawSortDirection,
|
||||||
|
page: crate::RawPageRequest,
|
||||||
|
) -> Self {
|
||||||
|
return Self { direction, network, page, pubkey, slots };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested deterministic traversal direction.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||||
|
return self.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the required logical network scope.
|
||||||
|
#[must_use]
|
||||||
|
pub fn network(&self) -> &crate::RawNetworkId {
|
||||||
|
return &self.network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns pagination inputs without exposing backend cursor contents.
|
||||||
|
#[must_use]
|
||||||
|
pub fn page(&self) -> &crate::RawPageRequest {
|
||||||
|
return &self.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an optional account-address restriction.
|
||||||
|
#[must_use]
|
||||||
|
pub fn pubkey(&self) -> std::option::Option<&crate::Pubkey> {
|
||||||
|
return self.pubkey.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns optional inclusive slot bounds.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn slots(&self) -> crate::RawSlotRange {
|
||||||
|
return self.slots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_query_error(field: &'static str) -> crate::Error {
|
||||||
|
return crate::Error::new(crate::ERROR_CODE_RAW_QUERY_INVALID, "invalid backend-agnostic RAW Store query").with_context("field", field);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../../unit_tests/model/raw_pagination.rs"]
|
||||||
|
mod tests;
|
||||||
453
crates/ksp-store-api/src/model/raw_primitives.rs
Normal file
453
crates/ksp-store-api/src/model/raw_primitives.rs
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model/raw_primitives.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
/// Maximum complete RAW account-data length admitted by the Store API.
|
||||||
|
///
|
||||||
|
/// This is a Store admission guard, not a Solana protocol-size claim.
|
||||||
|
pub const MAX_RAW_ACCOUNT_DATA_BYTES: usize = 16 * 1024 * 1024;
|
||||||
|
/// 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| return byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.' | b':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../../unit_tests/model/raw_primitives.rs"]
|
||||||
|
mod tests;
|
||||||
157
crates/ksp-store-api/src/model/raw_retention.rs
Normal file
157
crates/ksp-store-api/src/model/raw_retention.rs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model/raw_retention.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
/// Logical availability state of one canonical RAW payload.
|
||||||
|
///
|
||||||
|
/// These states describe payload retention only. They never prove that a
|
||||||
|
/// STRUCTURAL/DECODED/DOMAIN processor has completed.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawRetentionState {
|
||||||
|
/// Complete canonical RAW payload is retained in ordinary Store availability.
|
||||||
|
Full,
|
||||||
|
/// Complete logical RAW content is retained in a compacted representation.
|
||||||
|
Compacted,
|
||||||
|
/// RAW content has been moved to an archive managed outside the hot Store path.
|
||||||
|
Archived,
|
||||||
|
/// RAW payload is absent while a durable tombstone remains.
|
||||||
|
Purged,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit write mode for canonical RAW transaction acquisitions.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawTransactionAcquisitionMode {
|
||||||
|
/// Normal acquisition must skip an existing purged tombstone.
|
||||||
|
Normal,
|
||||||
|
/// Explicitly allow an existing purged transaction to be rehydrated.
|
||||||
|
ForceRehydrate,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal durable identity retained after a canonical RAW transaction payload is purged.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionTombstone {
|
||||||
|
content_hash: crate::RawContentHash,
|
||||||
|
format_id: crate::RawFormatId,
|
||||||
|
format_version: u32,
|
||||||
|
reference: crate::RawTransactionReference,
|
||||||
|
slot: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionTombstone {
|
||||||
|
/// Creates one minimal transaction tombstone from canonical RAW identity metadata.
|
||||||
|
pub fn try_new(
|
||||||
|
reference: crate::RawTransactionReference,
|
||||||
|
slot: u64,
|
||||||
|
format_id: crate::RawFormatId,
|
||||||
|
format_version: u32,
|
||||||
|
content_hash: crate::RawContentHash,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
if format_version == 0 {
|
||||||
|
return std::result::Result::Err(raw_retention_error("format_version"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { content_hash, format_id, format_version, reference, slot });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the canonical content hash retained after purge.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn content_hash(&self) -> crate::RawContentHash {
|
||||||
|
return self.content_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the KSP-owned canonical RAW format identifier retained after purge.
|
||||||
|
#[must_use]
|
||||||
|
pub fn format_id(&self) -> &crate::RawFormatId {
|
||||||
|
return &self.format_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the KSP-owned canonical RAW format version retained after purge.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn format_version(&self) -> u32 {
|
||||||
|
return self.format_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable backend-independent transaction identity.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawTransactionReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the known transaction slot retained after purge.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn slot(&self) -> u64 {
|
||||||
|
return self.slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requested compare-and-transition operation for one RAW transaction retention state.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawTransactionRetentionTransition {
|
||||||
|
expected: crate::RawRetentionState,
|
||||||
|
reference: crate::RawTransactionReference,
|
||||||
|
target: crate::RawRetentionState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTransactionRetentionTransition {
|
||||||
|
/// Creates one allowed forward retention transition.
|
||||||
|
///
|
||||||
|
/// Rehydration from `Purged` is deliberately excluded and uses
|
||||||
|
/// [`crate::RawTransactionAcquisitionMode::ForceRehydrate`] instead.
|
||||||
|
pub fn try_new(reference: crate::RawTransactionReference, expected: crate::RawRetentionState, target: crate::RawRetentionState) -> crate::Result<Self> {
|
||||||
|
if !allowed_transition(expected, target) {
|
||||||
|
return std::result::Result::Err(raw_retention_error("transition"));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { expected, reference, target });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the state the caller expects before applying the transition.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn expected(&self) -> crate::RawRetentionState {
|
||||||
|
return self.expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable transaction identity targeted by the transition.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawTransactionReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested forward retention state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn target(&self) -> crate::RawRetentionState {
|
||||||
|
return self.target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outcome of one atomic RAW retention transition.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum RawRetentionWriteOutcome {
|
||||||
|
/// The requested forward transition was applied.
|
||||||
|
Applied,
|
||||||
|
/// The entity was already at the requested target state.
|
||||||
|
AlreadyAtTarget,
|
||||||
|
/// The current state matched neither the caller's expected state nor the requested target.
|
||||||
|
///
|
||||||
|
/// This outcome makes a lost compare-and-transition race observable without
|
||||||
|
/// treating concurrent policy progress as malformed input.
|
||||||
|
ExpectedStateMismatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allowed_transition(expected: crate::RawRetentionState, target: crate::RawRetentionState) -> bool {
|
||||||
|
return matches!(
|
||||||
|
(expected, target),
|
||||||
|
(crate::RawRetentionState::Full, crate::RawRetentionState::Compacted)
|
||||||
|
| (crate::RawRetentionState::Full, crate::RawRetentionState::Archived)
|
||||||
|
| (crate::RawRetentionState::Compacted, crate::RawRetentionState::Archived)
|
||||||
|
| (crate::RawRetentionState::Archived, crate::RawRetentionState::Purged)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_retention_error(field: &'static str) -> crate::Error {
|
||||||
|
return crate::Error::new(crate::ERROR_CODE_RAW_RETENTION_INVALID, "invalid RAW retention lifecycle request").with_context("field", field);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../../unit_tests/model/raw_retention.rs"]
|
||||||
|
mod tests;
|
||||||
113
crates/ksp-store-api/src/model/raw_transaction.rs
Normal file
113
crates/ksp-store-api/src/model/raw_transaction.rs
Normal 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;
|
||||||
152
crates/ksp-store-api/tests/dependency_boundary.rs
Normal file
152
crates/ksp-store-api/tests/dependency_boundary.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
// file: crates/ksp-store-api/tests/dependency_boundary.rs
|
||||||
|
// version: 5
|
||||||
|
|
||||||
|
//! Dependency canaries for the Store API RAW foundation.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_006_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");
|
||||||
|
let dependencies_tail = match dependencies_tail {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let dependencies = match dependencies_tail.split("[lints]").next() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
assert_eq!(manifest_dependency_names(dependencies), std::vec!["ksp-core-lib"]);
|
||||||
|
for forbidden in [
|
||||||
|
"ksp-config-lib",
|
||||||
|
"ksp-interface-lib",
|
||||||
|
"ksp-logging-lib",
|
||||||
|
"ksp-materializer-api",
|
||||||
|
"ksp-offchain-transport-lib",
|
||||||
|
"ksp-onchain-transport-lib",
|
||||||
|
"ksp-program-api",
|
||||||
|
"ksp-program-lib",
|
||||||
|
"ksp-store-lib",
|
||||||
|
"ksp-store-postgres-lib",
|
||||||
|
"ksp-wallet-lib",
|
||||||
|
"async-trait",
|
||||||
|
"bincode",
|
||||||
|
"chrono",
|
||||||
|
"postgres",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sqlx",
|
||||||
|
"tauri",
|
||||||
|
"tokio",
|
||||||
|
"tokio-postgres",
|
||||||
|
"tonic",
|
||||||
|
"tracing",
|
||||||
|
"wincode",
|
||||||
|
] {
|
||||||
|
assert!(!dependencies.contains(forbidden), "forbidden Store API dependency detected: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_006_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_outcome = include_str!("../src/model/raw_outcome.rs");
|
||||||
|
let raw_pagination = include_str!("../src/model/raw_pagination.rs");
|
||||||
|
let raw_primitives = include_str!("../src/model/raw_primitives.rs");
|
||||||
|
let raw_retention = include_str!("../src/model/raw_retention.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_retention_capability = include_str!("../src/capability/raw_retention.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_outcome"));
|
||||||
|
assert!(model_home.contains("raw_pagination"));
|
||||||
|
assert!(model_home.contains("raw_primitives"));
|
||||||
|
assert!(model_home.contains("raw_retention"));
|
||||||
|
assert!(model_home.contains("raw_transaction"));
|
||||||
|
assert!(capability_home.contains("raw_account"));
|
||||||
|
assert!(capability_home.contains("raw_retention"));
|
||||||
|
assert!(capability_home.contains("raw_transaction"));
|
||||||
|
for source in [
|
||||||
|
crate_root,
|
||||||
|
model_home,
|
||||||
|
raw_account,
|
||||||
|
raw_outcome,
|
||||||
|
raw_pagination,
|
||||||
|
raw_primitives,
|
||||||
|
raw_retention,
|
||||||
|
raw_transaction,
|
||||||
|
capability_home,
|
||||||
|
raw_account_capability,
|
||||||
|
raw_retention_capability,
|
||||||
|
raw_transaction_capability,
|
||||||
|
] {
|
||||||
|
for forbidden in [
|
||||||
|
"ksp_store_lib",
|
||||||
|
"ksp_store_postgres_lib",
|
||||||
|
"ksp_onchain_transport_lib",
|
||||||
|
"ksp_program_api",
|
||||||
|
"serde::",
|
||||||
|
"sqlx::",
|
||||||
|
"tokio::",
|
||||||
|
"tokio_postgres::",
|
||||||
|
"std::env::",
|
||||||
|
"std::fs::",
|
||||||
|
"std::net::",
|
||||||
|
] {
|
||||||
|
assert!(!source.contains(forbidden), "forbidden Store API dependency/runtime path detected: {forbidden}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(!raw_transaction.contains("RawLog"));
|
||||||
|
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!(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_retention_capability.contains("trait RawTransactionRetentionRead"));
|
||||||
|
assert!(raw_retention_capability.contains("trait RawTransactionRetentionWrite"));
|
||||||
|
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_retention_capability.contains(forbidden));
|
||||||
|
assert!(!raw_transaction_capability.contains(forbidden));
|
||||||
|
}
|
||||||
|
assert!(!raw_pagination.contains("u64::MAX"));
|
||||||
|
assert!(!raw_pagination.contains("MAX_RAW_PAGE_ITEMS"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
|
||||||
|
let mut names = std::vec::Vec::new();
|
||||||
|
for line in section.lines() {
|
||||||
|
let content = match line.split('#').next() {
|
||||||
|
std::option::Option::Some(value) => value.trim(),
|
||||||
|
std::option::Option::None => continue,
|
||||||
|
};
|
||||||
|
if content.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = match content.split('=').next() {
|
||||||
|
std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"),
|
||||||
|
std::option::Option::None => continue,
|
||||||
|
};
|
||||||
|
if !name.is_empty() {
|
||||||
|
names.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names.sort_unstable();
|
||||||
|
return names;
|
||||||
|
}
|
||||||
194
crates/ksp-store-api/tests/external_backend.rs
Normal file
194
crates/ksp-store-api/tests/external_backend.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// file: crates/ksp-store-api/tests/external_backend.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
//! 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_raw_transactions<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a ksp_store_api::RawTransactionQuery,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawPage<ksp_store_api::RawTransactionReference>>> {
|
||||||
|
let _ = query;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawPage::new(std::vec::Vec::new(), 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,
|
||||||
|
mode: ksp_store_api::RawTransactionAcquisitionMode,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawAcquisitionWriteOutcome>> {
|
||||||
|
let _ = transaction;
|
||||||
|
let _ = observation;
|
||||||
|
let _ = mode;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawAcquisitionWriteOutcome::new(
|
||||||
|
ksp_store_api::RawEntityWriteOutcome::Inserted,
|
||||||
|
ksp_store_api::RawObservationWriteOutcome::Inserted,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ksp_store_api::RawObservationWriteOutcome>> {
|
||||||
|
let _ = observation;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_raw_account_states<'a>(
|
||||||
|
&'a self,
|
||||||
|
query: &'a ksp_store_api::RawAccountStateQuery,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawPage<ksp_store_api::RawAccountStateReference>>> {
|
||||||
|
let _ = query;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawPage::new(std::vec::Vec::new(), 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<ksp_store_api::RawAcquisitionWriteOutcome>> {
|
||||||
|
let _ = state;
|
||||||
|
let _ = observation;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawAcquisitionWriteOutcome::new(
|
||||||
|
ksp_store_api::RawEntityWriteOutcome::Inserted,
|
||||||
|
ksp_store_api::RawObservationWriteOutcome::Inserted,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ksp_store_api::RawObservationWriteOutcome>> {
|
||||||
|
let _ = observation;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_store_api::RawTransactionRetentionRead for ExternalMemoryBackend {
|
||||||
|
fn get_raw_transaction_retention_state<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a ksp_store_api::RawTransactionReference,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawRetentionState>>> {
|
||||||
|
let _ = reference;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw_transaction_tombstone<'a>(
|
||||||
|
&'a self,
|
||||||
|
reference: &'a ksp_store_api::RawTransactionReference,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawTransactionTombstone>>> {
|
||||||
|
let _ = reference;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_store_api::RawTransactionRetentionWrite for ExternalMemoryBackend {
|
||||||
|
fn transition_raw_transaction_retention<'a>(
|
||||||
|
&'a self,
|
||||||
|
transition: ksp_store_api::RawTransactionRetentionTransition,
|
||||||
|
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawRetentionWriteOutcome>> {
|
||||||
|
let _ = transition;
|
||||||
|
return std::boxed::Box::pin(async {
|
||||||
|
return std::result::Result::Ok(ksp_store_api::RawRetentionWriteOutcome::Applied);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_006_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 retention_read: &dyn ksp_store_api::RawTransactionRetentionRead = &backend;
|
||||||
|
let retention_write: &dyn ksp_store_api::RawTransactionRetentionWrite = &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;
|
||||||
|
let _ = retention_read;
|
||||||
|
let _ = retention_write;
|
||||||
|
return;
|
||||||
|
}
|
||||||
188
crates/ksp-store-api/tests/public_api.rs
Normal file
188
crates/ksp-store-api/tests/public_api.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// file: crates/ksp-store-api/tests/public_api.rs
|
||||||
|
// version: 6
|
||||||
|
|
||||||
|
//! Integration canaries for the public `ksp-store-api` surface.
|
||||||
|
|
||||||
|
fn consume_result(value: ksp_store_api::Result<ksp_store_api::Pubkey>) -> ksp_store_api::Result<ksp_store_api::Pubkey> {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code(value: &str) -> std::option::Option<ksp_store_api::RawProvenanceCode> {
|
||||||
|
return match ksp_store_api::RawProvenanceCode::new(value.to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||||
|
std::result::Result::Err(_) => std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_002_core_facade_remains_available_from_crate_root() {
|
||||||
|
let pubkey = ksp_store_api::Pubkey::new_from_array([0x31_u8; 32]);
|
||||||
|
let forwarded = consume_result(std::result::Result::Ok(pubkey));
|
||||||
|
assert!(forwarded.is_ok());
|
||||||
|
let error_code_type: std::option::Option<ksp_store_api::ErrorCode> = std::option::Option::None;
|
||||||
|
let error_context_type: std::option::Option<ksp_store_api::ErrorContext> = std::option::Option::None;
|
||||||
|
let error_type: std::option::Option<ksp_store_api::Error> = std::option::Option::None;
|
||||||
|
assert!(error_code_type.is_none());
|
||||||
|
assert!(error_context_type.is_none());
|
||||||
|
assert!(error_type.is_none());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_root() {
|
||||||
|
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let reference = ksp_store_api::RawTransactionReference::new(network, ksp_store_api::RawTransactionSignature::new([1_u8; 64]));
|
||||||
|
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 payload = match ksp_store_api::RawPayload::try_new(format, 1, vec![1_u8, 2_u8, 3_u8].into_boxed_slice(), ksp_store_api::RawContentHash::new([2_u8; 32]))
|
||||||
|
{
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let transaction = ksp_store_api::RawTransaction::new(reference.clone(), 123, std::option::Option::None, payload);
|
||||||
|
let received_at = match ksp_store_api::RawTimestamp::from_unix_millis(1_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let provider = match code("provider") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let protocol = match code("solana_http") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let method = match code("getTransaction") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let provenance = ksp_store_api::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_api::RawAcquisitionOrigin::Backfill, received_at);
|
||||||
|
let observation = ksp_store_api::RawTransactionObservation::new(ksp_store_api::RawObservationKey::new([3_u8; 32]), reference, provenance);
|
||||||
|
assert_eq!(transaction.slot(), 123);
|
||||||
|
assert_eq!(transaction.payload().bytes(), &[1_u8, 2_u8, 3_u8]);
|
||||||
|
assert_eq!(observation.transaction().network().as_str(), "mainnet-beta");
|
||||||
|
assert_eq!(observation.provenance().acquisition_method().as_str(), "getTransaction");
|
||||||
|
assert_eq!(ksp_store_api::ERROR_CODE_RAW_PAYLOAD_INVALID.domain(), "store_api");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_003_surface_keeps_backend_and_structural_types_out() {
|
||||||
|
let source = include_str!("../src/lib.rs");
|
||||||
|
assert!(!source.contains("pub mod "));
|
||||||
|
for forbidden in ["Postgres", "Sql", "Migration", "StructuralTransaction", "RawLog", "ProgramInstruction"] {
|
||||||
|
assert!(!source.contains(forbidden), "forbidden pre.003 Store API public concept detected: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_004_raw_account_state_and_observation_are_constructible_from_crate_root() {
|
||||||
|
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let reference = ksp_store_api::RawAccountStateReference::new(
|
||||||
|
network,
|
||||||
|
ksp_store_api::Pubkey::new_from_array([21_u8; 32]),
|
||||||
|
55,
|
||||||
|
ksp_store_api::RawContentHash::new([22_u8; 32]),
|
||||||
|
);
|
||||||
|
let state = ksp_store_api::RawAccountState::try_new(
|
||||||
|
reference.clone(),
|
||||||
|
123,
|
||||||
|
ksp_store_api::Pubkey::new_from_array([23_u8; 32]),
|
||||||
|
false,
|
||||||
|
9,
|
||||||
|
vec![1_u8, 2_u8].into_boxed_slice(),
|
||||||
|
);
|
||||||
|
assert!(state.is_ok());
|
||||||
|
let received_at = match ksp_store_api::RawTimestamp::from_unix_millis(2_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let provider = match code("provider") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let protocol = match code("solana_http") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let method = match code("getAccountInfo") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let provenance = ksp_store_api::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_api::RawAcquisitionOrigin::Backfill, received_at);
|
||||||
|
let observation = ksp_store_api::RawAccountObservation::new(ksp_store_api::RawObservationKey::new([24_u8; 32]), reference, provenance);
|
||||||
|
assert_eq!(observation.account().slot(), 55);
|
||||||
|
assert!(observation.write_version().is_none());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_006_query_outcome_and_retention_contracts_are_available_from_crate_root() {
|
||||||
|
let limit = match ksp_store_api::RawPageLimit::new(5_000_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(limit.get(), 5_000_000);
|
||||||
|
assert!(ksp_store_api::RawPageLimit::new(u64::MAX).is_ok());
|
||||||
|
let slots = match ksp_store_api::RawSlotRange::new(std::option::Option::Some(1), std::option::Option::Some(2)) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let query =
|
||||||
|
ksp_store_api::RawTransactionQuery::new(network, slots, ksp_store_api::RawSortDirection::Ascending, ksp_store_api::RawPageRequest::first(limit));
|
||||||
|
assert_eq!(query.page().limit().get(), 5_000_000);
|
||||||
|
let outcome =
|
||||||
|
ksp_store_api::RawAcquisitionWriteOutcome::new(ksp_store_api::RawEntityWriteOutcome::Inserted, ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||||
|
assert_eq!(outcome.entity(), ksp_store_api::RawEntityWriteOutcome::Inserted);
|
||||||
|
assert_eq!(outcome.observation(), ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||||
|
let retention_read: std::option::Option<&dyn ksp_store_api::RawTransactionRetentionRead> = std::option::Option::None;
|
||||||
|
let retention_write: std::option::Option<&dyn ksp_store_api::RawTransactionRetentionWrite> = std::option::Option::None;
|
||||||
|
assert!(retention_read.is_none());
|
||||||
|
assert!(retention_write.is_none());
|
||||||
|
assert_eq!(ksp_store_api::ERROR_CODE_RAW_CONFLICT.domain(), "store_api");
|
||||||
|
assert_eq!(ksp_store_api::ERROR_CODE_RAW_QUERY_INVALID.domain(), "store_api");
|
||||||
|
assert_eq!(ksp_store_api::ERROR_CODE_RAW_RETENTION_INVALID.domain(), "store_api");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_007_retention_race_outcome_is_available_from_crate_root() {
|
||||||
|
assert_ne!(ksp_store_api::RawRetentionWriteOutcome::Applied, ksp_store_api::RawRetentionWriteOutcome::ExpectedStateMismatch);
|
||||||
|
assert_ne!(ksp_store_api::RawRetentionWriteOutcome::AlreadyAtTarget, ksp_store_api::RawRetentionWriteOutcome::ExpectedStateMismatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
256
crates/ksp-store-api/tests/release_completeness.rs
Normal file
256
crates/ksp-store-api/tests/release_completeness.rs
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// file: crates/ksp-store-api/tests/release_completeness.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Release-level boundary and completeness canaries for the `0.3.1` Store API RAW surface.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_exact_crate_root_export_inventory_is_stable() {
|
||||||
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
|
let mut actual = std::vec::Vec::new();
|
||||||
|
for line in crate_root.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.starts_with("pub use ") {
|
||||||
|
actual.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual.sort_unstable();
|
||||||
|
let mut expected = std::vec![
|
||||||
|
"pub use ksp_core_lib::Error;",
|
||||||
|
"pub use ksp_core_lib::ErrorCode;",
|
||||||
|
"pub use ksp_core_lib::ErrorContext;",
|
||||||
|
"pub use ksp_core_lib::Pubkey;",
|
||||||
|
"pub use ksp_core_lib::Result;",
|
||||||
|
"pub use self::capability::StoreApiFuture;",
|
||||||
|
"pub use self::capability::raw_account::RawAccountObservationRead;",
|
||||||
|
"pub use self::capability::raw_account::RawAccountObservationWrite;",
|
||||||
|
"pub use self::capability::raw_account::RawAccountStateRead;",
|
||||||
|
"pub use self::capability::raw_account::RawAccountStateWrite;",
|
||||||
|
"pub use self::capability::raw_retention::RawTransactionRetentionRead;",
|
||||||
|
"pub use self::capability::raw_retention::RawTransactionRetentionWrite;",
|
||||||
|
"pub use self::capability::raw_transaction::RawTransactionObservationRead;",
|
||||||
|
"pub use self::capability::raw_transaction::RawTransactionObservationWrite;",
|
||||||
|
"pub use self::capability::raw_transaction::RawTransactionRead;",
|
||||||
|
"pub use self::capability::raw_transaction::RawTransactionWrite;",
|
||||||
|
"pub use self::error::ERROR_CODE_RAW_CONFLICT;",
|
||||||
|
"pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;",
|
||||||
|
"pub use self::error::ERROR_CODE_RAW_PAYLOAD_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_RETENTION_INVALID;",
|
||||||
|
"pub use self::model::raw_account::RawAccountObservation;",
|
||||||
|
"pub use self::model::raw_account::RawAccountState;",
|
||||||
|
"pub use self::model::raw_account::RawAccountStateReference;",
|
||||||
|
"pub use self::model::raw_outcome::RawAcquisitionWriteOutcome;",
|
||||||
|
"pub use self::model::raw_outcome::RawEntityWriteOutcome;",
|
||||||
|
"pub use self::model::raw_outcome::RawObservationWriteOutcome;",
|
||||||
|
"pub use self::model::raw_pagination::MAX_RAW_PAGE_CURSOR_BYTES;",
|
||||||
|
"pub use self::model::raw_pagination::RawAccountStateQuery;",
|
||||||
|
"pub use self::model::raw_pagination::RawPage;",
|
||||||
|
"pub use self::model::raw_pagination::RawPageCursor;",
|
||||||
|
"pub use self::model::raw_pagination::RawPageLimit;",
|
||||||
|
"pub use self::model::raw_pagination::RawPageRequest;",
|
||||||
|
"pub use self::model::raw_pagination::RawSlotRange;",
|
||||||
|
"pub use self::model::raw_pagination::RawSortDirection;",
|
||||||
|
"pub use self::model::raw_pagination::RawTransactionQuery;",
|
||||||
|
"pub use self::model::raw_primitives::MAX_RAW_ACCOUNT_DATA_BYTES;",
|
||||||
|
"pub use self::model::raw_primitives::MAX_RAW_CODE_BYTES;",
|
||||||
|
"pub use self::model::raw_primitives::MAX_RAW_PAYLOAD_BYTES;",
|
||||||
|
"pub use self::model::raw_primitives::MAX_RAW_SOURCE_PAYLOAD_BYTES;",
|
||||||
|
"pub use self::model::raw_primitives::MAX_RAW_UNIX_MILLIS;",
|
||||||
|
"pub use self::model::raw_primitives::RawAcquisitionOrigin;",
|
||||||
|
"pub use self::model::raw_primitives::RawAcquisitionProvenance;",
|
||||||
|
"pub use self::model::raw_primitives::RawContentHash;",
|
||||||
|
"pub use self::model::raw_primitives::RawFormatId;",
|
||||||
|
"pub use self::model::raw_primitives::RawNetworkId;",
|
||||||
|
"pub use self::model::raw_primitives::RawObservationKey;",
|
||||||
|
"pub use self::model::raw_primitives::RawPayload;",
|
||||||
|
"pub use self::model::raw_primitives::RawProvenanceCode;",
|
||||||
|
"pub use self::model::raw_primitives::RawTimestamp;",
|
||||||
|
"pub use self::model::raw_primitives::RawTransactionSignature;",
|
||||||
|
"pub use self::model::raw_retention::RawRetentionState;",
|
||||||
|
"pub use self::model::raw_retention::RawRetentionWriteOutcome;",
|
||||||
|
"pub use self::model::raw_retention::RawTransactionAcquisitionMode;",
|
||||||
|
"pub use self::model::raw_retention::RawTransactionRetentionTransition;",
|
||||||
|
"pub use self::model::raw_retention::RawTransactionTombstone;",
|
||||||
|
"pub use self::model::raw_transaction::RawTransaction;",
|
||||||
|
"pub use self::model::raw_transaction::RawTransactionObservation;",
|
||||||
|
"pub use self::model::raw_transaction::RawTransactionReference;",
|
||||||
|
];
|
||||||
|
expected.sort_unstable();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert!(!crate_root.contains("pub mod "));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_exact_production_module_inventory_is_raw_only() {
|
||||||
|
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
||||||
|
let root_names = rust_file_names(root.as_path());
|
||||||
|
assert!(root_names.is_ok());
|
||||||
|
let root_names = match root_names {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(root_names, std::vec!["capability.rs", "error.rs", "lib.rs", "model.rs"]);
|
||||||
|
let model_names = rust_file_names(root.join("model").as_path());
|
||||||
|
assert!(model_names.is_ok());
|
||||||
|
let model_names = match model_names {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
model_names,
|
||||||
|
std::vec!["raw_account.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());
|
||||||
|
assert!(capability_names.is_ok());
|
||||||
|
let capability_names = match capability_names {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(capability_names, std::vec!["raw_account.rs", "raw_retention.rs", "raw_transaction.rs"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_public_evolutive_enums_remain_non_exhaustive() {
|
||||||
|
let sources = [
|
||||||
|
include_str!("../src/model/raw_outcome.rs"),
|
||||||
|
include_str!("../src/model/raw_pagination.rs"),
|
||||||
|
include_str!("../src/model/raw_primitives.rs"),
|
||||||
|
include_str!("../src/model/raw_retention.rs"),
|
||||||
|
];
|
||||||
|
for enum_name in [
|
||||||
|
"RawAcquisitionOrigin",
|
||||||
|
"RawEntityWriteOutcome",
|
||||||
|
"RawObservationWriteOutcome",
|
||||||
|
"RawRetentionState",
|
||||||
|
"RawRetentionWriteOutcome",
|
||||||
|
"RawSortDirection",
|
||||||
|
"RawTransactionAcquisitionMode",
|
||||||
|
] {
|
||||||
|
assert_non_exhaustive(&sources, enum_name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_interface_store_ownership_and_negative_scope_remain_explicit() {
|
||||||
|
let interface_root = include_str!("../../ksp-interface-lib/src/lib.rs");
|
||||||
|
let store_sources = [
|
||||||
|
include_str!("../src/lib.rs"),
|
||||||
|
include_str!("../src/model.rs"),
|
||||||
|
include_str!("../src/model/raw_account.rs"),
|
||||||
|
include_str!("../src/model/raw_outcome.rs"),
|
||||||
|
include_str!("../src/model/raw_pagination.rs"),
|
||||||
|
include_str!("../src/model/raw_primitives.rs"),
|
||||||
|
include_str!("../src/model/raw_retention.rs"),
|
||||||
|
include_str!("../src/model/raw_transaction.rs"),
|
||||||
|
include_str!("../src/capability.rs"),
|
||||||
|
include_str!("../src/capability/raw_account.rs"),
|
||||||
|
include_str!("../src/capability/raw_retention.rs"),
|
||||||
|
include_str!("../src/capability/raw_transaction.rs"),
|
||||||
|
];
|
||||||
|
for persistent_name in ["RawTransaction", "RawAccountState", "RawRetentionState", "RawTransactionTombstone"] {
|
||||||
|
assert!(!interface_root.contains(persistent_name), "persistent Store model leaked into Interface crate root: {persistent_name}");
|
||||||
|
}
|
||||||
|
for source in store_sources {
|
||||||
|
for forbidden in [
|
||||||
|
"RawLogNotification",
|
||||||
|
"RawSlotEvent",
|
||||||
|
"RawVoteEvent",
|
||||||
|
"YellowstoneEntry",
|
||||||
|
"StructuralInstruction",
|
||||||
|
"StructuralTransaction",
|
||||||
|
"DecodedInstruction",
|
||||||
|
"DomainRecord",
|
||||||
|
"ProgramInstructionDecoder",
|
||||||
|
"PostgresStore",
|
||||||
|
"MySqlStore",
|
||||||
|
"StoreBackend",
|
||||||
|
] {
|
||||||
|
assert!(!source.contains(forbidden), "deferred or backend-specific Store surface detected: {forbidden}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_capability_inventory_stays_fine_grained_without_runtime_facade() {
|
||||||
|
let sources = [
|
||||||
|
include_str!("../src/capability/raw_account.rs"),
|
||||||
|
include_str!("../src/capability/raw_retention.rs"),
|
||||||
|
include_str!("../src/capability/raw_transaction.rs"),
|
||||||
|
];
|
||||||
|
let mut traits = std::vec::Vec::new();
|
||||||
|
for source in sources {
|
||||||
|
for line in source.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.starts_with("pub trait ") {
|
||||||
|
traits.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traits.sort_unstable();
|
||||||
|
let mut expected = std::vec![
|
||||||
|
"pub trait RawAccountObservationRead: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawAccountObservationWrite: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawAccountStateRead: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawAccountStateWrite: 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 RawTransactionRead: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawTransactionRetentionRead: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawTransactionRetentionWrite: std::marker::Send + std::marker::Sync {",
|
||||||
|
"pub trait RawTransactionWrite: std::marker::Send + std::marker::Sync {",
|
||||||
|
];
|
||||||
|
expected.sort_unstable();
|
||||||
|
assert_eq!(traits, expected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_non_exhaustive(sources: &[&str], enum_name: &str) {
|
||||||
|
let needle = "#[non_exhaustive]\n";
|
||||||
|
let declaration = std::format!("pub enum {enum_name}");
|
||||||
|
let mut found = false;
|
||||||
|
for source in sources {
|
||||||
|
if let std::option::Option::Some(position) = source.find(declaration.as_str()) {
|
||||||
|
let prefix = &source[..position];
|
||||||
|
let attribute_position = prefix.rfind(needle);
|
||||||
|
assert!(attribute_position.is_some(), "public evolutive enum is missing #[non_exhaustive]: {enum_name}");
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(found, "public evolutive enum not found: {enum_name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rust_file_names(directory: &std::path::Path) -> std::io::Result<std::vec::Vec<std::string::String>> {
|
||||||
|
let entries = match std::fs::read_dir(directory) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let mut names = std::vec::Vec::new();
|
||||||
|
for entry in entries {
|
||||||
|
let entry = match entry {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let file_type = match entry.file_type() {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
if !file_type.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = match entry.file_name().into_string() {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => continue,
|
||||||
|
};
|
||||||
|
if name.ends_with(".rs") {
|
||||||
|
names.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names.sort_unstable();
|
||||||
|
return std::result::Result::Ok(names);
|
||||||
|
}
|
||||||
124
crates/ksp-store-api/tests/security_hardening.rs
Normal file
124
crates/ksp-store-api/tests/security_hardening.rs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// file: crates/ksp-store-api/tests/security_hardening.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Adversarial and retention-race canaries for the Store API RAW foundation.
|
||||||
|
|
||||||
|
const HOSTILE_MARKER: &str = "STORE-RAW-SECRET-CANARY";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_hostile_model_query_and_provenance_errors_never_echo_input_material() {
|
||||||
|
let hostile_code = std::format!("https://example.invalid/{HOSTILE_MARKER}");
|
||||||
|
let network = ksp_store_api::RawNetworkId::new(hostile_code.clone());
|
||||||
|
assert!(network.is_err());
|
||||||
|
let network_error = match network {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(network_error.code(), ksp_store_api::ERROR_CODE_RAW_MODEL_INVALID);
|
||||||
|
assert!(!std::format!("{network_error}").contains(HOSTILE_MARKER));
|
||||||
|
assert!(!std::format!("{network_error:?}").contains(HOSTILE_MARKER));
|
||||||
|
let provenance = ksp_store_api::RawProvenanceCode::new(hostile_code.clone());
|
||||||
|
assert!(provenance.is_err());
|
||||||
|
let provenance_error = match provenance {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(provenance_error.code(), ksp_store_api::ERROR_CODE_RAW_PROVENANCE_INVALID);
|
||||||
|
assert!(!std::format!("{provenance_error}").contains(HOSTILE_MARKER));
|
||||||
|
assert!(!std::format!("{provenance_error:?}").contains(HOSTILE_MARKER));
|
||||||
|
let format = ksp_store_api::RawFormatId::new(hostile_code);
|
||||||
|
assert!(format.is_err());
|
||||||
|
let format_error = match format {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(format_error.code(), ksp_store_api::ERROR_CODE_RAW_PAYLOAD_INVALID);
|
||||||
|
assert!(!std::format!("{format_error}").contains(HOSTILE_MARKER));
|
||||||
|
assert!(!std::format!("{format_error:?}").contains(HOSTILE_MARKER));
|
||||||
|
let cursor = ksp_store_api::RawPageCursor::try_new(std::vec![0x41_u8; ksp_store_api::MAX_RAW_PAGE_CURSOR_BYTES + 1].into_boxed_slice());
|
||||||
|
assert!(cursor.is_err());
|
||||||
|
let cursor_error = match cursor {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(cursor_error.code(), ksp_store_api::ERROR_CODE_RAW_QUERY_INVALID);
|
||||||
|
assert!(!std::format!("{cursor_error}").contains(HOSTILE_MARKER));
|
||||||
|
assert!(!std::format!("{cursor_error:?}").contains(HOSTILE_MARKER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_raw_debug_surfaces_do_not_render_payload_hash_signature_or_account_bytes() {
|
||||||
|
let marker_bytes = HOSTILE_MARKER.as_bytes().to_vec().into_boxed_slice();
|
||||||
|
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 payload = ksp_store_api::RawPayload::try_new(format, 1, marker_bytes, ksp_store_api::RawContentHash::new([0x53_u8; 32]));
|
||||||
|
assert!(payload.is_ok());
|
||||||
|
let payload = match payload {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(!std::format!("{payload:?}").contains(HOSTILE_MARKER));
|
||||||
|
assert_eq!(std::format!("{:?}", payload.content_hash()), "RawContentHash(..)");
|
||||||
|
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let signature = ksp_store_api::RawTransactionSignature::new([0x54_u8; 64]);
|
||||||
|
assert_eq!(std::format!("{signature:?}"), "RawTransactionSignature(..)");
|
||||||
|
let reference = ksp_store_api::RawTransactionReference::new(network.clone(), signature);
|
||||||
|
let transaction = ksp_store_api::RawTransaction::new(reference, 7, std::option::Option::None, payload);
|
||||||
|
assert!(!std::format!("{transaction:?}").contains(HOSTILE_MARKER));
|
||||||
|
let account_reference = ksp_store_api::RawAccountStateReference::new(
|
||||||
|
network,
|
||||||
|
ksp_store_api::Pubkey::new_from_array([0x55_u8; 32]),
|
||||||
|
8,
|
||||||
|
ksp_store_api::RawContentHash::new([0x56_u8; 32]),
|
||||||
|
);
|
||||||
|
let account = ksp_store_api::RawAccountState::try_new(
|
||||||
|
account_reference,
|
||||||
|
1,
|
||||||
|
ksp_store_api::Pubkey::new_from_array([0x57_u8; 32]),
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
HOSTILE_MARKER.as_bytes().to_vec().into_boxed_slice(),
|
||||||
|
);
|
||||||
|
assert!(account.is_ok());
|
||||||
|
let account = match account {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(!std::format!("{account:?}").contains(HOSTILE_MARKER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_cursor_boundary_and_page_limit_preserve_security_without_executor_policy() {
|
||||||
|
let cursor = ksp_store_api::RawPageCursor::try_new(std::vec![0x5A_u8; ksp_store_api::MAX_RAW_PAGE_CURSOR_BYTES].into_boxed_slice());
|
||||||
|
assert!(cursor.is_ok());
|
||||||
|
let cursor = match cursor {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(cursor.as_bytes().len(), ksp_store_api::MAX_RAW_PAGE_CURSOR_BYTES);
|
||||||
|
assert!(!std::format!("{cursor:?}").contains("ZZZZ"));
|
||||||
|
assert!(ksp_store_api::RawPageCursor::try_new(std::vec![0x5A_u8; ksp_store_api::MAX_RAW_PAGE_CURSOR_BYTES + 1].into_boxed_slice()).is_err());
|
||||||
|
assert!(ksp_store_api::RawPageLimit::new(0).is_err());
|
||||||
|
let unlimited_policy = ksp_store_api::RawPageLimit::new(u64::MAX);
|
||||||
|
assert!(unlimited_policy.is_ok());
|
||||||
|
let unlimited_policy = match unlimited_policy {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(unlimited_policy.get(), u64::MAX);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_007_retention_outcome_distinguishes_lost_compare_and_transition_race() {
|
||||||
|
assert_ne!(ksp_store_api::RawRetentionWriteOutcome::Applied, ksp_store_api::RawRetentionWriteOutcome::ExpectedStateMismatch);
|
||||||
|
assert_ne!(ksp_store_api::RawRetentionWriteOutcome::AlreadyAtTarget, ksp_store_api::RawRetentionWriteOutcome::ExpectedStateMismatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
108
crates/ksp-store-api/unit_tests/model/raw_account.rs
Normal file
108
crates/ksp-store-api/unit_tests/model/raw_account.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// file: crates/ksp-store-api/unit_tests/model/raw_account.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
fn network() -> std::option::Option<crate::RawNetworkId> {
|
||||||
|
return match crate::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||||
|
std::result::Result::Err(_) => std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn provenance() -> std::option::Option<crate::RawAcquisitionProvenance> {
|
||||||
|
let provider = match crate::RawProvenanceCode::new("publicnode".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let protocol = match crate::RawProvenanceCode::new("yellowstone_grpc".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let method = match crate::RawProvenanceCode::new("accounts".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let received_at = match crate::RawTimestamp::from_unix_millis(1_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
return std::option::Option::Some(crate::RawAcquisitionProvenance::new(provider, protocol, method, crate::RawAcquisitionOrigin::Live, received_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_account_state_preserves_complete_common_fields_and_redacts_data_debug() {
|
||||||
|
let network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let pubkey = ksp_core_lib::Pubkey::new_from_array([1_u8; 32]);
|
||||||
|
let owner = ksp_core_lib::Pubkey::new_from_array([2_u8; 32]);
|
||||||
|
let reference = crate::RawAccountStateReference::new(network, pubkey, 42, crate::RawContentHash::new([3_u8; 32]));
|
||||||
|
let data = b"ACCOUNT_DATA_SENTINEL_NEVER_RENDER".to_vec().into_boxed_slice();
|
||||||
|
let state_result = crate::RawAccountState::try_new(reference.clone(), 500, owner, false, 7, data);
|
||||||
|
assert!(state_result.is_ok());
|
||||||
|
let state = match state_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(state.reference(), &reference);
|
||||||
|
assert_eq!(state.lamports(), 500);
|
||||||
|
assert!(!state.executable());
|
||||||
|
assert_eq!(state.rent_epoch(), 7);
|
||||||
|
assert_eq!(state.data(), b"ACCOUNT_DATA_SENTINEL_NEVER_RENDER");
|
||||||
|
let debug = format!("{state:?}");
|
||||||
|
assert!(!debug.contains("ACCOUNT_DATA_SENTINEL_NEVER_RENDER"));
|
||||||
|
assert!(debug.contains("data_len"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_account_state_rejects_only_oversized_data_and_allows_empty_accounts() {
|
||||||
|
let first_network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let first_reference =
|
||||||
|
crate::RawAccountStateReference::new(first_network, ksp_core_lib::Pubkey::new_from_array([4_u8; 32]), 1, crate::RawContentHash::new([5_u8; 32]));
|
||||||
|
let empty = crate::RawAccountState::try_new(
|
||||||
|
first_reference,
|
||||||
|
0,
|
||||||
|
ksp_core_lib::Pubkey::new_from_array([6_u8; 32]),
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
std::vec::Vec::new().into_boxed_slice(),
|
||||||
|
);
|
||||||
|
assert!(empty.is_ok());
|
||||||
|
let second_network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let second_reference =
|
||||||
|
crate::RawAccountStateReference::new(second_network, ksp_core_lib::Pubkey::new_from_array([7_u8; 32]), 2, crate::RawContentHash::new([8_u8; 32]));
|
||||||
|
let oversized = vec![0_u8; crate::MAX_RAW_ACCOUNT_DATA_BYTES + 1].into_boxed_slice();
|
||||||
|
let rejected = crate::RawAccountState::try_new(second_reference, 0, ksp_core_lib::Pubkey::new_from_array([9_u8; 32]), false, 0, oversized);
|
||||||
|
assert!(rejected.is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_account_observation_keeps_yellowstone_specific_metadata_optional() {
|
||||||
|
let network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let reference =
|
||||||
|
crate::RawAccountStateReference::new(network, ksp_core_lib::Pubkey::new_from_array([10_u8; 32]), 99, crate::RawContentHash::new([11_u8; 32]));
|
||||||
|
let provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let observation = crate::RawAccountObservation::new(crate::RawObservationKey::new([12_u8; 32]), reference.clone(), provenance)
|
||||||
|
.with_write_version(17)
|
||||||
|
.with_transaction_signature(crate::RawTransactionSignature::new([13_u8; 64]))
|
||||||
|
.with_is_startup(false);
|
||||||
|
assert_eq!(observation.account(), &reference);
|
||||||
|
assert_eq!(observation.write_version(), std::option::Option::Some(17));
|
||||||
|
assert_eq!(observation.is_startup(), std::option::Option::Some(false));
|
||||||
|
assert_eq!(observation.transaction_signature(), std::option::Option::Some(crate::RawTransactionSignature::new([13_u8; 64])));
|
||||||
|
return;
|
||||||
|
}
|
||||||
54
crates/ksp-store-api/unit_tests/model/raw_pagination.rs
Normal file
54
crates/ksp-store-api/unit_tests/model/raw_pagination.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// file: crates/ksp-store-api/unit_tests/model/raw_pagination.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn page_limit_rejects_only_zero_and_keeps_no_ksp_policy_maximum() {
|
||||||
|
assert!(crate::RawPageLimit::new(0).is_err());
|
||||||
|
let maximum = crate::RawPageLimit::new(u64::MAX);
|
||||||
|
assert!(maximum.is_ok());
|
||||||
|
let maximum = match maximum {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(maximum.get(), u64::MAX);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cursor_is_opaque_bounded_and_debug_does_not_render_bytes() {
|
||||||
|
assert!(crate::RawPageCursor::try_new(std::boxed::Box::new([])).is_err());
|
||||||
|
assert!(crate::RawPageCursor::try_new(vec![7_u8; crate::MAX_RAW_PAGE_CURSOR_BYTES + 1].into_boxed_slice()).is_err());
|
||||||
|
let cursor = crate::RawPageCursor::try_new(vec![0xAB_u8; 8].into_boxed_slice());
|
||||||
|
assert!(cursor.is_ok());
|
||||||
|
let cursor = match cursor {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let debug = format!("{cursor:?}");
|
||||||
|
assert!(debug.contains("len"));
|
||||||
|
assert!(!debug.contains("171"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_query_preserves_caller_limit_range_direction_and_network() {
|
||||||
|
let network = match crate::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let slots = match crate::RawSlotRange::new(std::option::Option::Some(10), std::option::Option::Some(20)) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let limit = match crate::RawPageLimit::new(5_000_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let query = crate::RawTransactionQuery::new(network, slots, crate::RawSortDirection::Ascending, crate::RawPageRequest::first(limit));
|
||||||
|
assert_eq!(query.network().as_str(), "mainnet-beta");
|
||||||
|
assert_eq!(query.slots().start_inclusive(), std::option::Option::Some(10));
|
||||||
|
assert_eq!(query.page().limit().get(), 5_000_000);
|
||||||
|
assert_eq!(query.direction(), crate::RawSortDirection::Ascending);
|
||||||
|
assert!(crate::RawSlotRange::new(std::option::Option::Some(20), std::option::Option::Some(10)).is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
197
crates/ksp-store-api/unit_tests/model/raw_primitives.rs
Normal file
197
crates/ksp-store-api/unit_tests/model/raw_primitives.rs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
// file: crates/ksp-store-api/unit_tests/model/raw_primitives.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
fn code(value: &str) -> std::option::Option<crate::RawProvenanceCode> {
|
||||||
|
return match crate::RawProvenanceCode::new(value.to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||||
|
std::result::Result::Err(_) => std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp(unix_millis: u64) -> std::option::Option<crate::RawTimestamp> {
|
||||||
|
return match crate::RawTimestamp::from_unix_millis(unix_millis) {
|
||||||
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||||
|
std::result::Result::Err(_) => std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_codes_are_bounded_nonempty_and_reject_url_like_or_control_values() {
|
||||||
|
let network = crate::RawNetworkId::new("mainnet-beta".to_owned());
|
||||||
|
assert!(network.is_ok());
|
||||||
|
let format = crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned());
|
||||||
|
assert!(format.is_ok());
|
||||||
|
let maximum = "a".repeat(crate::MAX_RAW_CODE_BYTES);
|
||||||
|
assert!(crate::RawProvenanceCode::new(maximum).is_ok());
|
||||||
|
assert!(crate::RawProvenanceCode::new(std::string::String::new()).is_err());
|
||||||
|
assert!(crate::RawProvenanceCode::new("bad value".to_owned()).is_err());
|
||||||
|
assert!(crate::RawProvenanceCode::new("https://secret.example".to_owned()).is_err());
|
||||||
|
assert!(crate::RawProvenanceCode::new("bad\nvalue".to_owned()).is_err());
|
||||||
|
assert!(crate::RawProvenanceCode::new("a".repeat(crate::MAX_RAW_CODE_BYTES + 1)).is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_timestamp_accepts_documented_maximum_and_rejects_larger_values() {
|
||||||
|
let maximum = crate::RawTimestamp::from_unix_millis(crate::MAX_RAW_UNIX_MILLIS);
|
||||||
|
assert!(maximum.is_ok());
|
||||||
|
let too_large = crate::RawTimestamp::from_unix_millis(crate::MAX_RAW_UNIX_MILLIS + 1);
|
||||||
|
assert!(too_large.is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_payload_is_nonempty_versioned_bounded_and_debug_omits_bytes() {
|
||||||
|
let format_result = crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned());
|
||||||
|
assert!(format_result.is_ok());
|
||||||
|
let format = match format_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let sentinel = b"RAW_PAYLOAD_SENTINEL_NEVER_RENDER".to_vec().into_boxed_slice();
|
||||||
|
let payload_result = crate::RawPayload::try_new(format, 1, sentinel, crate::RawContentHash::new([7_u8; 32]));
|
||||||
|
assert!(payload_result.is_ok());
|
||||||
|
let payload = match payload_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(payload.bytes(), b"RAW_PAYLOAD_SENTINEL_NEVER_RENDER");
|
||||||
|
assert_eq!(payload.byte_len(), b"RAW_PAYLOAD_SENTINEL_NEVER_RENDER".len());
|
||||||
|
assert_eq!(payload.format_version(), 1);
|
||||||
|
let debug = format!("{payload:?}");
|
||||||
|
assert!(!debug.contains("RAW_PAYLOAD_SENTINEL_NEVER_RENDER"));
|
||||||
|
assert!(debug.contains("len"));
|
||||||
|
let empty_format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(crate::RawPayload::try_new(empty_format, 1, std::vec::Vec::new().into_boxed_slice(), crate::RawContentHash::new([0_u8; 32])).is_err());
|
||||||
|
let zero_version_format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(crate::RawPayload::try_new(zero_version_format, 0, vec![1_u8].into_boxed_slice(), crate::RawContentHash::new([0_u8; 32])).is_err());
|
||||||
|
let oversized_format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let oversized = vec![0_u8; crate::MAX_RAW_PAYLOAD_BYTES + 1].into_boxed_slice();
|
||||||
|
let error = crate::RawPayload::try_new(oversized_format, 1, oversized, crate::RawContentHash::new([0_u8; 32]));
|
||||||
|
assert!(error.is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn acquisition_provenance_preserves_safe_metadata_and_validates_time_and_size() {
|
||||||
|
let received_at = match timestamp(2_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let observed_at = match timestamp(1_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let provider = match code("publicnode") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let protocol = match code("yellowstone_grpc") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let method = match code("transactions") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let endpoint = match code("main") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let commitment = match code("confirmed") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let session = match code("session_1") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let filter = match code("all_transactions") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let provenance = crate::RawAcquisitionProvenance::new(provider, protocol, method, crate::RawAcquisitionOrigin::Live, received_at)
|
||||||
|
.with_endpoint_id(endpoint)
|
||||||
|
.with_commitment(commitment)
|
||||||
|
.with_capture_session_id(session)
|
||||||
|
.with_filter_id(filter)
|
||||||
|
.with_source_payload_hash(crate::RawContentHash::new([3_u8; 32]));
|
||||||
|
let provenance_result = provenance.try_with_observed_at(observed_at);
|
||||||
|
assert!(provenance_result.is_ok());
|
||||||
|
let provenance = match provenance_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let provenance_result = provenance.try_with_source_payload_size_bytes(4_096);
|
||||||
|
assert!(provenance_result.is_ok());
|
||||||
|
let provenance = match provenance_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(provenance.provider().as_str(), "publicnode");
|
||||||
|
assert_eq!(provenance.protocol().as_str(), "yellowstone_grpc");
|
||||||
|
assert_eq!(provenance.acquisition_method().as_str(), "transactions");
|
||||||
|
assert_eq!(provenance.origin(), crate::RawAcquisitionOrigin::Live);
|
||||||
|
assert_eq!(provenance.observed_at(), std::option::Option::Some(observed_at));
|
||||||
|
assert_eq!(provenance.received_at(), received_at);
|
||||||
|
assert_eq!(provenance.source_payload_size_bytes(), std::option::Option::Some(4_096));
|
||||||
|
let reversed_provider = match code("provider") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let reversed_protocol = match code("solana_websocket") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let reversed_method = match code("transactionSubscribe") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let reversed_received = match timestamp(1_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let reversed_observed = match timestamp(2_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let reversed =
|
||||||
|
crate::RawAcquisitionProvenance::new(reversed_provider, reversed_protocol, reversed_method, crate::RawAcquisitionOrigin::Live, reversed_received)
|
||||||
|
.try_with_observed_at(reversed_observed);
|
||||||
|
assert!(reversed.is_err());
|
||||||
|
let oversized_provider = match code("provider") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let oversized_protocol = match code("solana_http") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let oversized_method = match code("getTransaction") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let oversized_received = match timestamp(2_000) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let oversized_source = crate::RawAcquisitionProvenance::new(
|
||||||
|
oversized_provider,
|
||||||
|
oversized_protocol,
|
||||||
|
oversized_method,
|
||||||
|
crate::RawAcquisitionOrigin::Backfill,
|
||||||
|
oversized_received,
|
||||||
|
)
|
||||||
|
.try_with_source_payload_size_bytes(crate::MAX_RAW_SOURCE_PAYLOAD_BYTES + 1);
|
||||||
|
assert!(oversized_source.is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
60
crates/ksp-store-api/unit_tests/model/raw_retention.rs
Normal file
60
crates/ksp-store-api/unit_tests/model/raw_retention.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// file: crates/ksp-store-api/unit_tests/model/raw_retention.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
fn reference() -> std::option::Option<crate::RawTransactionReference> {
|
||||||
|
let network = match crate::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
return std::option::Option::Some(crate::RawTransactionReference::new(network, crate::RawTransactionSignature::new([3_u8; 64])));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retention_transitions_are_forward_only_and_force_rehydrate_is_separate() {
|
||||||
|
let reference = match reference() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Full, crate::RawRetentionState::Compacted).is_ok());
|
||||||
|
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Full, crate::RawRetentionState::Archived).is_ok());
|
||||||
|
assert!(
|
||||||
|
crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Compacted, crate::RawRetentionState::Archived).is_ok()
|
||||||
|
);
|
||||||
|
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Archived, crate::RawRetentionState::Purged).is_ok());
|
||||||
|
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Purged, crate::RawRetentionState::Full).is_err());
|
||||||
|
assert!(crate::RawTransactionRetentionTransition::try_new(reference, crate::RawRetentionState::Full, crate::RawRetentionState::Purged).is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tombstone_keeps_minimal_identity_without_raw_payload() {
|
||||||
|
let reference = match reference() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let tombstone = crate::RawTransactionTombstone::try_new(reference, 99, format, 1, crate::RawContentHash::new([4_u8; 32]));
|
||||||
|
assert!(tombstone.is_ok());
|
||||||
|
let tombstone = match tombstone {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(tombstone.slot(), 99);
|
||||||
|
assert_eq!(tombstone.format_version(), 1);
|
||||||
|
let second_format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(crate::RawTransactionTombstone::try_new(tombstone.reference().clone(), tombstone.slot(), second_format, 0, tombstone.content_hash()).is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retention_outcome_distinguishes_target_idempotence_from_lost_compare_and_transition() {
|
||||||
|
assert_ne!(crate::RawRetentionWriteOutcome::AlreadyAtTarget, crate::RawRetentionWriteOutcome::ExpectedStateMismatch);
|
||||||
|
assert_ne!(crate::RawRetentionWriteOutcome::Applied, crate::RawRetentionWriteOutcome::ExpectedStateMismatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
85
crates/ksp-store-api/unit_tests/model/raw_transaction.rs
Normal file
85
crates/ksp-store-api/unit_tests/model/raw_transaction.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// file: crates/ksp-store-api/unit_tests/model/raw_transaction.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
fn network() -> std::option::Option<crate::RawNetworkId> {
|
||||||
|
return match crate::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||||
|
std::result::Result::Err(_) => std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn payload() -> std::option::Option<crate::RawPayload> {
|
||||||
|
let format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
return match crate::RawPayload::try_new(
|
||||||
|
format,
|
||||||
|
1,
|
||||||
|
b"canonical transaction including logs".to_vec().into_boxed_slice(),
|
||||||
|
crate::RawContentHash::new([5_u8; 32]),
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||||
|
std::result::Result::Err(_) => std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn provenance() -> std::option::Option<crate::RawAcquisitionProvenance> {
|
||||||
|
let provider = match crate::RawProvenanceCode::new("publicnode".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let protocol = match crate::RawProvenanceCode::new("yellowstone_grpc".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let method = match crate::RawProvenanceCode::new("transactions".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let received_at = match crate::RawTimestamp::from_unix_millis(1_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
return std::option::Option::Some(crate::RawAcquisitionProvenance::new(provider, protocol, method, crate::RawAcquisitionOrigin::Backfill, received_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_transaction_identity_is_network_plus_signature_and_payload_remains_whole() {
|
||||||
|
let network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let payload = match payload() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let signature = crate::RawTransactionSignature::new([9_u8; 64]);
|
||||||
|
let reference = crate::RawTransactionReference::new(network, signature);
|
||||||
|
let transaction = crate::RawTransaction::new(reference, 42, std::option::Option::None, payload);
|
||||||
|
assert_eq!(transaction.reference().network().as_str(), "mainnet-beta");
|
||||||
|
assert_eq!(transaction.reference().signature(), signature);
|
||||||
|
assert_eq!(transaction.slot(), 42);
|
||||||
|
assert!(transaction.block_time().is_none());
|
||||||
|
assert_eq!(transaction.payload().bytes(), b"canonical transaction including logs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_observation_is_separate_from_canonical_raw_payload() {
|
||||||
|
let network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let signature = crate::RawTransactionSignature::new([11_u8; 64]);
|
||||||
|
let reference = crate::RawTransactionReference::new(network, signature);
|
||||||
|
let observation = crate::RawTransactionObservation::new(crate::RawObservationKey::new([12_u8; 32]), reference, provenance);
|
||||||
|
assert_eq!(observation.transaction().signature(), signature);
|
||||||
|
assert_eq!(observation.observation_key().as_bytes(), &[12_u8; 32]);
|
||||||
|
assert_eq!(observation.provenance().provider().as_str(), "publicnode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
190
deltas/0.3.1/pre.001-fix.001.md
Normal file
190
deltas/0.3.1/pre.001-fix.001.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.001-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.001-fix.001` — split façade Store / backend PostgreSQL et roadmap
|
||||||
|
|
||||||
|
## 1. Base
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.1-pre.001
|
||||||
|
Cargo : 0.3.1-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce correctif reste dans le couloir de conception/audit de `pre.001`. Il ne crée aucune crate Store, aucun code runtime, aucune Config exécutable, aucune migration et aucune dependency PostgreSQL.
|
||||||
|
|
||||||
|
## 2. Motif opérateur
|
||||||
|
|
||||||
|
Le brainstorming postérieur à `pre.001` affine le split Store :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1
|
||||||
|
ksp-store-api uniquement
|
||||||
|
|
||||||
|
0.3.2
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
`ksp-store-api` reste propriétaire du modèle objet/struct commun RAW/observations et des contrats/capabilities backend. `ksp-store-lib` devient la façade/runtime commune consommée par les jobs/workers/apps. Les backends sont des crates séparées importées optionnellement par `ksp-store-lib` selon les features compilées.
|
||||||
|
|
||||||
|
## 3. Graphe corrigé
|
||||||
|
|
||||||
|
Cible durable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api
|
||||||
|
^ ^
|
||||||
|
| |
|
||||||
|
ksp-store-lib ksp-store-postgres-lib
|
||||||
|
| ^
|
||||||
|
| feature postgres|
|
||||||
|
+-----------------+
|
||||||
|
|
||||||
|
futur ksp-store-mysql-lib -> ksp-store-api
|
||||||
|
ksp-store-lib[mysql] -> futur ksp-store-mysql-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Règles :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-postgres-lib -X-> ksp-store-lib
|
||||||
|
backend alternatif -X-> ksp-store-lib
|
||||||
|
consumer métier -X-> crate backend directe
|
||||||
|
consumer métier -> ksp-store-lib uniquement côté Store
|
||||||
|
```
|
||||||
|
|
||||||
|
`ksp-store-lib` réexportera la surface commune de `ksp-store-api` nécessaire aux consumers afin qu'un worker/job n'ait pas à dépendre séparément de l'API et du backend.
|
||||||
|
|
||||||
|
## 4. Features et sélection runtime
|
||||||
|
|
||||||
|
`0.3.2` introduira :
|
||||||
|
|
||||||
|
```text
|
||||||
|
default = [postgres]
|
||||||
|
postgres -> dep:ksp-store-postgres-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Les futures features backend pourront être compilées simultanément. La feature contrôle la **disponibilité dans le binaire** ; Config contrôle le **backend actif**.
|
||||||
|
|
||||||
|
Un appel avec une Config demandant un backend KSP connu mais non présent dans les features compilées doit être rejeté explicitement, avec un code stable de type :
|
||||||
|
|
||||||
|
```text
|
||||||
|
STORE_BACKEND_NOT_COMPILED
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun fallback silencieux vers PostgreSQL n'est permis.
|
||||||
|
|
||||||
|
## 5. Config, URI, secrets et `.env`
|
||||||
|
|
||||||
|
La responsabilité reste conforme aux règles Config KSP :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-config-lib
|
||||||
|
= documents + schemas + placeholders + .env + provenance/sensitivity
|
||||||
|
|
||||||
|
ksp-store-lib
|
||||||
|
= settings runtime + backend dispatch
|
||||||
|
|
||||||
|
ksp-store-*-lib
|
||||||
|
= connexion/persistence backend
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour un backend disposant d'une URI/DSN naturelle, la Config utilisera une URI plutôt qu'une décomposition artificielle, sauf besoin backend réellement justifié. Des options backend-specific peuvent être ajoutées sous forme typée lorsque nécessaire.
|
||||||
|
|
||||||
|
Invariants :
|
||||||
|
|
||||||
|
- une URI pouvant contenir user/password/token est `Secret` dans son ensemble ;
|
||||||
|
- les credentials proviennent de placeholders `KSP_SECRET_*` résolus par `ksp-config-lib` ;
|
||||||
|
- toute nouvelle variable apparaît commentée dans `.env.example` lors de sa première utilisation réelle ;
|
||||||
|
- le vrai `.env` et les vrais URI/credentials ne sont jamais versionnés ;
|
||||||
|
- Store et backend ne lisent jamais directement `.env` ni les variables KSP/KSPB ;
|
||||||
|
- URI/DSN/password/token restent absents des `Debug`, logs, erreurs et health ;
|
||||||
|
- l'absence de feature backend est détectée avant toute connexion ou fallback driver implicite.
|
||||||
|
|
||||||
|
La forme exacte de `std.store`, du nom des variables et des settings runtime est reportée à `0.3.2`.
|
||||||
|
|
||||||
|
## 6. ROADMAP corrigé
|
||||||
|
|
||||||
|
La séquence devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1 ksp-store-api RAW/observations
|
||||||
|
0.3.2 ksp-store-lib + ksp-store-postgres-lib
|
||||||
|
0.3.3 wires Interface nécessaires aux acquisitions/normalisation future
|
||||||
|
0.3.4 ksp-job-api + premier backfill historique
|
||||||
|
0.3.5 application backfill/inspection RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
La ligne `0.3.1` passe en cours et n'annonce plus de persistence PostgreSQL dans cette release.
|
||||||
|
|
||||||
|
## 7. Version Cargo
|
||||||
|
|
||||||
|
Ce correctif est strictement documentaire. Conformément à `VER-ID-008`, `workspace.package.version` reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
ROADMAP.md
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichier ajouté
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.1/pre.001-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Validations exécutées
|
||||||
|
|
||||||
|
Dans l'environnement de génération du fix :
|
||||||
|
|
||||||
|
```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 (184 table(s), 122 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo` n'est pas disponible dans cet environnement. Comme le fix ne modifie aucun fichier de code/build/runtime/config exécutable, aucune version Cargo n'est changée ; le gate opérateur reste néanmoins :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun test Store ciblé n'existe encore et aucun gate PostgreSQL n'est requis dans ce fix documentaire.
|
||||||
|
|
||||||
|
## 12. Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question ne bloque `0.3.1-pre.002`.
|
||||||
|
|
||||||
|
Restent volontairement reportés à `0.3.2` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
forme exacte de StoreSettings
|
||||||
|
shape du document std.store
|
||||||
|
nom exact des variables KSP_SECRET_* Store
|
||||||
|
URI/DSN PostgreSQL exact accepté
|
||||||
|
pool/TLS/timeouts
|
||||||
|
migrations/schema/indexes
|
||||||
|
mapping PostgreSQL des modèles API
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces détails ne changent pas les frontières déjà fixées par le présent correctif.
|
||||||
194
deltas/0.3.1/pre.001-fix.002.md
Normal file
194
deltas/0.3.1/pre.001-fix.002.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.001-fix.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.001-fix.002` — taxonomie N1, STRUCTURAL et lifecycle RAW
|
||||||
|
|
||||||
|
## 1. Base
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.1-pre.001-fix.001
|
||||||
|
Cargo : 0.3.1-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur post-`fix.001` fourni est vert pour `cargo fmt --all`, audits Rust/Markdown, `cargo check --workspace` et `cargo clippy --workspace --all-targets`. L'opérateur indique également que la validation des tests est OK.
|
||||||
|
|
||||||
|
Ce correctif reste strictement documentaire : aucune crate, API Rust, Config, dependency, migration ou runtime Store n'est créé.
|
||||||
|
|
||||||
|
## 2. Motif
|
||||||
|
|
||||||
|
Le brainstorming avant `pre.002` révèle que la taxonomie précédente était encore trop proche d'un pipeline linéaire et traitait à tort les logs transactionnels et `logsSubscribe` comme une même famille persistante.
|
||||||
|
|
||||||
|
Le correctif doit aussi préserver deux besoins futurs avant qu'une API publique ne les rende difficiles à ajouter :
|
||||||
|
|
||||||
|
```text
|
||||||
|
frontière claire ksp-interface-lib / ksp-store-api
|
||||||
|
processing versionné + retention/compaction/archive/purge du RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Taxonomie N1 corrigée
|
||||||
|
|
||||||
|
Règle d'admission : deux réponses HTTP/WS/gRPC/provider convergent vers le même modèle N1 seulement si elles satisfont intégralement la même sémantique KSP sans perte.
|
||||||
|
|
||||||
|
Classification courante :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction + observation
|
||||||
|
= N1 persistant certain
|
||||||
|
|
||||||
|
transaction logMessages
|
||||||
|
= partie de RawTransaction ; extraction future en N2 STRUCTURAL
|
||||||
|
|
||||||
|
RawAccountState + observation
|
||||||
|
= modèle N1 à prévoir après matrice de compatibilité
|
||||||
|
|
||||||
|
TransactionStatusObservation
|
||||||
|
= modèle/observation à explorer et prévoir si la sémantique converge
|
||||||
|
|
||||||
|
logsSubscribe / slot / vote
|
||||||
|
= event-only candidats ; pas de persistence Store par défaut
|
||||||
|
|
||||||
|
RawBlock
|
||||||
|
= IDEA seulement ; getBlock sert d'abord de conteneur d'acquisition de transactions
|
||||||
|
|
||||||
|
Yellowstone Entry
|
||||||
|
= examiné et non retenu actuellement
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. N2 renommé STRUCTURAL
|
||||||
|
|
||||||
|
La chaîne historique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RAW -> CORE -> DECODE -> SPECIALIZED
|
||||||
|
```
|
||||||
|
|
||||||
|
est remplacée comme vocabulaire cible par :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RAW -> STRUCTURAL -> DECODED -> DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
N2 décrit une décomposition structurelle, pas un « Core » métier. Le premier cas certain est `RawTransaction -> STRUCTURAL` avec transaction/message, account refs, instructions top-level, CPI/inner instructions, logs/meta/balances/return data.
|
||||||
|
|
||||||
|
Toutes les familles N1 ne sont pas obligées de passer par N2.
|
||||||
|
|
||||||
|
## 5. Frontière Interface / Store
|
||||||
|
|
||||||
|
La règle fixée est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
persistant / replayable / queryable
|
||||||
|
-> ksp-store-api
|
||||||
|
|
||||||
|
observation durable d'un fait Store
|
||||||
|
-> ksp-store-api
|
||||||
|
|
||||||
|
event-only passif inter-composants
|
||||||
|
-> ksp-interface-lib de préférence
|
||||||
|
|
||||||
|
format canonique « donnée persistée disponible »
|
||||||
|
-> ksp-store-api, publication par worker/runtime après commit
|
||||||
|
|
||||||
|
shape HTTP/WS/gRPC/provider
|
||||||
|
-> Transport seulement
|
||||||
|
```
|
||||||
|
|
||||||
|
Un modèle event-only n'implique jamais automatiquement une capability Store. Les conversions restent explicites lorsque les sémantiques diffèrent.
|
||||||
|
|
||||||
|
## 6. Processing et lifecycle RAW
|
||||||
|
|
||||||
|
L'audit des documents kbot2 embarqués dans l'archive kbot3 retrouve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
full -> compacted -> archived -> purged
|
||||||
|
```
|
||||||
|
|
||||||
|
et un replay piloté par une identité de processing incluant stage, processor/version et input hash.
|
||||||
|
|
||||||
|
KSP reprend les concepts en les redessinant :
|
||||||
|
|
||||||
|
- aucun `processed: bool` ne constitue la preuve durable unique ;
|
||||||
|
- le futur processing ledger doit être version-aware et input-hash-aware ;
|
||||||
|
- la policy d'éligibilité à la rétention appartient à un worker/job/maintenance layer, pas au Store ;
|
||||||
|
- le Store applique uniquement une transition logique/atomique demandée ;
|
||||||
|
- un tombstone minimal reste après purge afin qu'un backfill normal n'acquière pas de nouveau la même transaction ;
|
||||||
|
- une réhydratation après purge exige un mode explicitement forcé ;
|
||||||
|
- les policies de rétention peuvent différer selon la famille RAW.
|
||||||
|
|
||||||
|
Le payload RAW n'a donc pas vocation à rester éternellement en stockage chaud lorsque les couches dérivées et la policy active permettent sa compaction/archive/purge.
|
||||||
|
|
||||||
|
## 7. ROADMAP
|
||||||
|
|
||||||
|
`ROADMAP.md` est corrigé pour :
|
||||||
|
|
||||||
|
- remplacer CORE par STRUCTURAL dans la progression future ;
|
||||||
|
- rappeler que les niveaux ne sont pas obligatoires pour toutes les familles ;
|
||||||
|
- préciser `0.3.1` avec admission cross-source, events non persistés et lifecycle/tombstone ;
|
||||||
|
- ajouter un bloc TODO/IDEAS N1/processing/rétention ;
|
||||||
|
- conserver `0.3.2 = ksp-store-lib + ksp-store-postgres-lib` inchangé.
|
||||||
|
|
||||||
|
## 8. Version Cargo
|
||||||
|
|
||||||
|
Fix documentaire uniquement. Conformément au workflow, `workspace.package.version` reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
ROADMAP.md
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichier ajouté
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.1/pre.001-fix.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Validations exécutées
|
||||||
|
|
||||||
|
Dans l'environnement de génération du fix :
|
||||||
|
|
||||||
|
```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 (184 table(s), 123 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo` n'est pas disponible dans cet environnement. Le fix est strictement documentaire et la base opérateur post-`fix.001` a déjà passé `cargo fmt`, `cargo check` et Clippy. Après application du présent fix, le gate opérateur reste :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun code n'est modifié et aucun test Store ciblé n'existe encore.
|
||||||
|
|
||||||
|
## 13. Suite
|
||||||
|
|
||||||
|
`pre.002` peut ensuite créer le scaffold `ksp-store-api` avec une taxonomie désormais suffisamment contrainte pour éviter de figer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
un faux RawLog persistant
|
||||||
|
un pipeline N1->N2 obligatoire
|
||||||
|
un processed bool irréversible
|
||||||
|
une retention éternelle du RAW
|
||||||
|
une dépendance implicite entre event runtime et Store
|
||||||
|
```
|
||||||
296
deltas/0.3.1/pre.001.md
Normal file
296
deltas/0.3.1/pre.001.md
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.001` — audit Store API, N1 RAW et split PostgreSQL
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Base directe attendue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14
|
||||||
|
workspace.package.version = 0.2.14
|
||||||
|
```
|
||||||
|
|
||||||
|
Sources obligatoires réellement disponibles à l'ouverture :
|
||||||
|
|
||||||
|
```text
|
||||||
|
archive opérateur khadhroony-solana-project-v0.2.14.zip
|
||||||
|
archive historique khadhroony-bot3_v0.5.3-pre.005-fix010.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
La metadata Git n'est pas incluse dans l'archive opérateur ; le tag `v0.2.14` ne peut donc pas être interrogé localement. La version Cargo, `deltas/0.2.14/rel.001.md`, le prompt 020 et la surface Program API publiée concordent avec la base stable attendue.
|
||||||
|
|
||||||
|
Commit attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.1-pre.001
|
||||||
|
```
|
||||||
|
|
||||||
|
Archive overlay attendue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-general-0.3.1-pre.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Ouvrir `0.3.1` uniquement par le gate prévu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
lecture règles + architecture
|
||||||
|
audit base KSP stable
|
||||||
|
audit historique kbot3
|
||||||
|
audit PostgreSQL/driver actuel
|
||||||
|
brainstorming N1 RAW
|
||||||
|
split ksp-store-api / ksp-store-lib
|
||||||
|
ownership
|
||||||
|
API candidate
|
||||||
|
backend extension model
|
||||||
|
threat model
|
||||||
|
stratégie de tests
|
||||||
|
sizing et prévision souple recalibrée
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune crate Store fonctionnelle n'est ajoutée dans cette livraison.
|
||||||
|
|
||||||
|
## 3. Décision de scope majeure
|
||||||
|
|
||||||
|
Le scope du prompt initial est volontairement réduit et scindé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1 = ksp-store-api uniquement
|
||||||
|
0.3.2 = ksp-store-lib PostgreSQL via tokio-postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
`ksp-store-api` possédera le modèle objet/struct commun et les opérations backend-agnostic. Les représentations internes, rows, requêtes, migrations, pools et transactions SQL seront invisibles aux consumers.
|
||||||
|
|
||||||
|
Une future implémentation alternative, par exemple `ksp-store-mysql-lib`, dépendra directement de `ksp-store-api` et non de `ksp-store-lib`.
|
||||||
|
|
||||||
|
La composition/configuration choisira l'implémentation liée au host puis fournira la même façade `Store` aux consumers.
|
||||||
|
|
||||||
|
Le `ROADMAP.md` stable contient encore l'ancienne prévision combinée. Il n'est pas modifié dans cette tranche et sera réconcilié dans la lane de préparation de publication conformément au workflow KSP.
|
||||||
|
|
||||||
|
## 4. N1 RAW retenu
|
||||||
|
|
||||||
|
Le niveau N1/D1 est défini comme acquisition persistée/replayable.
|
||||||
|
|
||||||
|
Surface initiale `0.3.1` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction
|
||||||
|
RawTransactionObservation
|
||||||
|
RawLog
|
||||||
|
RawLogObservation
|
||||||
|
RawPayload/provenance/reference/outcome/page communs
|
||||||
|
```
|
||||||
|
|
||||||
|
Familles prévues mais reportées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawAccount
|
||||||
|
RawBlock
|
||||||
|
RawSlot/updates si besoin durable distinct
|
||||||
|
autres acquisitions justifiées ultérieurement
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTP, WebSocket et gRPC sont des moyens d'acquisition/provenance, pas des familles RAW séparées.
|
||||||
|
|
||||||
|
Le RAW transaction devra pouvoir être transformé ultérieurement en normalisation Solana générique (actuellement nommée CORE/D2), puis décomposé en instructions top-level et CPI indépendantes afin qu'un decoder absent/Unsupported n'empêche pas le traitement du reste.
|
||||||
|
|
||||||
|
## 5. API/backend model
|
||||||
|
|
||||||
|
Décisions candidates :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Store = façade consumer commune
|
||||||
|
StoreBackend = contrat d'implémentation externe object-safe
|
||||||
|
capabilities = StoreHealth + RawTransactionStore + RawLogStore
|
||||||
|
async = StoreFuture<'a, T> boxed KSP-owned par défaut
|
||||||
|
atomicité = opérations métier persist_*_acquisition
|
||||||
|
transaction SQL handle public = interdit
|
||||||
|
page = 100 défaut / 500 max / cursor opaque borné
|
||||||
|
```
|
||||||
|
|
||||||
|
Sémantique d'idempotence :
|
||||||
|
|
||||||
|
```text
|
||||||
|
nouvelle clé -> Inserted
|
||||||
|
même clé + même contenu -> AlreadyPresent
|
||||||
|
même clé + contenu divergent -> Error Conflict
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `has_*` n'est requis avant write.
|
||||||
|
|
||||||
|
## 6. Héritage kbot3
|
||||||
|
|
||||||
|
L'archive historique a été extraite et les surfaces Store prioritaires réellement relues.
|
||||||
|
|
||||||
|
Le Store historique comporte 16 tables N1-N3, 240 ressources SQL atomiques et 79 index attendus. La séparation façade/PostgreSQL, les observations, la pagination bornée et l'idempotence sont réutilisables conceptuellement, mais les contrats N2/N3 et la représentation physique ne sont pas repris.
|
||||||
|
|
||||||
|
Résumé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
REPRENDRE séparation façade/backend, health portable, repository capabilities, pagination bornée, observations, idempotence
|
||||||
|
REDESSINER StoreOpenOptions, raw DTO/entities, write semantics, diagnostics, Config Store
|
||||||
|
REPORTER schema/migrations/indexes, raw table physique, replay processing, CORE, DECODE, materialization, processing ledger
|
||||||
|
REJETER maintenance destructive publique, JSON backend options opaque, PK SQL comme identité API, monolithe N1/N2/N3
|
||||||
|
```
|
||||||
|
|
||||||
|
La matrice détaillée est dans `docs/plans/022-V0_3_1_STORE_RAW_PLAN.md`.
|
||||||
|
|
||||||
|
## 7. Audit externe
|
||||||
|
|
||||||
|
Audit actuel au 28 août 2026 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgreSQL 18.6 — stable courant, 13 août 2026
|
||||||
|
tokio-postgres 0.7.18 — stable courant, 12 juin 2026
|
||||||
|
Rust 2024, MSRV 1.85
|
||||||
|
prepared statements / transactions / COPY / async pipelined supportés
|
||||||
|
```
|
||||||
|
|
||||||
|
Décision opérateur : `tokio-postgres` sera le driver interne du backend PostgreSQL de référence dans `0.3.2`.
|
||||||
|
|
||||||
|
Aucune dependency PostgreSQL n'est ajoutée dans `0.3.1`.
|
||||||
|
|
||||||
|
## 8. Dependency graph `0.3.1`
|
||||||
|
|
||||||
|
Cible :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
`ksp-interface-lib`, serde, chrono, tokio et toute crate DB restent absents tant qu'un usage public réel ne les justifie pas.
|
||||||
|
|
||||||
|
## 9. Sizing recalibré
|
||||||
|
|
||||||
|
Prévision active :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.001 audit/design/split
|
||||||
|
pre.002 scaffold ksp-store-api
|
||||||
|
pre.003 primitives RAW + transaction
|
||||||
|
pre.004 raw logs + extensibilité N1
|
||||||
|
pre.005 backend contract + Store facade + external canary
|
||||||
|
pre.006 queries/pagination/outcomes/notification reference
|
||||||
|
pre.007 adversarial/API hardening + completeness
|
||||||
|
pre.008 gate technique final
|
||||||
|
pre.009 réconciliation documentaire
|
||||||
|
pre.010 préparation de publication minimale
|
||||||
|
rel.001 publication stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Le futur `0.3.2` ouvre `ksp-store-lib` PostgreSQL/`tokio-postgres`.
|
||||||
|
|
||||||
|
## 10. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
deltas/0.3.1/pre.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Version Cargo
|
||||||
|
|
||||||
|
La prerelease non-fix synchronise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.3.1-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune crate Store n'existe encore ; le changement Cargo identifie uniquement le gate `pre.001` conformément au workflow.
|
||||||
|
|
||||||
|
## 14. Validations de baseline
|
||||||
|
|
||||||
|
Le journal opérateur fourni à l'ouverture sur `v0.2.14` montre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 175 tables / 128 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Les smokes live opt-in restent ignorés comme prévu par leurs contrats.
|
||||||
|
|
||||||
|
Cette preuve de base ne remplace pas le gate après application de `pre.001`.
|
||||||
|
|
||||||
|
## 15. Validations après application
|
||||||
|
|
||||||
|
Dans l'environnement de génération du présent overlay, les contrôles statiques suivants ont été réellement exécutés après modification :
|
||||||
|
|
||||||
|
```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 (184 tables, 121 files)
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo` n'est pas installé dans l'environnement de génération utilisé pour préparer l'archive. `cargo fmt --all`, `cargo check --workspace` et `cargo clippy --workspace --all-targets` n'ont donc pas été rejoués ici et ne sont pas déclarés PASS. Le baseline opérateur `v0.2.14` fourni reste vert, mais ne remplace pas le gate opérateur après application.
|
||||||
|
|
||||||
|
## 16. Validations non requises dans cette tranche
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo test -p ksp-store-api crate encore absente
|
||||||
|
cargo tree -p ksp-store-api crate encore absente
|
||||||
|
PostgreSQL live backend reporté à 0.3.2
|
||||||
|
migration/schema tests backend reporté à 0.3.2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 17. Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question architecturale ne bloque `pre.002`.
|
||||||
|
|
||||||
|
Restent volontairement à stabiliser par les tranches de code :
|
||||||
|
|
||||||
|
```text
|
||||||
|
nom exact des wrappers d'identité RAW
|
||||||
|
format_id/version concret du premier RawPayload transaction
|
||||||
|
bornes finales payload/provenance
|
||||||
|
identité déterministe exacte d'un RawLog
|
||||||
|
forme finale StoreFuture/backend traits
|
||||||
|
référence notification introduite en pre.006 ou reportée si insuffisamment stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces questions ne remettent pas en cause :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Store API seule en 0.3.1
|
||||||
|
PostgreSQL/tokio-postgres en 0.3.2
|
||||||
|
modèle public commun backend-agnostic
|
||||||
|
transaction + logs comme premières familles N1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 18. Application et validation opérateur
|
||||||
|
|
||||||
|
Après application de l'overlay :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun scaffold Store, SQL, migration ou changement Config ne doit être ajouté à ce delta.
|
||||||
105
deltas/0.3.1/pre.002.md
Normal file
105
deltas/0.3.1/pre.002.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.002`
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.001-fix.003
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Créer uniquement le scaffold de `ksp-store-api` et matérialiser la frontière de dépendances validée par `pre.001`, sans introduire encore les primitives RAW de `pre.003` ni aucun backend/runtime Store.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/Cargo.toml
|
||||||
|
crates/ksp-store-api/src/capability.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/public_api.rs
|
||||||
|
deltas/0.3.1/pre.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Le workspace passe à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.2
|
||||||
|
```
|
||||||
|
|
||||||
|
et enregistre `crates/ksp-store-api` comme membre.
|
||||||
|
|
||||||
|
## Décisions matérialisées
|
||||||
|
|
||||||
|
- `ksp-store-api` dépend uniquement de `ksp-core-lib`.
|
||||||
|
- les modules `model` et `capability` sont privés et distincts ; aucune arborescence de module interne n'est une API publique.
|
||||||
|
- la façade crate-root réexporte seulement les primitives Core réellement communes nécessaires aux futurs contrats Store : `Error`, `ErrorCode`, `ErrorContext`, `Pubkey` et `Result`.
|
||||||
|
- `0.3.1` reste N1 RAW-only ; STRUCTURAL reste une couche ultérieure distincte et aucun type N2 n'est créé dans cette tranche.
|
||||||
|
- aucune dépendance Interface, Transport, Program, Materializer, Config, Logging, async runtime, serde, codec ou backend n'est introduite.
|
||||||
|
- aucun marker trait ou enum Store artificiel n'est créé avant qu'une opération/modèle réel ne le justifie.
|
||||||
|
- `RawTransaction`, provenance, payload, observation et idempotence restent réservés à `pre.003`.
|
||||||
|
|
||||||
|
## Tests ajoutés
|
||||||
|
|
||||||
|
`dependency_boundary.rs` vérifie :
|
||||||
|
|
||||||
|
- la dépendance runtime exacte Core-only ;
|
||||||
|
- l'absence des crates backend/runtime/transport/program/config ;
|
||||||
|
- l'absence de chemins SQL/PostgreSQL/async/env/filesystem dans la façade.
|
||||||
|
|
||||||
|
`public_api.rs` vérifie :
|
||||||
|
|
||||||
|
- la consommation externe des réexports Core depuis le crate-root ;
|
||||||
|
- l'absence de `pub mod` ;
|
||||||
|
- l'absence de concepts backend et de modèles RAW introduits prématurément.
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
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/0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations non exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
`cargo`/`rustfmt` ne sont pas disponibles 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
|
||||||
|
RawTransaction concret
|
||||||
|
RawAccountState
|
||||||
|
TransactionStatusObservation
|
||||||
|
capabilities read/write
|
||||||
|
queries/outcomes
|
||||||
|
retention/tombstone concret
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
PostgreSQL/tokio-postgres
|
||||||
|
Config std.store
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3/N4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
`0.3.1-pre.003` introduit les primitives RAW communes puis `RawTransaction` et son observation, sans faire sortir les logs inclus de la transaction vers un modèle `RawLog` distinct.
|
||||||
93
deltas/0.3.1/pre.003-fix.001.md
Normal file
93
deltas/0.3.1/pre.003-fix.001.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.003-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.003-fix.001` — conformité Clippy `implicit_return`
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.003` confirme :
|
||||||
|
|
||||||
|
- `cargo fmt --all` propre ;
|
||||||
|
- audits Rust/Markdown propres ;
|
||||||
|
- `cargo check --workspace` propre ;
|
||||||
|
- `cargo test -p ksp-store-api` propre : 6 unitaires, 2 dependency-boundary, 3 public-api ;
|
||||||
|
- `cargo clippy --workspace --all-targets` échoue uniquement sur `clippy::implicit_return` dans `valid_raw_code`.
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Corriger exclusivement le lint Clippy signalé dans la closure passée à `Iterator::all` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/src/model/raw_primitives.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
Le workspace impose `-D clippy::implicit-return`. La closure exprime donc explicitement son `return` sans changer la condition de validation des logical RAW codes.
|
||||||
|
|
||||||
|
## Changement fonctionnel
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
La condition reste exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ASCII alphanumeric
|
||||||
|
ou `_`, `-`, `.`, `:`
|
||||||
|
```
|
||||||
|
|
||||||
|
Les bornes, erreurs, API publique, modèles RAW, provenance, payload, dépendances et tests restent inchangés.
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
Correctif ciblé sans nouvelle tranche fonctionnelle. Conformément au workflow de versioning KSP, `workspace.package.version` devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.3-fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers inclus dans le payload
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-store-api/src/model/raw_primitives.rs
|
||||||
|
deltas/0.3.1/pre.003-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` est inclus pour rendre le fix directement applicable avec le workflow d'overlay KSP et porte l'identité exacte du fix :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = "0.3.1-pre.3-fix.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Le seul fichier de code modifié est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/src/model/raw_primitives.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichier ajouté
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.1/pre.003-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
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/0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement de génération. Le gate opérateur doit donc rejouer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
204
deltas/0.3.1/pre.003.md
Normal file
204
deltas/0.3.1/pre.003.md
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.003`
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.002
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.002` est fourni vert : audits Rust/Markdown, `cargo check --workspace`, Clippy workspace et `cargo test -p ksp-store-api` passent.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Matérialiser les primitives N1 RAW backend-agnostic puis le premier modèle persistant réel `RawTransaction` avec observation d'acquisition séparée, sans introduire encore les matrices HTTP/WS/gRPC de `pre.004`, les capabilities de `pre.005` ni aucun backend/runtime Store.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
Le workspace passe à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Surface ajoutée
|
||||||
|
|
||||||
|
Primitives communes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawNetworkId
|
||||||
|
RawTransactionSignature
|
||||||
|
RawTransactionReference
|
||||||
|
RawFormatId
|
||||||
|
RawContentHash
|
||||||
|
RawObservationKey
|
||||||
|
RawTimestamp
|
||||||
|
RawAcquisitionOrigin
|
||||||
|
RawProvenanceCode
|
||||||
|
RawAcquisitionProvenance
|
||||||
|
RawPayload
|
||||||
|
```
|
||||||
|
|
||||||
|
Première famille N1 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction
|
||||||
|
RawTransactionObservation
|
||||||
|
```
|
||||||
|
|
||||||
|
Codes d'erreur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
store_api.raw_model_invalid
|
||||||
|
store_api.raw_payload_invalid
|
||||||
|
store_api.raw_provenance_invalid
|
||||||
|
```
|
||||||
|
|
||||||
|
## Invariants RAW
|
||||||
|
|
||||||
|
`RawPayload` contient uniquement un format de persistence KSP déjà source-independent :
|
||||||
|
|
||||||
|
```text
|
||||||
|
format_id
|
||||||
|
format_version > 0
|
||||||
|
bytes non vides
|
||||||
|
content_hash [u8; 32]
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Store API ne choisit ni codec ni algorithme de conversion Transport. Le producer/converter du futur format canonique doit fournir des bytes complets et leur digest déterministe.
|
||||||
|
|
||||||
|
Bornes Store-owned :
|
||||||
|
|
||||||
|
```text
|
||||||
|
code logique <= 128 bytes
|
||||||
|
payload RAW canonique <= 16 MiB
|
||||||
|
source payload size meta <= 64 MiB
|
||||||
|
Unix timestamp <= 9999-12-31T23:59:59.999Z
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces valeurs sont des admission guards internes et ne prétendent pas définir des maxima Solana.
|
||||||
|
|
||||||
|
`RawPayload` et `RawTransaction` ne sont volontairement pas `Clone`, afin d'éviter de rendre triviale la copie de gros documents RAW.
|
||||||
|
|
||||||
|
## Identité transactionnelle
|
||||||
|
|
||||||
|
La référence durable est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionReference
|
||||||
|
network
|
||||||
|
signature [u8; 64]
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune PK SQL/backend ne traverse l'API. `RawTransaction` ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
slot: u64
|
||||||
|
block_time: Option<RawTimestamp>
|
||||||
|
RawPayload
|
||||||
|
```
|
||||||
|
|
||||||
|
Les logs contenus dans la transaction restent à l'intérieur du payload canonique N1. Aucun modèle `RawLog` persistant ni type STRUCTURAL n'est créé.
|
||||||
|
|
||||||
|
## Observation et provenance
|
||||||
|
|
||||||
|
Une observation réussie reste distincte du RAW :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionObservation
|
||||||
|
observation_key [u8; 32]
|
||||||
|
transaction reference
|
||||||
|
provenance
|
||||||
|
```
|
||||||
|
|
||||||
|
La provenance peut représenter avec des logical codes sûrs :
|
||||||
|
|
||||||
|
```text
|
||||||
|
provider
|
||||||
|
protocol
|
||||||
|
acquisition method
|
||||||
|
origin live/backfill/import/replay/repair
|
||||||
|
endpoint id optionnel
|
||||||
|
commitment optionnel
|
||||||
|
capture/session id optionnel
|
||||||
|
filter id optionnel
|
||||||
|
observed_at optionnel
|
||||||
|
received_at
|
||||||
|
source payload size/hash optionnels
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle n'accepte aucun payload source et ses logical codes refusent notamment les espaces, contrôles et formes URL contenant `/`. Le contrat reste explicitement non-secret : un caller ne doit jamais placer une credential dans un logical code.
|
||||||
|
|
||||||
|
`observed_at`, lorsqu'il existe, ne peut pas être postérieur à `received_at`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Tests unitaires ajoutés :
|
||||||
|
|
||||||
|
- bornes/validation des codes ;
|
||||||
|
- borne de timestamp ;
|
||||||
|
- payload non vide/versionné/borné ;
|
||||||
|
- `Debug` du payload sans bytes ;
|
||||||
|
- provenance et ordre temporel ;
|
||||||
|
- identité `network + signature` ;
|
||||||
|
- séparation `RawTransaction` / `RawTransactionObservation`.
|
||||||
|
|
||||||
|
Canaris d'intégration mis à jour :
|
||||||
|
|
||||||
|
- surface crate-root des nouveaux modèles ;
|
||||||
|
- dépendance runtime exacte `ksp-core-lib` ;
|
||||||
|
- absence de backend, SQL, serde, codec, async runtime, Transport et Program ;
|
||||||
|
- absence de `RawLog` et de type STRUCTURAL dans la production `pre.003`.
|
||||||
|
|
||||||
|
## Documentation mise à jour
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Ils figent les primitives et bornes réellement matérialisées par cette tranche sans avancer les matrices cross-source de `pre.004`.
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
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/0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
conversion HTTP/WS/gRPC -> RawTransaction canonique
|
||||||
|
RawAccountState
|
||||||
|
TransactionStatusObservation
|
||||||
|
events logs/slot/vote
|
||||||
|
capabilities read/write
|
||||||
|
queries/outcomes
|
||||||
|
retention/tombstone concret
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
PostgreSQL/tokio-postgres
|
||||||
|
Config std.store
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3/N4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
`0.3.1-pre.004` audite les formes HTTP/WS/gRPC réelles afin de figer la matrice d'admission cross-source et d'introduire seulement les familles N1 supplémentaires dont la sémantique commune est effectivement démontrée, en priorité `RawAccountState`/observation et `TransactionStatusObservation`.
|
||||||
252
deltas/0.3.1/pre.004.md
Normal file
252
deltas/0.3.1/pre.004.md
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.004` — admission cross-source + account state N1
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.3-fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.003-fix.001` est fourni vert : `cargo fmt --all`, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace et `cargo test -p ksp-store-api` passent.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Auditer les formes HTTP/WS/gRPC déjà possédées par `ksp-onchain-transport-lib` avant d'ajouter une nouvelle famille N1, puis matérialiser uniquement le modèle dont la sémantique commune est démontrée.
|
||||||
|
|
||||||
|
La tranche :
|
||||||
|
|
||||||
|
- admet `RawAccountState`/`RawAccountObservation` avec bytes complets + slot durable ;
|
||||||
|
- sépare les enrichissements Yellowstone de l'état canonique commun ;
|
||||||
|
- refuse conceptuellement les réponses account partielles/parsées comme états persistants ;
|
||||||
|
- diffère `TransactionStatusObservation` parce que snapshot HTTP, transition WS et update Yellowstone ne représentent pas encore le même fait ;
|
||||||
|
- ferme la classification logs/slot/vote/block/Entry sans créer de modèles Store prématurés.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
Le workspace passe à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audit account cross-source
|
||||||
|
|
||||||
|
Surfaces KSP relues :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP
|
||||||
|
getAccountInfo
|
||||||
|
getMultipleAccounts
|
||||||
|
getProgramAccounts
|
||||||
|
|
||||||
|
WebSocket
|
||||||
|
accountSubscribe
|
||||||
|
programSubscribe
|
||||||
|
Helius standard account/program reuse
|
||||||
|
|
||||||
|
Yellowstone gRPC
|
||||||
|
Account / AccountInfo
|
||||||
|
```
|
||||||
|
|
||||||
|
Champs communs retenus pour un état complet :
|
||||||
|
|
||||||
|
```text
|
||||||
|
network
|
||||||
|
pubkey
|
||||||
|
slot
|
||||||
|
lamports
|
||||||
|
owner
|
||||||
|
executable
|
||||||
|
rent_epoch
|
||||||
|
complete account data bytes
|
||||||
|
canonical state hash
|
||||||
|
```
|
||||||
|
|
||||||
|
Règles d'admission :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getAccountInfo/getMultipleAccounts
|
||||||
|
-> admissibles avec account non-null + bytes complets
|
||||||
|
|
||||||
|
getProgramAccounts/programSubscribe
|
||||||
|
-> slot/context obligatoire
|
||||||
|
|
||||||
|
accountSubscribe
|
||||||
|
-> admissible avec bytes complets
|
||||||
|
|
||||||
|
Yellowstone Account
|
||||||
|
-> admissible seulement sans accounts_data_slice tronquant les bytes
|
||||||
|
|
||||||
|
jsonParsed/dataSlice/bare program notification
|
||||||
|
-X-> RawAccountState persistant
|
||||||
|
```
|
||||||
|
|
||||||
|
`space` n'est pas une vérité stockée séparément lorsque les bytes complets sont présents : `data.len()` est déterministe.
|
||||||
|
|
||||||
|
## Modèles ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawAccountStateReference
|
||||||
|
network
|
||||||
|
pubkey
|
||||||
|
slot
|
||||||
|
state_hash
|
||||||
|
|
||||||
|
RawAccountState
|
||||||
|
reference
|
||||||
|
lamports
|
||||||
|
owner
|
||||||
|
executable
|
||||||
|
rent_epoch
|
||||||
|
complete data bytes
|
||||||
|
|
||||||
|
RawAccountObservation
|
||||||
|
observation_key
|
||||||
|
account reference
|
||||||
|
provenance
|
||||||
|
optional write_version
|
||||||
|
optional transaction_signature
|
||||||
|
optional is_startup
|
||||||
|
```
|
||||||
|
|
||||||
|
La référence inclut `state_hash` parce que plusieurs écritures d'une account peuvent survenir dans le même slot alors que HTTP/WS standards ne possèdent pas le `write_version` Yellowstone. Plusieurs sources observant le même état complet peuvent donc converger sans faire de l'ordinal Yellowstone un identifiant commun.
|
||||||
|
|
||||||
|
Le calcul du digest reste producer/converter-owned. `ksp-store-api` n'ajoute aucun codec ni algorithme de hash.
|
||||||
|
|
||||||
|
## Borne account data
|
||||||
|
|
||||||
|
Nouvelle admission guard Store-owned :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAX_RAW_ACCOUNT_DATA_BYTES = 16 MiB
|
||||||
|
```
|
||||||
|
|
||||||
|
Les bytes vides restent valides pour une account vide. La borne n'est pas présentée comme une limite protocolaire Solana.
|
||||||
|
|
||||||
|
`RawAccountState` n'implémente pas `Clone` et son `Debug` ne rend jamais les bytes.
|
||||||
|
|
||||||
|
## Observation account
|
||||||
|
|
||||||
|
Les informations présentes uniquement sur certaines sources restent observation-only :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Yellowstone write_version
|
||||||
|
Yellowstone transaction signature
|
||||||
|
Yellowstone is_startup
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTP/WS utilisent le même `RawAccountObservation` sans inventer ces valeurs.
|
||||||
|
|
||||||
|
Provider/protocol/method/endpoint/commitment/timing restent dans `RawAcquisitionProvenance` conformément à `pre.003`.
|
||||||
|
|
||||||
|
## Transaction status différé
|
||||||
|
|
||||||
|
Surfaces auditées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getSignatureStatuses
|
||||||
|
= snapshot interrogé avec confirmations/confirmationStatus
|
||||||
|
|
||||||
|
signatureSubscribe
|
||||||
|
= event one-shot de réception/commitment demandé
|
||||||
|
|
||||||
|
Yellowstone TransactionStatus
|
||||||
|
= update avec slot/signature/is_vote/index/error, sans commitment commun
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche n'introduit donc aucun `TransactionStatusObservation` générique rempli d'options. La future conception devra distinguer snapshot durable et event realtime et auditer l'ownership `ksp-interface-lib` des événements passifs.
|
||||||
|
|
||||||
|
## Classification fermée
|
||||||
|
|
||||||
|
```text
|
||||||
|
logsSubscribe
|
||||||
|
-> event-only candidat ; pas de RawLog Store
|
||||||
|
|
||||||
|
transaction logMessages
|
||||||
|
-> restent dans RawTransaction jusqu'à N2 STRUCTURAL
|
||||||
|
|
||||||
|
slot/root/slotsUpdates
|
||||||
|
-> event-only candidat
|
||||||
|
|
||||||
|
vote
|
||||||
|
-> event-only candidat après compatibilité utile
|
||||||
|
|
||||||
|
getBlock/Yellowstone Block
|
||||||
|
-> conteneur d'acquisition RawTransaction ; RawBlock reste IDEA
|
||||||
|
|
||||||
|
Yellowstone Entry
|
||||||
|
-> explicitement non retenu
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Store runtime ne publie aucune notification ; workers/analyzers/runtime possèdent les futurs déclenchements.
|
||||||
|
|
||||||
|
## Tests ajoutés/mis à jour
|
||||||
|
|
||||||
|
Unitaires account :
|
||||||
|
|
||||||
|
- état complet et champs communs ;
|
||||||
|
- account data vide accepté ;
|
||||||
|
- account data oversized rejeté ;
|
||||||
|
- `Debug` sans bytes ;
|
||||||
|
- metadata Yellowstone optionnelle sur observation.
|
||||||
|
|
||||||
|
Canaris d'intégration :
|
||||||
|
|
||||||
|
- surface crate-root `RawAccountState*`/`RawAccountObservation` ;
|
||||||
|
- dépendance runtime toujours exactement Core-only ;
|
||||||
|
- absence de Transport/backend/runtime/codec ;
|
||||||
|
- absence de `TransactionStatusObservation`, `RawLogNotification`, `RawSlotEvent`, `RawVoteEvent`, `RawBlock` et `YellowstoneEntry` publics.
|
||||||
|
|
||||||
|
## Documentation mise à jour
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
La matrice cross-source est désormais explicite et `pre.004` ne prétend pas que toutes les réponses on-chain constituent des modèles Store.
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
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/0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
source converter HTTP/WS/gRPC concret
|
||||||
|
TransactionStatus model commun
|
||||||
|
logs/slot/vote event model
|
||||||
|
RawBlock persistence
|
||||||
|
Yellowstone Entry persistence
|
||||||
|
capabilities read/write
|
||||||
|
queries/outcomes
|
||||||
|
retention/tombstone concret
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
PostgreSQL/tokio-postgres
|
||||||
|
Config std.store
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3/N4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
`0.3.1-pre.005` introduit les capabilities backend extensibles et object-safe pour les modèles persistants réellement matérialisés, sans façade runtime `Store`, sans backend PostgreSQL et sans obliger un backend à supporter toutes les familles N1.
|
||||||
165
deltas/0.3.1/pre.005.md
Normal file
165
deltas/0.3.1/pre.005.md
Normal 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.
|
||||||
95
deltas/0.3.1/pre.006-fix.001.md
Normal file
95
deltas/0.3.1/pre.006-fix.001.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.006-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.006-fix.001` — conformité Clippy pagination
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.006
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.006` confirme :
|
||||||
|
|
||||||
|
- `cargo fmt --all` propre ;
|
||||||
|
- audits Rust/Markdown propres ;
|
||||||
|
- `cargo check --workspace` propre ;
|
||||||
|
- `cargo test -p ksp-store-api` propre : 14 unitaires, 2 dependency-boundary, 1 external-backend, 6 public-api ;
|
||||||
|
- `cargo clippy --workspace --all-targets` termine avec exactement deux warnings ciblés dans `ksp-store-api` : `clippy::collapsible_if` et `clippy::bool_comparison`.
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Corriger exclusivement les deux warnings Clippy remontés par le gate opérateur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/src/model/raw_pagination.rs
|
||||||
|
crates/ksp-store-api/tests/dependency_boundary.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
Le premier warning concerne la validation d'un `RawSlotRange` inversé ; le second concerne uniquement l'écriture d'une assertion de canari.
|
||||||
|
|
||||||
|
## Changement fonctionnel
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
La validation conserve exactement la même règle : lorsqu'une borne de début et une borne de fin sont toutes deux présentes, `start > end` reste rejeté.
|
||||||
|
|
||||||
|
Le canari conserve exactement la même assertion : la source de pagination ne doit pas contenir le littéral `u64::MAX`, afin de ne pas transformer cette valeur en plafond KSP implicite.
|
||||||
|
|
||||||
|
Aucun contrat de pagination, outcome, rétention, capability, modèle RAW ou export public n'est modifié.
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
Ce fix modifie du code Rust. Conformément au workflow KSP, l'identité de build devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.6-fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette identité distincte garantit notamment que les commandes Cargo exécutées après application du fix ne réutilisent pas ambiguëment l'identité de build de `0.3.1-pre.6`.
|
||||||
|
|
||||||
|
## Fichiers inclus dans le payload
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-store-api/src/model/raw_pagination.rs
|
||||||
|
crates/ksp-store-api/tests/dependency_boundary.rs
|
||||||
|
deltas/0.3.1/pre.006-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` porte :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = "0.3.1-pre.6-fix.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hors scope
|
||||||
|
|
||||||
|
```text
|
||||||
|
API publique
|
||||||
|
queries/pagination
|
||||||
|
outcomes d'idempotence
|
||||||
|
rétention/tombstones
|
||||||
|
capabilities Store
|
||||||
|
ksp-store-lib
|
||||||
|
backend PostgreSQL
|
||||||
|
plans/validation/ROADMAP
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
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/0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement de génération. Le gate opérateur doit donc rejouer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
243
deltas/0.3.1/pre.006.md
Normal file
243
deltas/0.3.1/pre.006.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.006.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.006` — queries, outcomes et lifecycle RAW
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.005` est intégralement vert : audits Rust/Markdown, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test -p ksp-store-api` passent.
|
||||||
|
|
||||||
|
## Recalibrage pagination
|
||||||
|
|
||||||
|
Le brainstorming opérateur précise une règle durable : Store fournit une primitive de query/pagination, **pas une policy d'exécution**.
|
||||||
|
|
||||||
|
Une limitation physique réelle d'un backend peut contraindre ce qu'il sert en une opération. En revanche `ksp-store-api`, `ksp-store-lib` et les crates backend ne doivent pas inventer un plafond métier inférieur (`100`, `500`, etc.) simplement pour gouverner le travail d'un futur executor.
|
||||||
|
|
||||||
|
`pre.006` matérialise donc :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawPageLimit > 0
|
||||||
|
aucun maximum fonctionnel KSP arbitraire
|
||||||
|
RawPageCursor opaque <= 4 KiB
|
||||||
|
```
|
||||||
|
|
||||||
|
La borne du cursor protège un token d'entrée hostile ; elle ne limite pas le nombre de résultats. Le futur job/worker choisira sa taille de travail et son backlog via ses propres policies/processing contracts.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Finaliser les contrats N1 encore nécessaires avant le hardening de release :
|
||||||
|
|
||||||
|
```text
|
||||||
|
queries/list cursorisés
|
||||||
|
outcomes idempotents
|
||||||
|
conflit stable
|
||||||
|
atomic acquisition finalisée
|
||||||
|
rétention logique transactionnelle
|
||||||
|
tombstone anti-rebackfill
|
||||||
|
normal skip / force rehydrate explicite
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune compression, archive physique, base de données, policy de purge ou executor n'est implémenté.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/src/capability/raw_retention.rs
|
||||||
|
crates/ksp-store-api/src/model/raw_outcome.rs
|
||||||
|
crates/ksp-store-api/src/model/raw_pagination.rs
|
||||||
|
crates/ksp-store-api/src/model/raw_retention.rs
|
||||||
|
crates/ksp-store-api/unit_tests/model/raw_pagination.rs
|
||||||
|
crates/ksp-store-api/unit_tests/model/raw_retention.rs
|
||||||
|
deltas/0.3.1/pre.006.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-store-api/src/capability.rs
|
||||||
|
crates/ksp-store-api/src/capability/raw_account.rs
|
||||||
|
crates/ksp-store-api/src/capability/raw_transaction.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
|
||||||
|
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.6
|
||||||
|
```
|
||||||
|
|
||||||
|
## Queries et pagination
|
||||||
|
|
||||||
|
Surface ajoutée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawPageCursor
|
||||||
|
RawPageLimit
|
||||||
|
RawPageRequest
|
||||||
|
RawPage<T>
|
||||||
|
RawSlotRange
|
||||||
|
RawSortDirection
|
||||||
|
RawTransactionQuery
|
||||||
|
RawAccountStateQuery
|
||||||
|
```
|
||||||
|
|
||||||
|
`RawPageLimit::new(0)` est invalide. Aucun maximum KSP n'est imposé ; `u64::MAX` reste représentable par le contrat. Les queries sont network-scoped et peuvent borner les slots ; les account queries peuvent en plus sélectionner un pubkey.
|
||||||
|
|
||||||
|
Les capabilities read gagnent :
|
||||||
|
|
||||||
|
```text
|
||||||
|
list_raw_transactions(query)
|
||||||
|
list_raw_account_states(query)
|
||||||
|
```
|
||||||
|
|
||||||
|
Les pages retournent des références compactes et un cursor opaque, pas des rows/PK/SQL backend.
|
||||||
|
|
||||||
|
## Outcomes et conflits
|
||||||
|
|
||||||
|
Surface ajoutée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawEntityWriteOutcome
|
||||||
|
Inserted
|
||||||
|
AlreadyPresent
|
||||||
|
Rehydrated
|
||||||
|
SkippedPurged
|
||||||
|
|
||||||
|
RawObservationWriteOutcome
|
||||||
|
Inserted
|
||||||
|
AlreadyPresent
|
||||||
|
NotRecorded
|
||||||
|
|
||||||
|
RawAcquisitionWriteOutcome
|
||||||
|
entity + observation
|
||||||
|
```
|
||||||
|
|
||||||
|
Les opérations `persist_raw_*_acquisition` retournent désormais l'outcome combiné et conservent le contrat all-or-nothing RAW + observation.
|
||||||
|
|
||||||
|
Une même identité avec un contenu divergent doit produire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ERROR_CODE_RAW_CONFLICT
|
||||||
|
```
|
||||||
|
|
||||||
|
et jamais un overwrite silencieux.
|
||||||
|
|
||||||
|
Les écritures d'observation seules retournent `RawObservationWriteOutcome`.
|
||||||
|
|
||||||
|
## Lifecycle RAW transaction
|
||||||
|
|
||||||
|
Surface ajoutée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawRetentionState
|
||||||
|
Full
|
||||||
|
Compacted
|
||||||
|
Archived
|
||||||
|
Purged
|
||||||
|
|
||||||
|
RawTransactionTombstone
|
||||||
|
RawTransactionRetentionTransition
|
||||||
|
RawRetentionWriteOutcome
|
||||||
|
RawTransactionAcquisitionMode
|
||||||
|
Normal
|
||||||
|
ForceRehydrate
|
||||||
|
|
||||||
|
RawTransactionRetentionRead
|
||||||
|
RawTransactionRetentionWrite
|
||||||
|
```
|
||||||
|
|
||||||
|
Transitions génériques admises :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Full -> Compacted
|
||||||
|
Full -> Archived
|
||||||
|
Compacted -> Archived
|
||||||
|
Archived -> Purged
|
||||||
|
```
|
||||||
|
|
||||||
|
Interdits :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Full -> Purged direct
|
||||||
|
Purged -> Full via transition générique
|
||||||
|
```
|
||||||
|
|
||||||
|
La réhydratation est un mode d'acquisition explicitement forcé. En mode normal, un tombstone purgé doit rester connu et produire `SkippedPurged` sans rebackfill automatique.
|
||||||
|
|
||||||
|
Le tombstone conserve uniquement l'identité transactionnelle, slot, format/version et hash canonique nécessaires au contrat anti-rebackfill ; aucun payload RAW n'y subsiste.
|
||||||
|
|
||||||
|
## Ownership rétention
|
||||||
|
|
||||||
|
Le Store applique la transition demandée mais ne décide jamais que les couches supérieures sont terminées. L'éligibilité compression/archive/purge appartient à un futur worker/job/maintenance policy fondé sur les preuves de processing versionnées.
|
||||||
|
|
||||||
|
La policy reste family-specific ; `RawAccountState` ne reçoit donc aucun lifecycle de purge copié artificiellement depuis `RawTransaction` dans cette tranche.
|
||||||
|
|
||||||
|
## Canari backend externe
|
||||||
|
|
||||||
|
Le backend externe de test est mis à jour pour :
|
||||||
|
|
||||||
|
```text
|
||||||
|
queries/pages
|
||||||
|
outcomes détaillés
|
||||||
|
mode Normal/ForceRehydrate
|
||||||
|
retention read/write
|
||||||
|
```
|
||||||
|
|
||||||
|
Il reste implémentable sans `ksp-store-lib`, PostgreSQL, runtime DB ou dépendance async supplémentaire.
|
||||||
|
|
||||||
|
## Hors scope confirmé
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
PostgreSQL/tokio-postgres
|
||||||
|
Config/std.store
|
||||||
|
policy executor/backlog métier
|
||||||
|
processing ledger concret
|
||||||
|
compression/archive/purge physique
|
||||||
|
health runtime
|
||||||
|
TransactionStatusObservation commun
|
||||||
|
models event-only logs/slot/vote
|
||||||
|
RawBlock persistence
|
||||||
|
Yellowstone Entry persistence
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3 DECODED
|
||||||
|
N4 DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations opérateur de la base
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-store-api PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations de génération
|
||||||
|
|
||||||
|
Les audits structurels/Markdown sont rejoués sur l'overlay `pre.006`. `cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement de génération ; le gate Cargo final reste donc opérateur.
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
`0.3.1-pre.007` effectue le hardening adversarial/completeness : cursor/provenance hostile, races de rétention, exact exports/modules, frontière Interface/Store, dependency firewall et scope négatif N2/N3/N4.
|
||||||
39
deltas/0.3.1/pre.007-fix.001.md
Normal file
39
deltas/0.3.1/pre.007-fix.001.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Delta `0.3.1-pre.007-fix.001`
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Corriger exclusivement les erreurs de compilation et Clippy des nouveaux canaris de hardening/completeness introduits par `0.3.1-pre.007`.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.7-fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le changement de version est requis car ce fix modifie du code Rust/tests exécutables ; il fournit également une identité Cargo distincte pour le rebuild.
|
||||||
|
|
||||||
|
## Corrections
|
||||||
|
|
||||||
|
- retire quatre emprunts `&ErrorCode` erronés dans `tests/security_hardening.rs` ; `Error::code()` retourne déjà `ErrorCode` par valeur ;
|
||||||
|
- remplace les six usages de `?` interdits par `clippy::question-mark-used` dans `tests/release_completeness.rs` par des branches `Result` explicites ;
|
||||||
|
- ne modifie aucun contrat public, modèle RAW, capability, outcome, règle de rétention ou comportement fonctionnel.
|
||||||
|
|
||||||
|
## Payload
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-store-api/tests/release_completeness.rs
|
||||||
|
crates/ksp-store-api/tests/security_hardening.rs
|
||||||
|
deltas/0.3.1/pre.007-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
166
deltas/0.3.1/pre.007.md
Normal file
166
deltas/0.3.1/pre.007.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.007.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.007` — hardening adversarial et complétude Store API
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.6-fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.006-fix.001` est intégralement propre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat Store API observé : 14 tests unitaires, 2 dependency-boundary, 1 external-backend, 6 public-api et doc-tests propres.
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Fermer la tranche de hardening/completeness de `ksp-store-api` sans ouvrir une nouvelle famille N1 ni une couche supérieure :
|
||||||
|
|
||||||
|
```text
|
||||||
|
payload/cursor/provenance adversariaux
|
||||||
|
retention compare-and-transition race
|
||||||
|
exports/modules/capabilities exacts
|
||||||
|
frontière ksp-interface-lib / ksp-store-api
|
||||||
|
scope négatif event-only + N2/N3/N4
|
||||||
|
backend/dependency firewall
|
||||||
|
```
|
||||||
|
|
||||||
|
## Race de rétention
|
||||||
|
|
||||||
|
`RawTransactionRetentionTransition` transporte déjà :
|
||||||
|
|
||||||
|
```text
|
||||||
|
expected
|
||||||
|
reference
|
||||||
|
target
|
||||||
|
```
|
||||||
|
|
||||||
|
Le résultat manquait toutefois d'un état distinct lorsque l'état durable avait changé concurremment. `RawRetentionWriteOutcome` ajoute donc :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ExpectedStateMismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
Sémantique requise pour un backend :
|
||||||
|
|
||||||
|
```text
|
||||||
|
persisted == expected
|
||||||
|
-> compare-and-transition atomique
|
||||||
|
-> Applied si succès
|
||||||
|
|
||||||
|
persisted == target
|
||||||
|
-> AlreadyAtTarget
|
||||||
|
|
||||||
|
persisted != expected && persisted != target
|
||||||
|
-> ExpectedStateMismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette course n'est ni un input invalide ni une autorisation pour écraser le nouvel état. La décision de retry/reload reste au caller/worker policy.
|
||||||
|
|
||||||
|
## Hardening adversarial
|
||||||
|
|
||||||
|
Nouveau canari :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/tests/security_hardening.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
Il vérifie notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
URL/marker hostile rejeté sans echo dans Error Display/Debug
|
||||||
|
RawPayload Debug sans bytes
|
||||||
|
RawContentHash Debug opaque
|
||||||
|
RawTransactionSignature Debug opaque
|
||||||
|
RawTransaction Debug sans payload bytes
|
||||||
|
RawAccountState Debug sans account bytes
|
||||||
|
cursor exact à la borne accepté
|
||||||
|
cursor au-dessus de la borne rejeté
|
||||||
|
page limit 0 rejeté
|
||||||
|
page limit u64::MAX accepté sans policy Store artificielle
|
||||||
|
ExpectedStateMismatch distinct de Applied/AlreadyAtTarget
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complétude et frontières
|
||||||
|
|
||||||
|
Nouveau canari :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/tests/release_completeness.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
Il verrouille :
|
||||||
|
|
||||||
|
```text
|
||||||
|
60 exports crate-root exacts
|
||||||
|
aucun pub mod
|
||||||
|
inventaire exact src/model/capability
|
||||||
|
public enums évolutifs #[non_exhaustive]
|
||||||
|
10 traits capability fins exacts
|
||||||
|
aucun Store/StoreBackend runtime monolithique
|
||||||
|
aucun RawLogNotification/RawSlotEvent/RawVoteEvent public
|
||||||
|
aucun YellowstoneEntry
|
||||||
|
aucun Structural*/Decoded*/Domain* public
|
||||||
|
aucun backend PostgreSQL/MySQL dans l'API
|
||||||
|
aucun modèle persistant Store réexporté par ksp-interface-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
La frontière durable reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-interface-lib
|
||||||
|
-> contrats/wires/event-only inter-composants lorsque réellement nécessaires
|
||||||
|
|
||||||
|
ksp-store-api
|
||||||
|
-> modèles persistants/replayables/queryables + capabilities backend-agnostic
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun modèle event-only nouveau n'est créé par cette tranche.
|
||||||
|
|
||||||
|
## Hors scope
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
SQL/migrations
|
||||||
|
Config std.store
|
||||||
|
Transport -> RAW adapter
|
||||||
|
RawLogNotification / RawSlotEvent / RawVoteEvent
|
||||||
|
TransactionStatusObservation
|
||||||
|
RawBlock / Yellowstone Entry
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3 DECODED
|
||||||
|
N4 DOMAIN
|
||||||
|
processing ledger
|
||||||
|
worker/executor policy
|
||||||
|
compression/archive physiques
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
La tranche suivante normale porte :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.7
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations attendues
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
157
deltas/0.3.1/pre.008.md
Normal file
157
deltas/0.3.1/pre.008.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.008.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.008` — gate technique final Store API
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement après :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.007-fix.001
|
||||||
|
workspace.package.version = 0.3.1-pre.7-fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni le **29 août 2026** est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audits Rust / exports / workspace PASS / clean
|
||||||
|
audit Markdown PASS — 186 tables / 132 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-store-api PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate Store API comprend :
|
||||||
|
|
||||||
|
```text
|
||||||
|
15 unit tests
|
||||||
|
2 dependency-boundary tests
|
||||||
|
1 external-backend test
|
||||||
|
7 public-api tests
|
||||||
|
5 release-completeness tests
|
||||||
|
4 security-hardening tests
|
||||||
|
doc-tests propres
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
`pre.008` est exclusivement le gate technique final de `0.3.1`.
|
||||||
|
|
||||||
|
Aucun nouveau modèle, capability, outcome, query, règle de rétention, event-only type, backend, codec, dépendance ou comportement runtime n'est ajouté. Les canaris déjà présents couvrent la surface fonctionnelle de la release.
|
||||||
|
|
||||||
|
La tranche doit seulement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bump la prerelease Cargo vers pre.8
|
||||||
|
mettre le plan et la validation en état de gate final
|
||||||
|
tracer le présent delta
|
||||||
|
faire rejouer le gate workspace complet + graphes Cargo par l'opérateur
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Version
|
||||||
|
|
||||||
|
La prerelease non-fix synchronise l'identité Cargo :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.7-fix.1
|
||||||
|
->
|
||||||
|
0.3.1-pre.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Le bump garantit une identité de build distincte après la série `pre.007/fix.001` et évite toute ambiguïté avec les artefacts Cargo précédents.
|
||||||
|
|
||||||
|
## 4. Surface déjà couverte
|
||||||
|
|
||||||
|
Les canaris existants verrouillent notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction + RawTransactionObservation
|
||||||
|
RawAccountState + RawAccountObservation
|
||||||
|
provenance / références / payload / hashes bornés
|
||||||
|
queries cursorisées sans plafond métier Store arbitraire
|
||||||
|
outcomes idempotence/conflict
|
||||||
|
atomic acquisition contracts
|
||||||
|
RawRetentionState + tombstone + force-rehydrate
|
||||||
|
ExpectedStateMismatch pour les races de rétention
|
||||||
|
10 capabilities fines object-safe
|
||||||
|
backend externe implémentable sans ksp-store-lib
|
||||||
|
60 exports crate-root exacts
|
||||||
|
frontière ksp-interface-lib / ksp-store-api
|
||||||
|
aucun backend SQL/runtime
|
||||||
|
aucune surface N2 STRUCTURAL / N3 DECODED / N4 DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun nouveau test n'est ajouté uniquement pour matérialiser `pre.008`.
|
||||||
|
|
||||||
|
## 5. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.1/pre.008.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Surface explicitement inchangée
|
||||||
|
|
||||||
|
Cette tranche ne touche pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/src/**
|
||||||
|
crates/ksp-store-api/tests/**
|
||||||
|
crates/ksp-store-api/unit_tests/**
|
||||||
|
crates/ksp-store-api/Cargo.toml
|
||||||
|
README.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
prompts/**
|
||||||
|
config/**
|
||||||
|
docs/architecture/**
|
||||||
|
docs/rules/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Gate opérateur final
|
||||||
|
|
||||||
|
Exécuter :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
cargo test -p ksp-logging-lib --test ownership
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-store-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat architectural attendu pour le graphe ciblé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api
|
||||||
|
└── ksp-core-lib
|
||||||
|
└── dépendances low-level Core existantes
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance Store -> Interface/Transport/Program/Config/Logging/backend DB ne doit apparaître.
|
||||||
|
|
||||||
|
`cargo tree --duplicates` est un gate d'inspection : les doublons transitifs déjà présents dans le workspace doivent être observés, mais `pre.008` n'introduit aucune dépendance nouvelle susceptible d'en créer.
|
||||||
|
|
||||||
|
## 9. Suite
|
||||||
|
|
||||||
|
Si ce gate complet est vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.009
|
||||||
|
```
|
||||||
|
|
||||||
|
ouvre uniquement la réconciliation documentaire finale (`ksp-store-api/README.md`, `USAGE.md`, plan, validation et indexes/références durables réellement concernés), sans rouvrir `CHANGELOG.md`, `ROADMAP.md` ni le prompt `0.3.2`.
|
||||||
123
deltas/0.3.1/pre.009.md
Normal file
123
deltas/0.3.1/pre.009.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.009.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.009` — Réconciliation documentaire finale Store API RAW
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.008
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de cette base a été exécuté après `cargo clean` et passe les audits Rust/Markdown, `cargo check --workspace`, Clippy, les tests ciblés des crates présentes, `cargo test --workspace`, les trois builds Tauri Linux et les graphes Cargo. La commande ciblée initiale `cargo test -p ksp-program-lib` utilisait un nom de package inexistant ; `cargo test -p ksp-program-api` a ensuite été exécuté séparément et passe intégralement.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Fermer la réconciliation documentaire durable de `0.3.1` sans développement fonctionnel nouveau et sans empiéter sur la lane de publication `pre.010`.
|
||||||
|
|
||||||
|
La tranche doit refléter la surface réellement matérialisée de `ksp-store-api` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction / RawTransactionObservation
|
||||||
|
RawAccountState / RawAccountObservation
|
||||||
|
primitives RAW/provenance/format/hash/timestamps
|
||||||
|
queries cursorisées sans plafond métier KSP arbitraire
|
||||||
|
outcomes d'idempotence/conflit
|
||||||
|
RawRetentionState + tombstone transactionnel
|
||||||
|
normal skip après purge + force rehydrate explicite
|
||||||
|
10 capabilities backend fines/object-safe
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle doit également figer explicitement les reports :
|
||||||
|
|
||||||
|
```text
|
||||||
|
TransactionStatusObservation
|
||||||
|
logs/slot/vote event-only contracts
|
||||||
|
StoreHealth/readiness runtime
|
||||||
|
type canonique dédié de wake-up persisté-data
|
||||||
|
processing ledger / backlog métier / executor policy
|
||||||
|
compression/archive physique
|
||||||
|
ksp-store-lib / PostgreSQL / std.store
|
||||||
|
N2 STRUCTURAL / N3 DECODED / N4 DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.1/pre.009.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/000-README.md
|
||||||
|
docs/IDEAS.md
|
||||||
|
docs/architecture/003-COMPONENT_CONTRACTS.md
|
||||||
|
docs/architecture/004-COMPONENT_INVENTORY.md
|
||||||
|
docs/architecture/005-DEPENDENCY_GRAPH.md
|
||||||
|
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
|
||||||
|
docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md
|
||||||
|
docs/plans/000-README.md
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/000-README.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## Décisions réconciliées
|
||||||
|
|
||||||
|
- `workspace.package.version` passe à `0.3.1-pre.9` conformément à `VER-ID-009` pour une nouvelle prerelease non-fix, même documentaire.
|
||||||
|
- `ksp-store-api` reste la seule crate Store de `0.3.1` et conserve uniquement `ksp-core-lib` comme dépendance runtime normale.
|
||||||
|
- Le health/readiness n'est pas un contrat RAW nécessaire et n'est pas inventé dans `ksp-store-api`; il est reporté au runtime `ksp-store-lib` de `0.3.2` si un consumer réel le justifie.
|
||||||
|
- `0.3.1` n'ajoute aucun type dédié de notification persistée-data. Les références durables existantes pourront alimenter un futur contrat de wake-up lorsqu'un publisher/consumer réel sera introduit.
|
||||||
|
- PostgreSQL `LISTEN/NOTIFY` n'est plus présenté comme mécanisme déjà retenu. Un futur publisher suit `persist -> commit -> publish`; un mécanisme PostgreSQL éventuel reste une optimisation backend/runtime et jamais un event bus possédé par Store.
|
||||||
|
- Les consumers runtime ordinaires — jobs, workers et apps — utilisent `ksp-store-lib` à partir de `0.3.2`; ils ne dépendent pas d'un backend concret. Les crates backend implémentent `ksp-store-api` et ne dépendent pas de `ksp-store-lib`.
|
||||||
|
- La pagination Store exprime navigation/cursorisation. Batch-size, priorité et policy de sélection appartiennent au worker/job/executor. Store ne crée aucun plafond métier global inférieur à une capacité physique disponible du backend.
|
||||||
|
- `RawAccountState` et `RawAccountObservation` sont réconciliés comme surface réellement livrée de `0.3.1`, tandis que `TransactionStatusObservation` reste explicitement reporté.
|
||||||
|
- Les index `docs/`, plans et validations référencent désormais `0.2.14` et la candidate `0.3.1` actuelle.
|
||||||
|
- `README.md` racine ne contient aucune surface Store détaillée devenue fausse et aucun README/USAGE propre à `ksp-store-api` n'existe encore ; aucun fichier artificiel n'est créé uniquement pour satisfaire une prévision ancienne.
|
||||||
|
- `CHANGELOG.md`, `ROADMAP.md` et `prompts/021-V0_3_2_START_PROMPT.md` restent strictement réservés à `pre.010`.
|
||||||
|
- La réconciliation globale du vocabulaire historique `CORE` vers le futur N2 `STRUCTURAL` n'est toujours pas ouverte dans les architectures générales ; elle reste reportée au moment où N2 sera réellement introduit.
|
||||||
|
|
||||||
|
## Validations exécutées pendant la 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), 134 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tableaux modifiés ont été réalignés selon le format RustRover attendu par l'audit Markdown.
|
||||||
|
|
||||||
|
## Validations non exécutées pendant la génération
|
||||||
|
|
||||||
|
L'environnement de génération ne dispose pas de la toolchain Cargo/Rust utilisée par l'opérateur ; aucun `cargo check`, Clippy ou test Cargo n'est revendiqué pour cette archive.
|
||||||
|
|
||||||
|
Aucun code Rust, manifest de crate, ressource runtime, migration ou configuration exécutable n'est modifié dans cette tranche. Le gate lourd `pre.008` reste la preuve technique de référence ; `pre.009` nécessite seulement un re-gate ciblé après application.
|
||||||
|
|
||||||
|
## Gate opérateur demandé
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Les builds Tauri et le `cargo test --workspace` complet n'ont pas besoin d'être rejoués pour cette tranche purement documentaire/versionnée si ce gate ciblé reste vert.
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question bloquante pour `0.3.1`.
|
||||||
|
|
||||||
|
Les décisions runtime/backend restantes appartiennent au prompt et à la release `0.3.2` : façade `ksp-store-lib`, backend `ksp-store-postgres-lib`, Config `std.store`, migrations et validation PostgreSQL réelle.
|
||||||
135
deltas/0.3.1/pre.010.md
Normal file
135
deltas/0.3.1/pre.010.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.010.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.010` — Redécoupage documentaire des slices Store/PostgreSQL
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.009
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur ciblé de `pre.009` est propre : audits Rust/Markdown, `cargo check --workspace`, Clippy et `cargo test -p ksp-store-api` passent. Le gate lourd `pre.008`, reconstruit après `cargo clean`, reste la preuve technique de référence pour le workspace et les bundles Tauri.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Réduire avant publication le scope de la future implémentation Store/PostgreSQL afin que chaque release concrète reste clôturable dans une session sans sacrifier migrations, concurrence, atomicité, idempotence ou validation PostgreSQL réelle.
|
||||||
|
|
||||||
|
La décision opérateur est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib et ksp-store-postgres-lib restent développées ensemble
|
||||||
|
mais l'ancien scope unique 0.3.2 est découpé en trois releases
|
||||||
|
```
|
||||||
|
|
||||||
|
Nouvelle trajectoire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.2 fondation runtime/backend PostgreSQL
|
||||||
|
0.3.3 vertical slice PostgreSQL RawTransaction complète
|
||||||
|
0.3.4 vertical slice PostgreSQL RawAccountState + complétude Store RAW
|
||||||
|
0.3.5 Interface events/acquisitions partagés si consumer réel
|
||||||
|
0.3.6 ksp-job-api + premier backfill RAW
|
||||||
|
0.3.7 application backfill/inspection RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.1/pre.010.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/architecture/004-COMPONENT_INVENTORY.md
|
||||||
|
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## Décisions figées
|
||||||
|
|
||||||
|
- `workspace.package.version` passe à `0.3.1-pre.10` : nouvelle prerelease non-fix, même si la tranche est documentaire.
|
||||||
|
- `ksp-store-lib` et `ksp-store-postgres-lib` ne sont **jamais** séparées en deux releases où l'une existerait sans l'autre ; elles avancent de pair sur `0.3.2`, `0.3.3` et `0.3.4`.
|
||||||
|
- `0.3.2` est limité à la fondation runtime/backend : crates, features, Config `std.store`, sélection backend, erreur backend connu non compilé, connexion/TLS/pooling à auditer, migrations/bootstrap privés, health/readiness portable si le gate `pre.001` le confirme.
|
||||||
|
- `0.3.2` n'implémente pas artificiellement les capabilities `RawTransaction` ou `RawAccountState` uniquement pour augmenter le scope.
|
||||||
|
- `0.3.3` implémente la conformance PostgreSQL complète de `RawTransaction` : observation, get/list cursorisé, atomicité RAW + observation, idempotence/conflit, rétention/tombstone/force-rehydrate et races/rollback réels.
|
||||||
|
- `0.3.4` implémente `RawAccountState` + observation, puis la complétude/conformance cross-family, les migrations/indexes finaux et le gate PostgreSQL réel final de la couche RAW.
|
||||||
|
- La pagination Store reste une primitive de navigation/cursorisation ; batch-size, priorité et policy appartiennent aux workers/jobs/executors. Une limitation physique backend peut être reflétée sans devenir un plafond métier global imposé par Store.
|
||||||
|
- Les anciennes étapes Interface/backfill/app sont renumérotées `0.3.5`, `0.3.6`, `0.3.7`.
|
||||||
|
- La préparation de publication minimale est décalée de `pre.010` à `pre.011` afin de ne pas mélanger cette correction de trajectoire avec `CHANGELOG.md`, `ROADMAP.md` et le prompt `0.3.2`.
|
||||||
|
- `ROADMAP.md`, `CHANGELOG.md` et `prompts/021-V0_3_2_START_PROMPT.md` restent donc inchangés dans `pre.010`; ils seront réconciliés uniquement dans `pre.011` conformément à `PROMPT_STRUCTURE.md`.
|
||||||
|
|
||||||
|
## Pourquoi trois slices
|
||||||
|
|
||||||
|
L'ancien `0.3.2` cumulait au minimum :
|
||||||
|
|
||||||
|
```text
|
||||||
|
création de deux crates
|
||||||
|
feature/backend dispatch
|
||||||
|
Config et secrets
|
||||||
|
connexion/pool/TLS PostgreSQL
|
||||||
|
migrations/bootstrap
|
||||||
|
RawTransaction
|
||||||
|
RawAccountState
|
||||||
|
queries/pagination
|
||||||
|
atomicité/idempotence/races
|
||||||
|
rétention/tombstones/rehydration
|
||||||
|
health/readiness
|
||||||
|
validation PostgreSQL réelle
|
||||||
|
```
|
||||||
|
|
||||||
|
Deux slices auraient encore concentré la fondation runtime/backend et une famille RAW lourde dans la même session. Trois slices réduisent le risque de saturation tout en conservant une progression architecturale cohérente et testable.
|
||||||
|
|
||||||
|
## Validations exécutées pendant la 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), 136 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tableaux touchés ont été réalignés selon le format RustRover attendu par l'audit Markdown.
|
||||||
|
|
||||||
|
## Validations non exécutées pendant la génération
|
||||||
|
|
||||||
|
L'environnement de génération ne dispose pas de la toolchain Cargo/Rust utilisée par l'opérateur ; aucun `cargo check`, Clippy ou test Cargo n'est revendiqué pour cette archive.
|
||||||
|
|
||||||
|
Aucun code Rust, manifest de crate, schema Config, migration ou runtime n'est modifié dans cette tranche.
|
||||||
|
|
||||||
|
## Gate opérateur demandé
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate lourd workspace/Tauri n'a pas besoin d'être rejoué pour cette tranche documentaire si ce gate ciblé reste vert.
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
Si `pre.010` est propre, `pre.011` est la préparation de publication minimale :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompts/021-V0_3_2_START_PROMPT.md
|
||||||
|
deltas/0.3.1/pre.011.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Le prompt `0.3.2` devra être strictement dimensionné sur la **fondation runtime/backend PostgreSQL** et annoncer explicitement `0.3.3`/`0.3.4` comme slices suivantes, sans commencer leur implémentation.
|
||||||
270
deltas/0.3.1/pre.011.md
Normal file
270
deltas/0.3.1/pre.011.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.011.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.011` — préparation de publication et prompt `0.3.2`
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.010
|
||||||
|
workspace.package.version = 0.3.1-pre.10
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.010`, exécuté le **29 août 2026**, est propre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS / clean
|
||||||
|
python3 scripts/audit_markdown_tables.py PASS / clean — 186 tables / 135 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-store-api PASS
|
||||||
|
unit 15/15
|
||||||
|
dependency_boundary 2/2
|
||||||
|
external_backend 1/1
|
||||||
|
public_api 7/7
|
||||||
|
release_completeness 5/5
|
||||||
|
security_hardening 4/4
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate technique lourd de référence de `pre.008`, exécuté après `cargo clean`, a également validé le workspace complet, les tests ciblés, `cargo test --workspace`, les trois builds Tauri et les graphes Cargo. Le seul nom de package erroné utilisé dans la première séquence (`ksp-program-lib`) a été immédiatement remplacé par `cargo test -p ksp-program-api`, qui passe intégralement.
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Dernière prerelease avant `rel.001`, strictement limitée à la préparation de publication :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bump workspace vers 0.3.1-pre.11
|
||||||
|
ajout de l'entrée stable candidate 0.3.1 dans CHANGELOG.md
|
||||||
|
fermeture 0.3.1 et trajectoire 0.3.2..0.3.7 dans ROADMAP.md
|
||||||
|
création du prompt 0.3.2
|
||||||
|
delta pre.011
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun code, test, manifest de crate, README/USAGE, plan, validation, architecture ou règle normative n'est rouvert.
|
||||||
|
|
||||||
|
## 3. Version Cargo
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.10
|
||||||
|
-> 0.3.1-pre.11
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Changelog `0.3.1`
|
||||||
|
|
||||||
|
`CHANGELOG.md` enregistre la surface stable candidate réellement livrée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api uniquement
|
||||||
|
RawTransaction + observation
|
||||||
|
RawAccountState + observation
|
||||||
|
primitives RAW/provenance/hash/timestamp
|
||||||
|
queries cursorisées sans plafond métier KSP
|
||||||
|
10 capabilities object-safe
|
||||||
|
idempotence/conflit
|
||||||
|
rétention/tombstone/force-rehydrate
|
||||||
|
ExpectedStateMismatch
|
||||||
|
60 exports crate-root
|
||||||
|
backend externe canari
|
||||||
|
aucun PostgreSQL/runtime Config/Transport/Program/Logging
|
||||||
|
aucune surface STRUCTURAL/DECODED/DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
Il enregistre aussi le gate de référence après `cargo clean` et le redécoupage final :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.2 fondation Store/PostgreSQL
|
||||||
|
0.3.3 RawTransaction PostgreSQL
|
||||||
|
0.3.4 RawAccountState + complétude RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Roadmap
|
||||||
|
|
||||||
|
`ROADMAP.md` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
passe 0.3.1 à [X]
|
||||||
|
réduit explicitement 0.3.2 à la fondation runtime/backend
|
||||||
|
ajoute 0.3.3 RawTransaction
|
||||||
|
ajoute 0.3.4 RawAccountState + complétude
|
||||||
|
renumérote Interface / Job / application en 0.3.5 / 0.3.6 / 0.3.7
|
||||||
|
reclasse les TODO déjà matérialisés par 0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
La règle de pagination reste visible : Store navigue/cursorise ; batch-size, priorité et policy appartiennent aux futurs workers/jobs/executors.
|
||||||
|
|
||||||
|
## 6. Prompt `0.3.2`
|
||||||
|
|
||||||
|
Le nouveau prompt est confronté à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/rules/PROMPT_STRUCTURE.md
|
||||||
|
docs/rules/VERSION_WORKFLOW.md
|
||||||
|
docs/rules/FILE_CONTRACTS.md
|
||||||
|
docs/rules/RULES_KSP.md
|
||||||
|
docs/rules/RULES_DEPENDENCIES.md
|
||||||
|
|
||||||
|
docs/architecture/003-COMPONENT_CONTRACTS.md
|
||||||
|
docs/architecture/004-COMPONENT_INVENTORY.md
|
||||||
|
docs/architecture/005-DEPENDENCY_GRAPH.md
|
||||||
|
docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md
|
||||||
|
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
|
||||||
|
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
L'archive historique reste requise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
khadhroony-bot3_v0.5.3-pre.005-fix010.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
mais seulement pour réauditer les éléments physiques Store/PostgreSQL pertinents : runtime, pool, Config, migrations, schema/versioning et health. Elle n'est jamais une base de code.
|
||||||
|
|
||||||
|
Références externes vérifiées lors de la préparation du prompt, le **29 août 2026** :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgreSQL stable : 18.6
|
||||||
|
tokio-postgres : 0.7.18
|
||||||
|
```
|
||||||
|
|
||||||
|
Le prompt impose de réauditer ces versions à l'ouverture réelle de `0.3.2`.
|
||||||
|
|
||||||
|
## 7. Scope strict du prompt suivant
|
||||||
|
|
||||||
|
`0.3.2` ouvre ensemble :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
pour :
|
||||||
|
|
||||||
|
```text
|
||||||
|
features/backend dispatch
|
||||||
|
Store settings/lifecycle
|
||||||
|
Config std.store
|
||||||
|
connexion/pool/TLS PostgreSQL
|
||||||
|
migrations/bootstrap privés
|
||||||
|
health/readiness seulement si justifié
|
||||||
|
PostgreSQL integration réelle
|
||||||
|
```
|
||||||
|
|
||||||
|
Sont explicitement réservés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.3 -> RawTransaction PostgreSQL complet
|
||||||
|
0.3.4 -> RawAccountState PostgreSQL + complétude RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune table RAW métier n'est demandée à `0.3.2`.
|
||||||
|
|
||||||
|
## 8. Prévision souple intégrée au prompt
|
||||||
|
|
||||||
|
Le prompt réserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.001 audit/threat model/dependencies/sizing
|
||||||
|
pre.002 scaffold deux crates + feature graph
|
||||||
|
pre.003 settings/backend selection/lifecycle
|
||||||
|
pre.004 Config std.store
|
||||||
|
pre.005 connexion/pool/TLS
|
||||||
|
pre.006 migrations/bootstrap
|
||||||
|
pre.007 composition facade/backend + diagnostics
|
||||||
|
pre.008 PostgreSQL integration réelle
|
||||||
|
pre.009 hardening/completeness/feature matrix
|
||||||
|
pre.010 gate technique final
|
||||||
|
pre.011 réconciliation documentaire
|
||||||
|
pre.012 préparation de publication
|
||||||
|
rel.001 publication stable
|
||||||
|
```
|
||||||
|
|
||||||
|
La prévision reste souple et doit être recalibrée par `0.3.2-pre.001`.
|
||||||
|
|
||||||
|
## 9. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/021-V0_3_2_START_PROMPT.md
|
||||||
|
deltas/0.3.1/pre.011.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## 12. Surfaces explicitement non rouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
README.md
|
||||||
|
RULES.md
|
||||||
|
.env.example
|
||||||
|
config/**
|
||||||
|
crates/**
|
||||||
|
docs/**
|
||||||
|
prompts/001..020
|
||||||
|
```
|
||||||
|
|
||||||
|
En particulier :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-api/**
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
docs/architecture/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Validations de préparation
|
||||||
|
|
||||||
|
À exécuter sur l'arbre reconstruit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
audit Rust workspace
|
||||||
|
audit Markdown
|
||||||
|
contrôle exact du payload overlay
|
||||||
|
contrôle versions/file headers
|
||||||
|
contrôle qu'aucun fichier hors lane n'est modifié
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun résultat Cargo nouveau n'est revendiqué par la génération de cette tranche.
|
||||||
|
|
||||||
|
## 14. Gate opérateur
|
||||||
|
|
||||||
|
La lane est documentaire/de publication ; un gate ciblé suffit :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate lourd workspace/Tauri a déjà été exécuté sur `pre.008` et aucune surface code/runtime/dependency n'est rouverte depuis.
|
||||||
|
|
||||||
|
## 15. Étape suivante
|
||||||
|
|
||||||
|
Si le gate reste vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-rel.001
|
||||||
|
```
|
||||||
|
|
||||||
|
La publication stable doit être strictement mécanique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.3.1
|
||||||
|
deltas/0.3.1/rel.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
`rel.001` ne doit rouvrir ni `CHANGELOG.md`, ni `ROADMAP.md`, ni le prompt `0.3.2`.
|
||||||
272
deltas/0.3.1/rel.001.md
Normal file
272
deltas/0.3.1/rel.001.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
<!-- file: deltas/0.3.1/rel.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-rel.001` — publication stable Store API RAW foundation
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Base directe attendue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.011
|
||||||
|
workspace.package.version = 0.3.1-pre.11
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu pour cette livraison :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.1-rel.001
|
||||||
|
```
|
||||||
|
|
||||||
|
Tag stable attendu après validation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Responsabilité de `rel.001`
|
||||||
|
|
||||||
|
Cette livraison effectue uniquement la mécanique de publication stable définie par `VER-LIFECYCLE-012`.
|
||||||
|
|
||||||
|
Elle :
|
||||||
|
|
||||||
|
- passe `workspace.package.version` de `0.3.1-pre.11` à `0.3.1` ;
|
||||||
|
- ajoute le présent delta `rel.001` ;
|
||||||
|
- ne corrige ni code, ni test, ni manifest de crate, ni README/USAGE, ni plan/validation, ni architecture/règle, ni prompt, ni `CHANGELOG.md`, ni `ROADMAP.md`.
|
||||||
|
|
||||||
|
Tout défaut appartenant à un couloir antérieur doit renvoyer vers une prerelease appropriée ; `rel.001` n'est jamais une tranche de rattrapage.
|
||||||
|
|
||||||
|
## 3. Preuves acquises avant publication
|
||||||
|
|
||||||
|
Le gate technique complet de `0.3.1-pre.008`, exécuté le **29 août 2026** après `cargo clean`, a reconstruit et validé la workspace depuis zéro :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo clean PASS
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
General Rust rule audit clean
|
||||||
|
Rust export completeness audit 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit clean
|
||||||
|
python3 scripts/audit_markdown_tables.py PASS
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tauri build — SOL Prices Desk PASS
|
||||||
|
cargo tauri build — Wallet Desk PASS
|
||||||
|
cargo tauri build — Config Desk PASS
|
||||||
|
cargo tree -p ksp-store-api --edges normal inspecté
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
Le test ciblé `ksp-program-api`, initialement invoqué sous un nom de package erroné dans la séquence opérateur, a ensuite été rejoué explicitement avec le bon package et est passé intégralement.
|
||||||
|
|
||||||
|
Le gate ciblé de la préparation minimale de publication `0.3.1-pre.011`, exécuté le **29 août 2026**, est également vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
General Rust rule audit clean
|
||||||
|
Rust export completeness audit 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit clean
|
||||||
|
python3 scripts/audit_markdown_tables.py PASS — 186 tables / 137 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-store-api PASS
|
||||||
|
unit 15 passed
|
||||||
|
dependency_boundary 2 passed
|
||||||
|
external_backend 1 passed
|
||||||
|
public_api 7 passed
|
||||||
|
release_completeness 5 passed
|
||||||
|
security_hardening 4 passed
|
||||||
|
doc-tests 0 failed
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tranches `pre.009`, `pre.010` et `pre.011` n'ont rouvert aucun code Rust de `ksp-store-api` ni aucune dépendance runtime ; elles ont porté la réconciliation documentaire, le redécoupage de la trajectoire Store/PostgreSQL puis la préparation de publication.
|
||||||
|
|
||||||
|
## 4. Version stable publiée
|
||||||
|
|
||||||
|
La version Cargo devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
La release stable publiée est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1 — Store API RAW foundation
|
||||||
|
```
|
||||||
|
|
||||||
|
La surface stable comprend notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api
|
||||||
|
RawTransaction + RawTransactionObservation
|
||||||
|
RawAccountState + RawAccountObservation
|
||||||
|
primitives RAW bornées et redaction-safe
|
||||||
|
10 capabilities backend-agnostic fines et object-safe
|
||||||
|
queries/cursors sans plafond métier arbitraire imposé par Store
|
||||||
|
outcomes d'idempotence et de conflit
|
||||||
|
lifecycle RawTransaction Full -> Compacted -> Archived -> Purged
|
||||||
|
RawTransactionTombstone minimal
|
||||||
|
ForceRehydrate explicite
|
||||||
|
ExpectedStateMismatch pour compare-and-transition concurrent
|
||||||
|
60 exports crate-root exacts
|
||||||
|
runtime dependency normale : ksp-core-lib uniquement
|
||||||
|
```
|
||||||
|
|
||||||
|
Restent volontairement absents de `0.3.1` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib runtime
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
PostgreSQL / SQL / migrations runtime
|
||||||
|
Config std.store
|
||||||
|
worker / job / executor policy
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3 DECODED
|
||||||
|
N4 DOMAIN
|
||||||
|
RawLog persistant
|
||||||
|
TransactionStatusObservation commun
|
||||||
|
backend-specific rows / SQL ids
|
||||||
|
```
|
||||||
|
|
||||||
|
La frontière de responsabilité stable reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api
|
||||||
|
= modèles persistants/replayables + capabilities backend-agnostic
|
||||||
|
|
||||||
|
ksp-interface-lib
|
||||||
|
= contrats passifs/event-only inter-composants lorsqu'ils existent
|
||||||
|
|
||||||
|
worker / job / executor
|
||||||
|
= batch-size, priorité, stratégie de sélection, policy de traitement
|
||||||
|
```
|
||||||
|
|
||||||
|
La pagination Store reste une primitive de navigation/cursorisation. Une limite physique future d'un backend peut être exposée ou gérée par continuation, mais Store n'invente pas de plafond métier global inférieur.
|
||||||
|
|
||||||
|
## 5. Trajectoire suivante confirmée
|
||||||
|
|
||||||
|
Le redécoupage final conserve `ksp-store-lib` et `ksp-store-postgres-lib` développés ensemble à chaque release, en trois slices verticales :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.2 fondation runtime/backend PostgreSQL
|
||||||
|
0.3.3 vertical slice PostgreSQL RawTransaction complète
|
||||||
|
0.3.4 vertical slice PostgreSQL RawAccountState + complétude Store RAW
|
||||||
|
0.3.5 Interface events partagés réellement nécessaires
|
||||||
|
0.3.6 Job API + premier backfill RAW
|
||||||
|
0.3.7 application backfill/inspection RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
Le prompt actif de la prochaine release est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/021-V0_3_2_START_PROMPT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Il exige l'archive historique `khadhroony-bot3_v0.5.3-pre.005-fix010.zip` pour l'audit ciblé des anciennes surfaces Store/PostgreSQL, sans lui donner d'autorité normative sur KSP.
|
||||||
|
|
||||||
|
## 6. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.1/rel.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Validations exécutées pour préparer le delta
|
||||||
|
|
||||||
|
Contrôles statiques de l'overlay :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml version 0.3.1
|
||||||
|
payload rel.001 2 fichiers exactement
|
||||||
|
CHANGELOG.md non rouvert
|
||||||
|
ROADMAP.md non rouvert
|
||||||
|
prompt 021 non rouvert
|
||||||
|
README/USAGE non rouverts
|
||||||
|
plan / validation non rouverts
|
||||||
|
architecture / règles non rouvertes
|
||||||
|
code / tests non rouverts
|
||||||
|
aucun fichier de rattrapage présent
|
||||||
|
```
|
||||||
|
|
||||||
|
La préparation de l'archive ne rejoue aucun test Cargo dans l'environnement de génération. Les preuves techniques acquises sont celles des gates opérateur ci-dessus ; le gate stable final reste à exécuter après application.
|
||||||
|
|
||||||
|
## 10. Validation stable après application
|
||||||
|
|
||||||
|
Exécuter avant commit/tag :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.1
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Il n'est pas nécessaire de rejouer les trois builds Tauri, les graphes Cargo ni les smokes live : `rel.001` ne modifie ni code/runtime, ni dépendance, ni Config, ni surface desktop.
|
||||||
|
|
||||||
|
## 11. Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question ouverte ne bloque la publication de `0.3.1`.
|
||||||
|
|
||||||
|
Les choix de pool, TLS, mécanisme de migrations/checksums/verrouillage, forme exacte de `StoreSettings/std.store` et health/readiness appartiennent à l'audit/sizing de `0.3.2-pre.001`.
|
||||||
|
|
||||||
|
Les capabilities PostgreSQL métier `RawTransaction` et `RawAccountState` restent volontairement reportées à `0.3.3` et `0.3.4` respectivement.
|
||||||
|
|
||||||
|
## 12. Commit et tag stable
|
||||||
|
|
||||||
|
Après succès du gate stable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commit : v0.3.1-rel.001
|
||||||
|
tag : v0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun tag intermédiaire `rel.001` n'est requis.
|
||||||
|
|
||||||
|
Commandes Git typiques :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add Cargo.toml deltas/0.3.1/rel.001.md
|
||||||
|
git commit -m "v0.3.1-rel.001"
|
||||||
|
git tag v0.3.1
|
||||||
|
git push
|
||||||
|
git push origin v0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Suite
|
||||||
|
|
||||||
|
Après publication du tag stable `v0.3.1`, ouvrir exclusivement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.2-pre.001 — Store runtime / PostgreSQL foundation
|
||||||
|
```
|
||||||
|
|
||||||
|
avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/021-V0_3_2_START_PROMPT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
La session suivante doit recevoir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
archive opérateur exacte de v0.3.1
|
||||||
|
khadhroony-bot3_v0.5.3-pre.005-fix010.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`0.3.2-pre.001` reste une tranche d'audit/brainstorming/sizing avant implémentation lourde et doit préserver strictement le découpage `0.3.2` / `0.3.3` / `0.3.4` défini en fin de `0.3.1`.
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/IDEAS.md -->
|
<!-- file: docs/IDEAS.md -->
|
||||||
<!-- version: 25 -->
|
<!-- version: 26 -->
|
||||||
|
|
||||||
# Idées à explorer
|
# Idées à explorer
|
||||||
|
|
||||||
@@ -292,9 +292,9 @@ Définir un mécanisme sûr de suspension/reprise d'une exécution lorsque la po
|
|||||||
|
|
||||||
### Schémas SQL D1/D2/D3/D4
|
### Schémas SQL D1/D2/D3/D4
|
||||||
|
|
||||||
**Status :** À explorer avec la première release Store
|
**Status :** À explorer à partir de `0.3.2`
|
||||||
|
|
||||||
La structure durable D1–D4 est retenue, mais les noms de tables, colonnes, contraintes, index et repositories doivent être conçus avec les premiers workloads réels.
|
`0.3.1` a stabilisé uniquement les contrats RAW backend-agnostic. Les noms de tables, colonnes, contraintes, index, migrations et repositories PostgreSQL appartiennent à `ksp-store-postgres-lib` à partir de `0.3.2`; les couches D2/D3/D4 n'ajoutent leur persistence qu'au moment où elles sont réellement ouvertes.
|
||||||
|
|
||||||
### Format générique D3
|
### Format générique D3
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ Le journal D3 doit accepter les outputs de materializers officiels ou externes s
|
|||||||
|
|
||||||
**Status :** Transférée vers une décision/règle
|
**Status :** Transférée vers une décision/règle
|
||||||
|
|
||||||
Les processing outcomes par processor/version/capability constituent la vérité du backlog. Les cursors sont des optimisations et les jobs historiques conservent en plus leurs checkpoints de source.
|
Les futurs processing outcomes versionnés constituent la preuve durable de traitement. Les queries/cursors Store décrivent la navigation dans les données et ne fixent ni batch-size, ni priorité, ni policy d'executor ; les jobs historiques conservent en plus leurs checkpoints de source.
|
||||||
|
|
||||||
### Jobs de replay
|
### Jobs de replay
|
||||||
|
|
||||||
@@ -318,9 +318,9 @@ L'ancienne liste figée `ksp-job-replay-core` / `ksp-job-replay-generic-material
|
|||||||
|
|
||||||
### Notification backend de référence
|
### Notification backend de référence
|
||||||
|
|
||||||
**Status :** Transférée vers une décision/règle
|
**Status :** À réauditer avec le premier publisher/consumer réel
|
||||||
|
|
||||||
PostgreSQL LISTEN/NOTIFY est retenu comme mécanisme initial de référence de wake-up, combiné à un periodic polling du backlog. Le Store reste la source de vérité.
|
Aucun mécanisme PostgreSQL de notification n'est figé par `0.3.1`. L'ordre durable reste `persist -> commit -> publish`, le publisher appartenant au worker/analyser/runtime de composition et non au Store lui-même. Un polling périodique peut compléter le wake-up. Si PostgreSQL `LISTEN/NOTIFY` devient utile plus tard, il reste une optimisation backend/runtime et jamais la source de vérité ni un event bus possédé par `ksp-store-lib`.
|
||||||
|
|
||||||
## Workers / jobs — questions d'implémentation restantes
|
## Workers / jobs — questions d'implémentation restantes
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/003-COMPONENT_CONTRACTS.md -->
|
<!-- file: docs/architecture/003-COMPONENT_CONTRACTS.md -->
|
||||||
<!-- version: 9 -->
|
<!-- version: 11 -->
|
||||||
|
|
||||||
# Contrats initiaux des composants KSP
|
# Contrats initiaux des composants KSP
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ Couples explicitement retenus :
|
|||||||
```text
|
```text
|
||||||
ksp-program-api / ksp-program-lib
|
ksp-program-api / ksp-program-lib
|
||||||
ksp-materializer-api / ksp-materializer-lib
|
ksp-materializer-api / ksp-materializer-lib
|
||||||
ksp-store-api / ksp-store-lib
|
ksp-store-api / ksp-store-lib + ksp-store-<backend>-lib
|
||||||
```
|
```
|
||||||
|
|
||||||
Lifecycle APIs séparées :
|
Lifecycle APIs séparées :
|
||||||
@@ -27,6 +27,8 @@ ksp-job-api
|
|||||||
ksp-execution-policy-api
|
ksp-execution-policy-api
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Pour Store, le couple API/façade est complété par des crates backend séparées : chaque `ksp-store-<backend>-lib` implémente `ksp-store-api` et ne dépend jamais de `ksp-store-lib`. Les consumers runtime ordinaires utilisent la façade `ksp-store-lib`, pas une crate backend concrète.
|
||||||
|
|
||||||
Une crate `*-api` n'est jamais créée uniquement pour la symétrie des noms.
|
Une crate `*-api` n'est jamais créée uniquement pour la symétrie des noms.
|
||||||
|
|
||||||
## Core
|
## Core
|
||||||
@@ -156,7 +158,9 @@ Interprétation Program/protocole puis matérialisation générique/journal dura
|
|||||||
|
|
||||||
Projections queryables de domaine : token, metadata, pools, trades, OHLC, routes, etc.
|
Projections queryables de domaine : token, metadata, pools, trades, OHLC, routes, etc.
|
||||||
|
|
||||||
`ksp-store-api` possède les contrats backend-agnostic. `ksp-store-lib` fournit PostgreSQL comme backend officiel.
|
`ksp-store-api` possède les contrats backend-agnostic. `ksp-store-lib` fournit la façade/runtime commune et sélectionne les backends compilés par features. PostgreSQL est le backend officiel par défaut mais son implémentation physique appartient à `ksp-store-postgres-lib`.
|
||||||
|
|
||||||
|
Les jobs, workers et applications consomment normalement `ksp-store-lib`; les crates backend dépendent de `ksp-store-api` et restent invisibles aux consumers. Une dépendance directe à `ksp-store-api` reste réservée aux implémentations de contrats ou composants réutilisables qui ont réellement besoin de ces traits/types sans façade runtime.
|
||||||
|
|
||||||
La première Store release est RAW-only ; les couches suivantes sont ajoutées quand elles sont réellement ouvertes.
|
La première Store release est RAW-only ; les couches suivantes sont ajoutées quand elles sont réellement ouvertes.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
|
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
|
||||||
<!-- version: 23 -->
|
<!-- version: 26 -->
|
||||||
|
|
||||||
# Inventaire initial des composants KSP
|
# Inventaire initial des composants KSP
|
||||||
|
|
||||||
@@ -17,42 +17,43 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
|
|||||||
|
|
||||||
## Inventaire synthétique
|
## Inventaire synthétique
|
||||||
|
|
||||||
| Domaine | Composant | Type | Statut | Première cible actuelle | Mission |
|
| Domaine | Composant | Type | Statut | Première cible actuelle | Mission |
|
||||||
|-------------------------|------------------------------------------|--------------------|--------------|---------------------------------|----------------------------------------------------------------|
|
|-------------------------|------------------------------------------|--------------------|--------------|---------------------------------|---------------------------------------------------------------------------------------------|
|
||||||
| Core | `ksp-core-lib` | lib | Stable | `0.1.1` | Error/Result, Program IDs et primitives fondamentales |
|
| Core | `ksp-core-lib` | lib | Stable | `0.1.1` | Error/Result, Program IDs et primitives fondamentales |
|
||||||
| Logging | `ksp-logging-lib` | lib | Stable | `0.1.2` | façade unique tracing KSP |
|
| Logging | `ksp-logging-lib` | lib | Stable | `0.1.2` | façade unique tracing KSP |
|
||||||
| Config | `ksp-config-lib` | lib | Stable | `0.1.3` | documents, profils, env et persistence Config |
|
| Config | `ksp-config-lib` | lib | Stable | `0.1.3` | documents, profils, env et persistence Config |
|
||||||
| Config Desk | `ksp-app-config-desk` | app | Stable | `0.1.4` | validation/management Config |
|
| Config Desk | `ksp-app-config-desk` | app | Stable | `0.1.4` | validation/management Config |
|
||||||
| On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1`–`0.2.4` | HTTP standard complet : 52/52 current + 14/14 historical |
|
| On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1`–`0.2.4` | HTTP standard complet : 52/52 current + 14/14 historical |
|
||||||
| Wallet | `ksp-wallet-lib` | lib | Stable | `0.2.5` | `.kspwallet`, VIEW/OWNER, secrets, signature, import/export |
|
| Wallet | `ksp-wallet-lib` | lib | Stable | `0.2.5` | `.kspwallet`, VIEW/OWNER, secrets, signature, import/export |
|
||||||
| Wallet Desk | `ksp-app-wallet-desk` | app | Stable | `0.2.6` | Wallet + Config + balance HTTP + projection SOL/USD auxiliaire |
|
| Wallet Desk | `ksp-app-wallet-desk` | app | Stable | `0.2.6` | Wallet + Config + balance HTTP + projection SOL/USD auxiliaire |
|
||||||
| Wallet V2 | `ksp-wallet-lib` | lib | Stable | `0.2.6` | wire/runtime V2 + API default/versionnée + migration explicite |
|
| Wallet V2 | `ksp-wallet-lib` | lib | Stable | `0.2.6` | wire/runtime V2 + API default/versionnée + migration explicite |
|
||||||
| Standard WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.7` | WebSocket Solana 18/18, sessions/subscriptions bornées |
|
| Standard WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.7` | WebSocket Solana 18/18, sessions/subscriptions bornées |
|
||||||
| Helius WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.8` | LaserStream WS : 7 standard + transaction, actor partagé |
|
| Helius WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.8` | LaserStream WS : 7 standard + transaction, actor partagé |
|
||||||
| Yellowstone | `ksp-onchain-transport-lib` | lib | Stable | `0.2.9` | client gRPC standard/provider-neutral |
|
| Yellowstone | `ksp-onchain-transport-lib` | lib | Stable | `0.2.9` | client gRPC standard/provider-neutral |
|
||||||
| Off-chain price | `ksp-offchain-transport-lib` | lib | Stable | `0.2.11` | prix SOL/USD multi-provider, limits et availability |
|
| Off-chain price | `ksp-offchain-transport-lib` | lib | Stable | `0.2.11` | prix SOL/USD multi-provider, limits et availability |
|
||||||
| SOL Prices Desk | `ksp-app-solprices-desk` | app | Stable | `0.2.12` | HID provider-agnostic pour visualisation/refresh prix |
|
| SOL Prices Desk | `ksp-app-solprices-desk` | app | Stable | `0.2.12` | HID provider-agnostic pour visualisation/refresh prix |
|
||||||
| Wire | `ksp-interface-lib` | lib | Retenu | `0.2.13` | façade wire officielle + API publique wire |
|
| Wire | `ksp-interface-lib` | lib | Stable | `0.2.13` | façade wire officielle + API publique wire |
|
||||||
| Program API | `ksp-program-api` | API | Retenu | `0.2.14` | contrats extensibles Program |
|
| Program API | `ksp-program-api` | API | Stable | `0.2.14` | contrats extensibles Program |
|
||||||
| Program impl. | `ksp-program-lib` | lib | Retenu | vertical slices ultérieurs | implementations Program officielles |
|
| Program impl. | `ksp-program-lib` | lib | Retenu | vertical slices ultérieurs | implementations Program officielles |
|
||||||
| Program extension | `ksp-program-<name>-lib` | lib externe | À la demande | dès besoin | implementation externe de `ksp-program-api` |
|
| Program extension | `ksp-program-<name>-lib` | lib externe | À la demande | dès besoin | implementation externe de `ksp-program-api` |
|
||||||
| Store API | `ksp-store-api` | API | Retenu | `0.3.1` | contrats persistence backend-agnostic, RAW d'abord |
|
| Store API | `ksp-store-api` | API | Retenu | `0.3.1` | RAW transaction/account, observations, queries, outcomes, rétention et capabilities backend |
|
||||||
| Store PostgreSQL | `ksp-store-lib` | lib | Retenu | `0.3.1` | backend PostgreSQL officiel, RAW d'abord |
|
| Store runtime | `ksp-store-lib` | lib | Retenu | `0.3.2`–`0.3.4` | fondation puis conformance RAW par slices, dispatch features/config |
|
||||||
| Job lifecycle | `ksp-job-api` | API | Retenu | `0.3.3` | lifecycle des jobs terminables |
|
| Store PostgreSQL | `ksp-store-postgres-lib` | lib | Retenu | `0.3.2`–`0.3.4` | fondation, RawTransaction puis RawAccountState/complétude |
|
||||||
| Backfill | `ksp-job-backfill` | job/lib à préciser | Retenu | `0.3.3` | acquisition historique vers RAW |
|
| Job lifecycle | `ksp-job-api` | API | Retenu | `0.3.6` | lifecycle des jobs terminables |
|
||||||
| Backfill Desk | nom à fixer | app | Retenu | `0.3.4` | contrôle/inspection du backfill RAW |
|
| Backfill | `ksp-job-backfill` | job/lib à préciser | Retenu | `0.3.6` | acquisition historique vers RAW via `ksp-store-lib` |
|
||||||
| Worker lifecycle | `ksp-worker-api` | API | Retenu | fin couche RAW | lifecycle des services continus |
|
| Backfill Desk | nom à fixer | app | Retenu | `0.3.7` | contrôle/inspection du backfill RAW |
|
||||||
| RAW worker | `ksp-worker-raw-retriever` ou nom révisé | worker | Retenu | fin couche RAW | acquisition live vers RAW |
|
| Worker lifecycle | `ksp-worker-api` | API | Retenu | fin couche RAW | lifecycle des services continus |
|
||||||
| CORE processor | nom à fixer | processor/lib | Retenu | couche CORE | normalisation Solana générique RAW -> CORE |
|
| RAW worker | `ksp-worker-raw-retriever` ou nom révisé | worker | Retenu | fin couche RAW | acquisition live vers RAW |
|
||||||
| CORE worker | nom à fixer | worker | Retenu | fin couche CORE | backlog RAW -> CORE continu |
|
| CORE processor | nom à fixer | processor/lib | Retenu | couche CORE | normalisation Solana générique RAW -> CORE |
|
||||||
| Materializer API | `ksp-materializer-api` | API | Retenu | premier groupe DECODE | contrats extensibles matérialisation |
|
| CORE worker | nom à fixer | worker | Retenu | fin couche CORE | backlog RAW -> CORE continu |
|
||||||
| Materializer impl. | `ksp-materializer-lib` | lib | Retenu | premier groupe DECODE | implementations officielles communes |
|
| Materializer API | `ksp-materializer-api` | API | Retenu | premier groupe DECODE | contrats extensibles matérialisation |
|
||||||
| Execution policy | `ksp-execution-policy-api` | API | Retenu | premier vrai besoin execution | décision/safety multi-contexte |
|
| Materializer impl. | `ksp-materializer-lib` | lib | Retenu | premier groupe DECODE | implementations officielles communes |
|
||||||
| Execution orchestration | `ksp-execution-lib` | lib | Retenu | premier vrai cycle execution | Program + policy + Wallet + transport |
|
| Execution policy | `ksp-execution-policy-api` | API | Retenu | premier vrai besoin execution | décision/safety multi-contexte |
|
||||||
| Scenarios | `ksp-scenario-<domain>-lib` | lib | Retenu | vertical slices | validation métier/devnet par groupe |
|
| Execution orchestration | `ksp-execution-lib` | lib | Retenu | premier vrai cycle execution | Program + policy + Wallet + transport |
|
||||||
| Scenario API | `ksp-scenario-api` | API | Non retenu | — | norme souple avant trait commun |
|
| Scenarios | `ksp-scenario-<domain>-lib` | lib | Retenu | vertical slices | validation métier/devnet par groupe |
|
||||||
| Market Desk | `ksp-app-market-desk` | app | Pressenti | après Meteora/Raydium/Pump/Orca | tokens, pools, trades, liquidity, price, OHLC |
|
| Scenario API | `ksp-scenario-api` | API | Non retenu | — | norme souple avant trait commun |
|
||||||
| Trading Intelligence | noms à définir | libs/jobs | Pressenti | après données stables | features/signaux/anomalies/ML |
|
| Market Desk | `ksp-app-market-desk` | app | Pressenti | après Meteora/Raydium/Pump/Orca | tokens, pools, trades, liquidity, price, OHLC |
|
||||||
|
| Trading Intelligence | noms à définir | libs/jobs | Pressenti | après données stables | features/signaux/anomalies/ML |
|
||||||
|
|
||||||
## Contrats séparés retenus
|
## Contrats séparés retenus
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
|
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
|
||||||
<!-- version: 15 -->
|
<!-- version: 17 -->
|
||||||
|
|
||||||
# Graphe de dépendances KSP
|
# Graphe de dépendances KSP
|
||||||
|
|
||||||
@@ -336,6 +336,12 @@ ksp-store-api
|
|||||||
-> ksp-core-lib
|
-> ksp-core-lib
|
||||||
|
|
||||||
ksp-store-lib
|
ksp-store-lib
|
||||||
|
-> ksp-store-api
|
||||||
|
-> ksp-core-lib
|
||||||
|
-> ksp-logging-lib
|
||||||
|
-> ksp-store-postgres-lib # feature postgres par défaut, optionnelle au build
|
||||||
|
|
||||||
|
ksp-store-postgres-lib
|
||||||
-> ksp-store-api
|
-> ksp-store-api
|
||||||
-> ksp-core-lib
|
-> ksp-core-lib
|
||||||
-> ksp-logging-lib
|
-> ksp-logging-lib
|
||||||
@@ -347,9 +353,11 @@ Interdictions :
|
|||||||
```text
|
```text
|
||||||
ksp-store-api -X-> transport/program/materializer
|
ksp-store-api -X-> transport/program/materializer
|
||||||
ksp-store-lib -X-> transport/program/materializer
|
ksp-store-lib -X-> transport/program/materializer
|
||||||
|
ksp-store-postgres-lib -X-> ksp-store-lib
|
||||||
|
worker/job -X-> ksp-store-postgres-lib
|
||||||
```
|
```
|
||||||
|
|
||||||
La première release Store (`0.3.1`) est RAW-only ; les contrats CORE/DECODE/SPECIALIZED sont ajoutés avec leurs couches.
|
La première release Store (`0.3.1`) est RAW-only ; les contrats CORE/DECODE/SPECIALIZED sont ajoutés avec leurs couches. Les consumers runtime ordinaires (jobs, workers, apps) utilisent `ksp-store-lib`; ils ne sélectionnent ni n'importent directement `ksp-store-postgres-lib` ou un autre backend.
|
||||||
|
|
||||||
## Jobs
|
## Jobs
|
||||||
|
|
||||||
@@ -364,7 +372,7 @@ Premier job pressenti :
|
|||||||
ksp-job-backfill
|
ksp-job-backfill
|
||||||
-> ksp-job-api
|
-> ksp-job-api
|
||||||
-> ksp-onchain-transport-lib
|
-> ksp-onchain-transport-lib
|
||||||
-> ksp-store-api
|
-> ksp-store-lib
|
||||||
-> ksp-config-lib
|
-> ksp-config-lib
|
||||||
-> ksp-logging-lib
|
-> ksp-logging-lib
|
||||||
```
|
```
|
||||||
@@ -426,7 +434,7 @@ backfill app
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
ksp-app-market-desk
|
ksp-app-market-desk
|
||||||
-> ksp-store-api
|
-> ksp-store-lib
|
||||||
-> live transport only where explicitly useful
|
-> live transport only where explicitly useful
|
||||||
-> KSP domain/query contracts
|
-> KSP domain/query contracts
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md -->
|
<!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# Acquisition, workers, jobs et pipelines spécialisés
|
# Acquisition, workers, jobs et pipelines spécialisés
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ status
|
|||||||
progress
|
progress
|
||||||
```
|
```
|
||||||
|
|
||||||
Les types exacts sont décidés à `0.3.3` avec le premier vrai backfill.
|
Les types exacts sont décidés à `0.3.6` avec le premier vrai backfill, après clôture des trois slices Store/PostgreSQL `0.3.2`–`0.3.4` et de la tranche Interface `0.3.5`.
|
||||||
|
|
||||||
Aucune `ksp-job-control-lib` n'est créée sans duplication concrète.
|
Aucune `ksp-job-control-lib` n'est créée sans duplication concrète.
|
||||||
|
|
||||||
@@ -290,6 +290,8 @@ output + durable outcome
|
|||||||
|
|
||||||
Les notifications ne remplacent jamais le Store.
|
Les notifications ne remplacent jamais le Store.
|
||||||
|
|
||||||
|
La taille du batch, la priorité et la stratégie de sélection appartiennent au worker/job/executor. Store fournit les primitives de query/cursor/claim nécessaires et peut refléter une contrainte physique du backend, mais `ksp-store-lib` n'invente pas un plafond métier global inférieur à la capacité réellement disponible.
|
||||||
|
|
||||||
La sémantique cible reste at-least-once avec idempotence durable, plutôt qu'un faux exactly-once.
|
La sémantique cible reste at-least-once avec idempotence durable, plutôt qu'un faux exactly-once.
|
||||||
|
|
||||||
## Processing outcomes
|
## Processing outcomes
|
||||||
@@ -340,7 +342,7 @@ Les événements utiles comprennent notamment :
|
|||||||
ksp-job-backfill
|
ksp-job-backfill
|
||||||
-> ksp-job-api
|
-> ksp-job-api
|
||||||
-> ksp-onchain-transport-lib
|
-> ksp-onchain-transport-lib
|
||||||
-> ksp-store-api / backend injecté
|
-> ksp-store-lib # façade Store ; backend sélectionné par feature + Config
|
||||||
-> ksp-config-lib # orchestration/config, pas ownership transport
|
-> ksp-config-lib # orchestration/config, pas ownership transport
|
||||||
-> ksp-logging-lib
|
-> ksp-logging-lib
|
||||||
```
|
```
|
||||||
@@ -351,7 +353,7 @@ ksp-job-backfill
|
|||||||
ksp-worker-raw-retriever
|
ksp-worker-raw-retriever
|
||||||
-> ksp-worker-api
|
-> ksp-worker-api
|
||||||
-> ksp-onchain-transport-lib
|
-> ksp-onchain-transport-lib
|
||||||
-> ksp-store-api / backend injecté
|
-> ksp-store-lib # façade Store ; backend sélectionné par feature + Config
|
||||||
-> ksp-config-lib
|
-> ksp-config-lib
|
||||||
-> ksp-logging-lib
|
-> ksp-logging-lib
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md -->
|
<!-- file: docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# Applications, services, scenarios et control plane
|
# Applications, services, scenarios et control plane
|
||||||
|
|
||||||
@@ -107,10 +107,11 @@ ksp-app-solprices-desk
|
|||||||
-> ksp-offchain-transport-lib
|
-> ksp-offchain-transport-lib
|
||||||
|
|
||||||
ksp-app-store-desk
|
ksp-app-store-desk
|
||||||
-> ksp-store-api
|
|
||||||
-> ksp-store-lib
|
-> ksp-store-lib
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`ksp-store-lib` réexporte la surface Store commune nécessaire aux applications ; une app ne dépend pas directement de `ksp-store-api` uniquement pour atteindre les modèles/capabilities, et ne dépend jamais d'une crate backend concrète.
|
||||||
|
|
||||||
Les couches N1–N4 expriment des responsabilités et une direction de dépendances ; elles n'imposent pas de traverser toutes les couches intermédiaires.
|
Les couches N1–N4 expriment des responsabilités et une direction de dépendances ; elles n'imposent pas de traverser toutes les couches intermédiaires.
|
||||||
|
|
||||||
## Tauri
|
## Tauri
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/000-README.md -->
|
<!-- file: docs/plans/000-README.md -->
|
||||||
<!-- version: 67 -->
|
<!-- version: 68 -->
|
||||||
|
|
||||||
# Plans KSP
|
# Plans KSP
|
||||||
|
|
||||||
@@ -28,8 +28,9 @@ Un plan décrit le périmètre, les décisions déjà acquises, les questions ou
|
|||||||
- [`017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md`](017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md) — plan historique clôturé de la release stable `0.2.10 — OrbitFlare Yellowstone gRPC`; il conserve le Devnet gRPC gratuit, N1/N2 immuables, la License Key `x-token` et le smoke final `Slot + Ping`.
|
- [`017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md`](017-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC_PLAN.md) — plan historique clôturé de la release stable `0.2.10 — OrbitFlare Yellowstone gRPC`; il conserve le Devnet gRPC gratuit, N1/N2 immuables, la License Key `x-token` et le smoke final `Slot + Ping`.
|
||||||
- [`018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md`](018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md) — plan historique clôturé de `0.2.11 — Off-chain price transport`; SOL/USD V1, huit providers REST sans SDK, registry/availability/rate limits Off-chain et Config `std.offchain_transport`.
|
- [`018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md`](018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md) — plan historique clôturé de `0.2.11 — Off-chain price transport`; SOL/USD V1, huit providers REST sans SDK, registry/availability/rate limits Off-chain et Config `std.offchain_transport`.
|
||||||
- [`019-V0_2_12_SOL_PRICES_DESK_PLAN.md`](019-V0_2_12_SOL_PRICES_DESK_PLAN.md) — plan historique clôturé de `0.2.12 — SOL Prices Desk + intégration prix Wallet Desk`; il conserve le gabarit 1434/1435, le composite Config, les DTO/commands provider-neutral, l’intégration Wallet minimale et les gates live/build finaux.
|
- [`019-V0_2_12_SOL_PRICES_DESK_PLAN.md`](019-V0_2_12_SOL_PRICES_DESK_PLAN.md) — plan historique clôturé de `0.2.12 — SOL Prices Desk + intégration prix Wallet Desk`; il conserve le gabarit 1434/1435, le composite Config, les DTO/commands provider-neutral, l’intégration Wallet minimale et les gates live/build finaux.
|
||||||
- [`020-V0_2_13_INTERFACE_PLAN.md`](020-V0_2_13_INTERFACE_PLAN.md) — plan candidat réconcilié de `0.2.13 — Interface / wire foundation`; il fixe la surface finale `ProgramAccountMeta` + `ProgramInstruction`, les bornes `255` / `10_240`, le firewall Interface -> Core, les canaris de complétude/consumer externe et la frontière avec Program API/RAW/CORE.
|
- [`020-V0_2_13_INTERFACE_PLAN.md`](020-V0_2_13_INTERFACE_PLAN.md) — plan historique clôturé de la release stable `0.2.13 — Interface / wire foundation`; il fixe la surface finale `ProgramAccountMeta` + `ProgramInstruction`, les bornes `255` / `10_240`, le firewall Interface -> Core, les canaris de complétude/consumer externe et la frontière avec Program API/RAW/CORE.
|
||||||
- [`021-V0_2_14_PROGRAM_API_PLAN.md`](021-V0_2_14_PROGRAM_API_PLAN.md) — plan candidat réconcilié de `0.2.14 — Program API foundation`; il fixe la façade instruction-only ouverte, les enums Recognition/Outcome, `ProgramInstructionDecoder`, l'output associé possédé par l'implémentation, le canari externe avec Program Pubkey non enregistré, le firewall Core/Interface et le report du payload canonique D3, du registry runtime et de `ProgramExecutionPreparer`.
|
- [`021-V0_2_14_PROGRAM_API_PLAN.md`](021-V0_2_14_PROGRAM_API_PLAN.md) — plan historique clôturé de la release stable `0.2.14 — Program API foundation`; il fixe la façade instruction-only ouverte, les enums Recognition/Outcome, `ProgramInstructionDecoder`, l'output associé possédé par l'implémentation, le canari externe avec Program Pubkey non enregistré, le firewall Core/Interface et le report du payload canonique D3, du registry runtime et de `ProgramExecutionPreparer`.
|
||||||
|
- [`022-V0_3_1_STORE_RAW_PLAN.md`](022-V0_3_1_STORE_RAW_PLAN.md) — plan candidat réconcilié de `0.3.1 — Store API RAW foundation`; il fixe `ksp-store-api` seul, les modèles transaction/account + observations, queries/outcomes/capabilities, rétention/tombstone, la frontière event-only/Interface et le report de `ksp-store-lib` + PostgreSQL à `0.3.2`.
|
||||||
|
|
||||||
Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre.
|
Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre.
|
||||||
|
|
||||||
|
|||||||
1522
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
Normal file
1522
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/000-README.md -->
|
<!-- file: docs/validation/000-README.md -->
|
||||||
<!-- version: 31 -->
|
<!-- version: 32 -->
|
||||||
|
|
||||||
# Validations KSP
|
# Validations KSP
|
||||||
|
|
||||||
@@ -24,5 +24,6 @@ Documents :
|
|||||||
- [`013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md`](013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md) — matrice finale de la release stable `0.2.10` : Free Devnet, auth `x-token`, endpoint security, heartbeat standard, Config V3, smoke live `Slot + Ping` et invariant N1/N2 immuable.
|
- [`013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md`](013-V0_2_10_ORBITFLARE_YELLOWSTONE_GRPC.md) — matrice finale de la release stable `0.2.10` : Free Devnet, auth `x-token`, endpoint security, heartbeat standard, Config V3, smoke live `Slot + Ping` et invariant N1/N2 immuable.
|
||||||
- [`014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md`](014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md) — matrice historique clôturée de `0.2.11` : SOL/USD multi-provider, numeric safety, rate limiting/availability, Config capability-aware et smoke keyless final `7/7`.
|
- [`014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md`](014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md) — matrice historique clôturée de `0.2.11` : SOL/USD multi-provider, numeric safety, rate limiting/availability, Config capability-aware et smoke keyless final `7/7`.
|
||||||
- [`015-V0_2_12_SOL_PRICES_DESK.md`](015-V0_2_12_SOL_PRICES_DESK.md) — matrice historique clôturée de `0.2.12` : screen/features, DTO/commands, Config/composite, frontières provider-neutral, sécurité, Wallet integration, packaging Tauri et gates live/build.
|
- [`015-V0_2_12_SOL_PRICES_DESK.md`](015-V0_2_12_SOL_PRICES_DESK.md) — matrice historique clôturée de `0.2.12` : screen/features, DTO/commands, Config/composite, frontières provider-neutral, sécurité, Wallet integration, packaging Tauri et gates live/build.
|
||||||
- [`016-V0_2_13_INTERFACE.md`](016-V0_2_13_INTERFACE.md) — matrice candidate finale de `0.2.13` : ownership Interface/Core/Transport/Program, surface passive, bornes/adversarial, façade publique exacte, consumer externe, release completeness, dependency firewall et gate technique `pre.006` intégralement vert.
|
- [`016-V0_2_13_INTERFACE.md`](016-V0_2_13_INTERFACE.md) — matrice historique clôturée de la release stable `0.2.13` : ownership Interface/Core/Transport/Program, surface passive, bornes/adversarial, façade publique exacte, consumer externe, release completeness et dependency firewall.
|
||||||
- [`017-V0_2_14_PROGRAM_API.md`](017-V0_2_14_PROGRAM_API.md) — matrice candidate finale de `0.2.14 — Program API foundation` : façade instruction-only ouverte, Recognition/Outcome, trait externe, Program Pubkey non enregistré, hardening adversarial, release completeness, firewall Core/Interface et gate technique `pre.006` intégralement vert.
|
- [`017-V0_2_14_PROGRAM_API.md`](017-V0_2_14_PROGRAM_API.md) — matrice historique clôturée de la release stable `0.2.14 — Program API foundation` : façade instruction-only ouverte, Recognition/Outcome, trait externe, Program Pubkey non enregistré, hardening adversarial, release completeness et firewall Core/Interface.
|
||||||
|
- [`018-V0_3_1_STORE_RAW.md`](018-V0_3_1_STORE_RAW.md) — matrice candidate finale de `0.3.1 — Store API RAW foundation` : modèles transaction/account, observations, capabilities backend, pagination sans policy executor, outcomes, rétention/tombstone, hardening, gate complet `pre.008` et reports explicites vers `0.3.2+`.
|
||||||
|
|||||||
537
docs/validation/018-V0_3_1_STORE_RAW.md
Normal file
537
docs/validation/018-V0_3_1_STORE_RAW.md
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
<!-- file: docs/validation/018-V0_3_1_STORE_RAW.md -->
|
||||||
|
<!-- version: 10 -->
|
||||||
|
|
||||||
|
# Validation `0.3.1` — Store API RAW foundation
|
||||||
|
|
||||||
|
## 1. Objet
|
||||||
|
|
||||||
|
Cette matrice est ouverte par `0.3.1-pre.001`, corrigée par `pre.001-fix.001` puis recalibrée par `pre.001-fix.002`. Elle valide **`ksp-store-api` uniquement**. La façade/runtime commune `ksp-store-lib` et l'implémentation PostgreSQL séparée `ksp-store-postgres-lib` seront développées de pair sur trois slices : fondation `0.3.2`, `RawTransaction` `0.3.3`, puis `RawAccountState` + complétude `0.3.4`.
|
||||||
|
|
||||||
|
Le scope concret N1 certain est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction + observation
|
||||||
|
RawAccountState + observation
|
||||||
|
provenance/références/outcomes/queries communs
|
||||||
|
contrats/capabilities backend externes
|
||||||
|
cycle de rétention logique + tombstone
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.004` prouve la convergence de `RawAccountState`/observation entre HTTP/WS/gRPC sous admission stricte bytes complets + slot. `TransactionStatusObservation` reste différé : snapshot HTTP, transition WS et update Yellowstone ne sont pas encore un fait unique. Les notifications logs/slot/vote restent event-only candidates, avec ownership Interface préféré lorsqu'elles ne sont pas persistées.
|
||||||
|
|
||||||
|
## 2. Gate `pre.001`
|
||||||
|
|
||||||
|
| Critère | Statut | Preuve / décision |
|
||||||
|
|----------------------------------|---------|-----------------------------------------------------------------------------------|
|
||||||
|
| base stable `0.2.14` | PASS | Cargo `0.2.14`, `rel.001` et prompt 020 présents |
|
||||||
|
| baseline opérateur | PASS | audits/check/Clippy fournis verts ; validation tests déclarée OK |
|
||||||
|
| archive kbot3 + kbot2 historique | PASS | kbot3 extraite ; `olddocs/archivekbot2` audité pour lifecycle/replay |
|
||||||
|
| règles Store/API relues | PASS | règles KSP/Dependencies/Workflow prescrites relues |
|
||||||
|
| split version | PASS | `0.3.1 = Store API`, `0.3.2..0.3.4 = Store lib + PostgreSQL lib par slices` |
|
||||||
|
| dependency graph `0.3.1` | PASS | cible Core-only |
|
||||||
|
| modèle backend commun | PASS | API object/struct commune, rows backend privées |
|
||||||
|
| admission multi-source | PASS | même modèle seulement si HTTP/WS/gRPC satisfont intégralement la même sémantique |
|
||||||
|
| `RawTransaction` | PASS | premier modèle persistant certain |
|
||||||
|
| logs dans transaction | PASS | restent dans `RawTransaction`, extraction seulement en N2 STRUCTURAL |
|
||||||
|
| logsSubscribe | REPORTÉ | event-only candidat ; pas de `RawLog` Store persistant par défaut |
|
||||||
|
| account state | PASS | `pre.004` matérialise modèle/observation sous bytes complets + slot |
|
||||||
|
| transaction status | REPORTÉ | HTTP snapshot, WS transition et Yellowstone update restent distincts |
|
||||||
|
| slot/vote | TODO | event-only candidats ; ownership Interface à auditer |
|
||||||
|
| RawBlock | IDEA | non persisté par défaut ; `getBlock` sert de conteneur d'acquisition |
|
||||||
|
| Yellowstone Entry | REJETÉ | aucun replay/decomposition/event métier justifiant un modèle Store |
|
||||||
|
| frontière Interface/Store | PASS | persistent/replay -> Store API ; event-only partagé -> Interface préférentiel |
|
||||||
|
| N2 nomenclature | PASS | `CORE` remplacé par nom de travail `STRUCTURAL` |
|
||||||
|
| pipeline non linéaire | PASS | toutes les familles N1 ne sont pas forcées N1->N2->N3->N4 |
|
||||||
|
| processing proof | PASS | futur ledger stage+processor/version+input hash ; `processed: bool` insuffisant |
|
||||||
|
| retention lifecycle | PASS | `Full/Compacted/Archived/Purged` redessiné backend-agnostic |
|
||||||
|
| tombstone anti-rebackfill | PASS | identité/hash/slot minimal conservé après purge ; backfill forcé distinct |
|
||||||
|
| notification ownership runtime | PASS | Store ne possède aucun event bus/scheduler/DB notify |
|
||||||
|
| schema/migrations PostgreSQL | REPORTÉ | fondation `0.3.2`, schémas RAW complétés en `0.3.3`/`0.3.4` |
|
||||||
|
| D2/N3/N4 persistence | ABSENT | hors `0.3.1` |
|
||||||
|
| threat model | PASS | source mismatch, purge prématurée, stale processing, backend/event leaks couverts |
|
||||||
|
| sizing | PASS | dix prereleases courtes + lanes fermeture séparées |
|
||||||
|
|
||||||
|
## 3. Décisions structurelles à prouver par le code
|
||||||
|
|
||||||
|
| Contrat | Décision `pre.001-fix.002` | Gate futur |
|
||||||
|
|--------------------------------|------------------------------------------------------------------------------|------------------------------|
|
||||||
|
| crate `ksp-store-api` | seule crate Store créée en `0.3.1` | `pre.002` |
|
||||||
|
| dépendance normale | `ksp-core-lib` uniquement par défaut | `pre.002` |
|
||||||
|
| `RawPayload` | KSP-owned, source-independent, versionné, borné, Debug sans bytes | `pre.003` |
|
||||||
|
| `RawTransaction` | objet persistant commun uniquement depuis une source complète | `pre.003` |
|
||||||
|
| `RawTransactionObservation` | acquisition/provenance séparée du RAW | `pre.003` |
|
||||||
|
| transaction `logMessages` | partie du RAW transactionnel | `pre.003` |
|
||||||
|
| logs structuraux | extraction N2 STRUCTURAL future, jamais `RawLog` N1 distinct | canari négatif `pre.003/007` |
|
||||||
|
| `RawAccountState`/observation | modèle admis si HTTP/WS/gRPC convergent sans perte, matérialisé en `pre.004` | `pre.004` |
|
||||||
|
| `TransactionStatusObservation` | non matérialisé : surfaces status non équivalentes | report explicite `pre.004` |
|
||||||
|
| logs/slot/vote event-only | ne créent aucune capability Store par défaut | `pre.004/007` |
|
||||||
|
| Interface vs Store | event-only partagé -> Interface ; persistent/replay -> Store API | `pre.004/007` |
|
||||||
|
| model vs capability | un modèle API n'oblige pas tous les backends à le persister | `pre.005` |
|
||||||
|
| capabilities | read/write fines et object-safe | `pre.005` |
|
||||||
|
| transaction handle | aucun handle SQL/backend public | `pre.005` |
|
||||||
|
| atomic acquisition | méthode métier RAW + observation all-or-nothing | `pre.005/006` |
|
||||||
|
| write outcome | `Inserted / AlreadyPresent`; divergence = `Err Conflict` | `pre.006` |
|
||||||
|
| page/cursor | limit caller > 0 ; sans max KSP ; cursor opaque <= 4 KiB | `pre.006` |
|
||||||
|
| `RawRetentionState` | logique `Full/Compacted/Archived/Purged`, sans détail physique | `pre.006` |
|
||||||
|
| tombstone | identité/hash/slot minimal durable après purge | `pre.006` |
|
||||||
|
| backfill normal après purge | skip distinct | `pre.006` |
|
||||||
|
| force rehydrate | chemin explicite distinct, jamais fallback automatique | `pre.006` |
|
||||||
|
| processing evidence | futur ledger version-aware, jamais un bool unique | boundary `pre.006/007` |
|
||||||
|
| façade runtime `Store` | reportée à `ksp-store-lib`, hors `0.3.1` | fondation `0.3.2` |
|
||||||
|
|
||||||
|
## 4. Dependency firewall final attendu
|
||||||
|
|
||||||
|
Le graphe final `0.3.1` doit rester :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api
|
||||||
|
└── ksp-core-lib
|
||||||
|
└── solana-pubkey
|
||||||
|
```
|
||||||
|
|
||||||
|
Interdits par défaut dans Store API :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-onchain-transport-lib
|
||||||
|
ksp-offchain-transport-lib
|
||||||
|
ksp-interface-lib sans usage de type réel
|
||||||
|
ksp-program-api
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-materializer-api
|
||||||
|
ksp-config-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
ksp-wallet-lib
|
||||||
|
tokio-postgres
|
||||||
|
sqlx
|
||||||
|
serde / serde_json
|
||||||
|
chrono
|
||||||
|
tokio
|
||||||
|
async-trait
|
||||||
|
Tauri
|
||||||
|
```
|
||||||
|
|
||||||
|
Toute divergence exige un usage public réel et une révision du plan.
|
||||||
|
|
||||||
|
## 5. Matrice N1 initiale
|
||||||
|
|
||||||
|
| Famille / fait | Classification actuelle | Persistence Store | N2 attendu | Action `0.3.1` |
|
||||||
|
|--------------------------------|--------------------------|-------------------|----------------|--------------------------------------------------------------------|
|
||||||
|
| transaction complète | N1 RAW | oui | STRUCTURAL oui | implémenter modèle + observation |
|
||||||
|
| logs contenus dans transaction | partie de RawTransaction | via transaction | STRUCTURAL oui | préserver lossless, ne pas dupliquer en `RawLog` |
|
||||||
|
| account state complet | N1 RAW | oui à terme | pas démontré | modèle + observation matérialisés en `pre.004` |
|
||||||
|
| transaction status | familles distinctes | non figée | non | différer : snapshot HTTP != transition WS != update Yellowstone |
|
||||||
|
| `logsSubscribe` notification | event-only candidat | non par défaut | non | ownership Interface/worker à figer |
|
||||||
|
| slot/root/slotsUpdates | event-only candidat | non | non | TODO use-case + compatibilité |
|
||||||
|
| vote | event-only candidat | non | non | TODO seulement si forme commune utile |
|
||||||
|
| block complet | acquisition container | non par défaut | — | IDEA uniquement ; extraire transactions plutôt que stocker le bloc |
|
||||||
|
| Yellowstone Entry | transport-only | non | — | explicitement non retenu |
|
||||||
|
|
||||||
|
## 6. Frontière N1 -> N2 STRUCTURAL
|
||||||
|
|
||||||
|
`0.3.1` doit préserver sans implémenter :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction
|
||||||
|
-> StructuralTransaction/message
|
||||||
|
-> account keys
|
||||||
|
-> top-level instructions
|
||||||
|
-> CPI/inner instructions
|
||||||
|
-> logs/meta/balances/return data
|
||||||
|
-> traitement individuel ultérieur
|
||||||
|
```
|
||||||
|
|
||||||
|
N2 est nommé **STRUCTURAL** parce qu'il décrit une décomposition générique Solana, pas un domaine métier « Core ».
|
||||||
|
|
||||||
|
Canaris :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api -X-> ksp-program-api
|
||||||
|
RawTransaction logMessages -X-> entité RawLog persistante séparée
|
||||||
|
une erreur/absence future de decoder -X-> blocage des autres instructions
|
||||||
|
```
|
||||||
|
|
||||||
|
Toutes les familles N1 ne sont pas obligées de posséder un N2. `RawAccountState` peut par exemple aller directement vers une future étape de decode si aucune décomposition structurelle utile n'est identifiée.
|
||||||
|
|
||||||
|
## 7. Extensibilité backend
|
||||||
|
|
||||||
|
Le canari externe doit démontrer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crate/test backend externe
|
||||||
|
-> dépend de ksp-store-api
|
||||||
|
-> définit son propre backend mémoire
|
||||||
|
-> implémente les contrats/capabilities publics
|
||||||
|
-X-> ksp-store-lib
|
||||||
|
-X-> PostgreSQL
|
||||||
|
```
|
||||||
|
|
||||||
|
`0.3.2` ajoutera ensuite la façade de consommation `ksp-store-lib` et la fondation `ksp-store-postgres-lib`. La conformance API du backend officiel sera matérialisée par famille : `RawTransaction` en `0.3.3`, puis `RawAccountState` et la complétude cross-family en `0.3.4`.
|
||||||
|
|
||||||
|
## 8. Threat/API gates futurs
|
||||||
|
|
||||||
|
| Gate | Attendu | Statut initial |
|
||||||
|
|---------------------------------------------|-------------------------------------------------|----------------|
|
||||||
|
| oversized RAW payload | rejet avant allocation pathologique | `pre.003` |
|
||||||
|
| payload Debug | aucun bytes brut | `pre.003` |
|
||||||
|
| partial source -> RawTransaction | interdit ; contrat complet exigé | `pre.003/004` |
|
||||||
|
| HTTP/WS/gRPC semantic mismatch | détecté par admission matrix | `pre.004` PASS |
|
||||||
|
| account bytes partiels/parsés | refusés avant `RawAccountState` | `pre.004` PASS |
|
||||||
|
| account sans slot durable | refusé comme état persistant | `pre.004` PASS |
|
||||||
|
| account data Debug | aucun bytes brut | `pre.004` PASS |
|
||||||
|
| 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` PASS |
|
||||||
|
| event-only -> Store capability | absent par défaut | `pre.004/007` |
|
||||||
|
| Interface/Store duplicate model | absent | `pre.007` |
|
||||||
|
| page limit 0 | rejet ; aucun max KSP artificiel | `pre.006` |
|
||||||
|
| SQL/backend cursor leak | absent | `pre.006/007` |
|
||||||
|
| 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` |
|
||||||
|
| rebackfill normal après purge | skip | `pre.006` |
|
||||||
|
| force rehydrate implicite | interdit | `pre.006` |
|
||||||
|
| N2/N3/N4 creep | aucune surface | `pre.007` |
|
||||||
|
|
||||||
|
### 8.1 Matérialisation `pre.003`
|
||||||
|
|
||||||
|
La tranche implémente et couvre localement par canaris source/audit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawNetworkId
|
||||||
|
RawTransactionSignature [u8; 64]
|
||||||
|
RawTransactionReference
|
||||||
|
RawFormatId
|
||||||
|
RawContentHash [u8; 32]
|
||||||
|
RawObservationKey [u8; 32]
|
||||||
|
RawTimestamp
|
||||||
|
RawAcquisitionOrigin
|
||||||
|
RawProvenanceCode
|
||||||
|
RawAcquisitionProvenance
|
||||||
|
RawPayload
|
||||||
|
RawTransaction
|
||||||
|
RawTransactionObservation
|
||||||
|
```
|
||||||
|
|
||||||
|
Gates matérialisés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Core-only dependency firewall conservé
|
||||||
|
payload KSP source-independent, non vide, version > 0
|
||||||
|
payload maximum Store = 16 MiB
|
||||||
|
source payload metadata maximum = 64 MiB
|
||||||
|
Debug RawPayload ne rend jamais les bytes
|
||||||
|
signature/observation key/hash ne dépendent d'aucune PK backend
|
||||||
|
provenance sans URL/source payload
|
||||||
|
observed_at <= received_at
|
||||||
|
logs transactionnels restent dans le payload RawTransaction
|
||||||
|
aucun RawLog/N2 STRUCTURAL/backend/runtime ajouté
|
||||||
|
```
|
||||||
|
|
||||||
|
La complétude sémantique d'une source HTTP/WS/gRPC vers le format canonique n'est pas simulée dans Store API : elle reste le gate d'admission/conversion de `pre.004`. `pre.003` exige seulement qu'un `RawTransaction` reçoive un `RawPayload` déjà canonique complet selon son format KSP déclaré.
|
||||||
|
|
||||||
|
### 8.2 Matérialisation `pre.004`
|
||||||
|
|
||||||
|
La tranche ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAX_RAW_ACCOUNT_DATA_BYTES
|
||||||
|
RawAccountStateReference
|
||||||
|
RawAccountState
|
||||||
|
RawAccountObservation
|
||||||
|
```
|
||||||
|
|
||||||
|
Invariants vérifiés par modèle/canaris :
|
||||||
|
|
||||||
|
```text
|
||||||
|
référence = network + pubkey + slot + canonical state hash
|
||||||
|
account data complet <= 16 MiB
|
||||||
|
empty account data autorisé
|
||||||
|
Debug RawAccountState ne rend jamais les bytes
|
||||||
|
write_version/transaction_signature/is_startup restent observation-only optionnels
|
||||||
|
HTTP/WS/gRPC source details n'entrent pas dans RawAccountState
|
||||||
|
aucun TransactionStatusObservation artificiel
|
||||||
|
aucun RawLogNotification/RawSlotEvent/RawVoteEvent/RawBlock/YellowstoneEntry public
|
||||||
|
```
|
||||||
|
|
||||||
|
Matrice d'admission validée architecturalement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getAccountInfo/getMultipleAccounts -> oui avec bytes complets
|
||||||
|
getProgramAccounts -> contexte obligatoire
|
||||||
|
accountSubscribe -> oui avec bytes complets
|
||||||
|
programSubscribe -> contexte obligatoire
|
||||||
|
Yellowstone Account -> aucun accounts_data_slice
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 8.4 Matérialisation `pre.006`
|
||||||
|
|
||||||
|
La tranche ajoute les contrats logiques suivants sans backend/runtime :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawPageCursor / RawPageLimit / RawPageRequest / RawPage<T>
|
||||||
|
RawSlotRange / RawSortDirection
|
||||||
|
RawTransactionQuery / RawAccountStateQuery
|
||||||
|
RawEntityWriteOutcome / RawObservationWriteOutcome / RawAcquisitionWriteOutcome
|
||||||
|
RawRetentionState
|
||||||
|
RawTransactionAcquisitionMode
|
||||||
|
RawTransactionTombstone
|
||||||
|
RawTransactionRetentionTransition / RawRetentionWriteOutcome
|
||||||
|
RawTransactionRetentionRead / RawTransactionRetentionWrite
|
||||||
|
```
|
||||||
|
|
||||||
|
Gates matérialisés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
page limit 0 rejeté
|
||||||
|
u64::MAX admis comme demande représentable : aucun maximum métier KSP
|
||||||
|
cursor opaque borné à 4 KiB et Debug sans contenu
|
||||||
|
range de slots inversée rejetée
|
||||||
|
queries limitées aux critères de données, sans backlog/executor policy
|
||||||
|
Inserted / AlreadyPresent explicites
|
||||||
|
contenu divergent = ERROR_CODE_RAW_CONFLICT
|
||||||
|
RAW + observation gardent le contrat atomique
|
||||||
|
Full -> Compacted -> Archived -> Purged, avec Full -> Archived autorisé
|
||||||
|
aucun Full -> Purged direct
|
||||||
|
aucun Purged -> Full via transition générique
|
||||||
|
force rehydrate explicite via acquisition transactionnelle
|
||||||
|
tombstone minimal sans payload
|
||||||
|
retention policy choisie hors Store par worker/job/maintenance
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 Matérialisation `pre.007`
|
||||||
|
|
||||||
|
La tranche ferme le hardening de surface sans ajouter de nouvelle famille N1 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
security_hardening.rs
|
||||||
|
release_completeness.rs
|
||||||
|
RawRetentionWriteOutcome::ExpectedStateMismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
Gates matérialisés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
erreurs model/query/provenance hostiles sans echo de valeur
|
||||||
|
Debug payload/hash/signature/account sans bytes sensibles
|
||||||
|
cursor exact 4 KiB accepté, dépassement rejeté
|
||||||
|
RawPageLimit u64::MAX reste représentable
|
||||||
|
compare-and-transition perdue distincte de Applied/AlreadyAtTarget
|
||||||
|
exports crate-root exacts
|
||||||
|
inventaire exact des modules production RAW
|
||||||
|
10 capabilities fines exactes, aucun StoreBackend/Store runtime
|
||||||
|
Interface ne réexporte pas les modèles persistants Store
|
||||||
|
Store API ne publie aucun event-only logs/slot/vote
|
||||||
|
aucune surface STRUCTURAL/DECODED/DOMAIN
|
||||||
|
aucune fuite PostgreSQL/MySQL/backend runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
La course de rétention est un état normal de concurrence : si l'état durable ne correspond plus à `expected` et n'est pas déjà `target`, le backend doit retourner `ExpectedStateMismatch`. Cette sémantique évite d'utiliser une erreur de validation pour une course et impose une compare-and-transition atomique à l'implémentation future.
|
||||||
|
|
||||||
|
### 8.6 Gate technique `pre.008`
|
||||||
|
|
||||||
|
Le gate opérateur final a été exécuté après `cargo clean`, donc sans dépendre du cache incrémental précédent.
|
||||||
|
|
||||||
|
Résultat :
|
||||||
|
|
||||||
|
```text
|
||||||
|
audits Rust/Markdown PASS
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
tests ciblés Core/Logging/Config/Transport/Off-chain/Wallet/Interface/Store/Desks PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tauri build SOL Prices Desk PASS
|
||||||
|
cargo tauri build Wallet Desk PASS
|
||||||
|
cargo tauri build Config Desk PASS
|
||||||
|
cargo tree -p ksp-store-api --edges normal inspecté
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
Le seul incident de commande était le nom inexistant `ksp-program-lib`; il ne correspond à aucune crate publiée. Le test ciblé correct `cargo test -p ksp-program-api` a ensuite été exécuté séparément et passe : 3 unit, 2 dependency-boundary, 2 external-implementation, 4 public-api, 4 release-completeness et 3 security-hardening, plus doc-tests.
|
||||||
|
|
||||||
|
Le graphe normal Store API reste strictement `ksp-store-api -> ksp-core-lib` et aucune dépendance backend/runtime n'entre dans la crate.
|
||||||
|
|
||||||
|
### 8.7 Réconciliation `pre.009`
|
||||||
|
|
||||||
|
**PASS opérateur ciblé.** Après application de `0.3.1-pre.009`, audits Rust/Markdown, `cargo check --workspace`, Clippy et `cargo test -p ksp-store-api` passent. Aucun re-gate Tauri/workspace complet n'était requis après le gate lourd propre de `pre.008`.
|
||||||
|
|
||||||
|
La surface finale réellement retenue pour `0.3.1` est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction / RawTransactionObservation
|
||||||
|
RawAccountState / RawAccountObservation
|
||||||
|
primitives de provenance/format/hash/timestamps
|
||||||
|
queries cursorisées sans plafond métier KSP arbitraire
|
||||||
|
outcomes idempotence/conflit
|
||||||
|
RawRetentionState + tombstone transactionnel
|
||||||
|
normal skip après purge + force rehydrate explicite
|
||||||
|
10 capabilities backend fines/object-safe
|
||||||
|
```
|
||||||
|
|
||||||
|
Sont explicitement reportés plutôt que simulés dans la foundation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
TransactionStatusObservation
|
||||||
|
logs/slot/vote event-only contracts
|
||||||
|
StoreHealth/readiness runtime
|
||||||
|
type canonique dédié de wake-up persisté-data
|
||||||
|
processing ledger / backlog métier / executor policy
|
||||||
|
compression/archive physique
|
||||||
|
backend PostgreSQL et std.store
|
||||||
|
N2 STRUCTURAL / N3 DECODED / N4 DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
`README.md` ne contient aucune surface Store détaillée devenue fausse et aucun document USAGE Store n'existe encore ; ils ne sont donc pas modifiés artificiellement par cette tranche.
|
||||||
|
|
||||||
|
## 9. Gates de fermeture
|
||||||
|
|
||||||
|
### Gate technique final `pre.008`
|
||||||
|
|
||||||
|
**PASS opérateur complet.** Voir §8.6. Le gate a été reconstruit après `cargo clean` et couvre audits, workspace check/Clippy/tests, builds Tauri des trois Desk et graphes Cargo. Le test ciblé `ksp-program-api` a également été rejoué avec le bon nom de package.
|
||||||
|
|
||||||
|
### Réconciliation documentaire `pre.009`
|
||||||
|
|
||||||
|
**PASS opérateur ciblé.** Voir §8.7. Le plan, la validation, les architectures Store réellement concernées et `IDEAS.md` sont réconciliés. Aucun README/USAGE Store n'existe à maintenir actuellement.
|
||||||
|
|
||||||
|
### Redécoupage documentaire `pre.010`
|
||||||
|
|
||||||
|
La décision opérateur postérieure à `pre.009` sépare l'ancien scope PostgreSQL unique en trois releases afin de préserver la qualité et la clôture par session, sans séparer `ksp-store-lib` de `ksp-store-postgres-lib`. Cette responsabilité documentaire doit être fermée avant la lane de publication.
|
||||||
|
|
||||||
|
### Préparation de publication `pre.011`
|
||||||
|
|
||||||
|
La préparation de publication finale est décalée à `pre.011` après le redécoupage documentaire `pre.010`. Elle doit rester limitée à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompts/021-V0_3_2_START_PROMPT.md
|
||||||
|
delta pre.011
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.011` ne doit plus réparer la taxonomie N1/N2 ni le découpage Store/PostgreSQL : ces décisions appartiennent aux tranches documentaires antérieures.
|
||||||
|
|
||||||
|
## 10. PostgreSQL reporté aux slices `0.3.2`–`0.3.4`
|
||||||
|
|
||||||
|
Aucun gate PostgreSQL live n'est demandé à `0.3.1`. Le développement de `ksp-store-lib` et `ksp-store-postgres-lib` reste conjoint, mais l'ancien scope unique `0.3.2` est redécoupé pour préserver la qualité et la clôture par session.
|
||||||
|
|
||||||
|
### `0.3.2` — fondation runtime/backend PostgreSQL
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib
|
||||||
|
-> façade Store commune minimale
|
||||||
|
-> réexports utiles de ksp-store-api
|
||||||
|
-> feature postgres par défaut
|
||||||
|
-> dispatch backend selon Config
|
||||||
|
-> erreur backend connu mais non compilé
|
||||||
|
-> health/readiness runtime si justifié par le gate pre.001
|
||||||
|
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
-> tokio-postgres
|
||||||
|
-> connexion/pool/TLS audités
|
||||||
|
-> bootstrap migrations KSP-owned
|
||||||
|
-> transaction primitive privée si nécessaire à la suite
|
||||||
|
|
||||||
|
Config
|
||||||
|
-> std.store
|
||||||
|
-> URI/DSN backend lorsque naturel
|
||||||
|
-> secrets ${KSP_SECRET_*} / .env owned par ksp-config-lib
|
||||||
|
-> aucun vrai secret versionné
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune capability `RawTransaction` ou `RawAccountState` n'est implémentée artificiellement dans cette fondation uniquement pour gonfler le scope.
|
||||||
|
|
||||||
|
### `0.3.3` — vertical slice PostgreSQL `RawTransaction`
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction + observation
|
||||||
|
write/read/get/list cursorisé
|
||||||
|
atomic RAW + observation
|
||||||
|
idempotence / AlreadyPresent / conflict
|
||||||
|
retention / tombstone / force rehydrate
|
||||||
|
rollback et races réelles
|
||||||
|
conformance ksp-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
### `0.3.4` — vertical slice PostgreSQL `RawAccountState` + complétude
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawAccountState + observation
|
||||||
|
write/read/get/list cursorisé
|
||||||
|
idempotence / conflict / atomic observation
|
||||||
|
conformance cross-family
|
||||||
|
migration/index hardening
|
||||||
|
backend dispatch/health final
|
||||||
|
gate PostgreSQL réel final
|
||||||
|
```
|
||||||
|
|
||||||
|
Règles communes aux trois slices :
|
||||||
|
|
||||||
|
```text
|
||||||
|
URI/DSN/credentials redacted
|
||||||
|
aucune lecture directe env/.env par Store ou backend
|
||||||
|
aucun plafond métier arbitraire de pagination imposé par Store
|
||||||
|
limitations physiques backend exposées sans devenir policy executor
|
||||||
|
PostgreSQL réel opt-in pendant développement puis gate final de la slice concernée
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. État des tranches
|
||||||
|
|
||||||
|
| Tranche | Objet | État |
|
||||||
|
|-----------|------------------------------------------|---------------------------------|
|
||||||
|
| `pre.001` | audit/design/taxonomie/split | PASS |
|
||||||
|
| `pre.002` | scaffold + taxonomie Store API | PASS |
|
||||||
|
| `pre.003` | primitives + RawTransaction | PASS après `fix.001` |
|
||||||
|
| `pre.004` | admission matrix + account/status models | PASS |
|
||||||
|
| `pre.005` | backend contracts/capabilities | PASS |
|
||||||
|
| `pre.006` | queries/outcomes/retention/tombstone | PASS après `fix.001` |
|
||||||
|
| `pre.007` | boundary/adversarial/completeness | PASS après `fix.001` |
|
||||||
|
| `pre.008` | gate technique final | PASS opérateur complet |
|
||||||
|
| `pre.009` | réconciliation documentaire | PASS opérateur ciblé |
|
||||||
|
| `pre.010` | redécoupage documentaire futures slices | PRÊT après audits documentaires |
|
||||||
|
| `pre.011` | préparation publication | À FAIRE |
|
||||||
|
| `rel.001` | stable | À FAIRE |
|
||||||
932
prompts/021-V0_3_2_START_PROMPT.md
Normal file
932
prompts/021-V0_3_2_START_PROMPT.md
Normal file
@@ -0,0 +1,932 @@
|
|||||||
|
<!-- file: prompts/021-V0_3_2_START_PROMPT.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Prompt de démarrage `0.3.2` — Store/PostgreSQL runtime foundation
|
||||||
|
|
||||||
|
## 1. Identité de la release et bases exactes requises
|
||||||
|
|
||||||
|
La base KSP attendue est **exclusivement** la release stable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
La release à ouvrir est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.2 — Store/PostgreSQL runtime foundation
|
||||||
|
```
|
||||||
|
|
||||||
|
La première tranche est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.2-pre.001
|
||||||
|
```
|
||||||
|
|
||||||
|
Deux archives sont requises au démarrage :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. archive opérateur correspondant exactement à KSP v0.3.1
|
||||||
|
2. archive historique khadhroony-bot3_v0.5.3-pre.005-fix010.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Ordre d'autorité :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.1 réelle / archive opérateur autorité KSP actuelle
|
||||||
|
règles + architecture de v0.3.1 autorité normative et architecturale
|
||||||
|
plan + validation 0.3.1 autorité sur les contrats Store API acquis
|
||||||
|
PostgreSQL/tokio-postgres actuels autorité externe sur les comportements présents
|
||||||
|
khadhroony-bot3 historique source d'audit/héritage uniquement
|
||||||
|
anciens prompts / snippets / mémoire auxiliaires seulement
|
||||||
|
```
|
||||||
|
|
||||||
|
L'archive kbot3 reste obligatoire pour `pre.001`, mais l'audit n'a pas à répéter mécaniquement tout `0.3.1`. Il doit rouvrir les parties physiques pertinentes : ancien runtime Store, configuration, connexion PostgreSQL, migrations, schema/versioning, health/readiness, erreurs, pool et stratégie d'intégration.
|
||||||
|
|
||||||
|
Si l'archive historique n'est pas disponible :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ne pas inventer son contenu depuis la mémoire
|
||||||
|
ne pas déclarer l'audit d'héritage physique terminé
|
||||||
|
ne pas figer le mécanisme final de migrations/bootstrap
|
||||||
|
```
|
||||||
|
|
||||||
|
Ne pas ouvrir `0.3.2` depuis :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.*
|
||||||
|
0.3.1-pre.*-fix.*
|
||||||
|
0.3.1-rel.* non encore validé stable
|
||||||
|
une archive intermédiaire de travail
|
||||||
|
khadhroony-bot3 comme base de code
|
||||||
|
un souvenir de session
|
||||||
|
```
|
||||||
|
|
||||||
|
À l'ouverture, vérifier au minimum :
|
||||||
|
|
||||||
|
```text
|
||||||
|
tag Git v0.3.1 si metadata Git disponible
|
||||||
|
workspace.package.version = 0.3.1
|
||||||
|
deltas/0.3.1/rel.001.md présent
|
||||||
|
prompts/021-V0_3_2_START_PROMPT.md présent
|
||||||
|
ksp-store-api présent
|
||||||
|
ksp-store-lib absent sauf contradiction de la base réelle
|
||||||
|
ksp-store-postgres-lib absent sauf contradiction de la base réelle
|
||||||
|
archive khadhroony-bot3_v0.5.3-pre.005-fix010.zip disponible
|
||||||
|
```
|
||||||
|
|
||||||
|
Si une divergence existe entre ce prompt et la base stable réelle, **la base stable réelle gagne** et la divergence devient une sortie explicite de `pre.001`.
|
||||||
|
|
||||||
|
`pre.001` est obligatoirement une tranche de **lecture + audit + brainstorming + threat model + dependency graph + sizing + planification**. Aucune implémentation lourde de connexion, pool, TLS, migration ou Config Store ne commence avant la sortie cohérente de ce gate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Mission et résultat attendu
|
||||||
|
|
||||||
|
`0.3.2` introduit ensemble :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
mais **uniquement comme fondation runtime/backend PostgreSQL**.
|
||||||
|
|
||||||
|
La séparation durable est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api
|
||||||
|
contrats backend-agnostic persistants
|
||||||
|
|
||||||
|
ksp-store-lib
|
||||||
|
façade/runtime Store commune
|
||||||
|
sélectionne uniquement les backends compilés
|
||||||
|
feature postgres activée par défaut
|
||||||
|
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
backend PostgreSQL officiel de référence
|
||||||
|
dépend de ksp-store-api
|
||||||
|
ne dépend jamais de ksp-store-lib
|
||||||
|
possède seul driver / pool / TLS / SQL / migrations physiques
|
||||||
|
```
|
||||||
|
|
||||||
|
Le résultat attendu à la clôture est une fondation réelle capable de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
construire des Store settings indépendants de Config
|
||||||
|
sélectionner explicitement un backend compilé
|
||||||
|
ouvrir et fermer proprement le runtime Store
|
||||||
|
ouvrir une connexion/pool PostgreSQL réellement fonctionnel
|
||||||
|
initialiser et vérifier la fondation de migrations/schema sans tables RAW métier
|
||||||
|
rejeter proprement un backend connu mais non compilé
|
||||||
|
recevoir la Config effective uniquement depuis ksp-config-lib
|
||||||
|
ne lire directement aucun .env / KSP_* / KSPB_* / PG* / .pgpass dans Store/backend
|
||||||
|
fournir des erreurs et diagnostics sans URI/credential/SQL parameter sensible
|
||||||
|
prouver la fondation sur un PostgreSQL réel dans un test opt-in isolé et non destructif
|
||||||
|
```
|
||||||
|
|
||||||
|
`0.3.2` **ne doit pas** implémenter les vertical slices persistence de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransaction réservé à 0.3.3
|
||||||
|
RawAccountState réservé à 0.3.4
|
||||||
|
```
|
||||||
|
|
||||||
|
La release peut créer les structures privées nécessaires au runtime et au moteur de migrations, mais aucun schéma/table/index métier `RawTransaction` ou `RawAccountState` n'est ajouté uniquement pour « tester » PostgreSQL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Sources de vérité internes obligatoires — ordre de lecture
|
||||||
|
|
||||||
|
### 3.1 Règles globales
|
||||||
|
|
||||||
|
Lire d'abord :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RULES.md
|
||||||
|
docs/000-README.md
|
||||||
|
|
||||||
|
docs/rules/RULES_GENERAL.md
|
||||||
|
docs/rules/RULES_KSP.md
|
||||||
|
docs/rules/RULES_RUST.md
|
||||||
|
docs/rules/RULES_DEPENDENCIES.md
|
||||||
|
docs/rules/RULES_DOCUMENTATION.md
|
||||||
|
docs/rules/FILE_CONTRACTS.md
|
||||||
|
docs/rules/VERSION_WORKFLOW.md
|
||||||
|
docs/rules/PROMPT_STRUCTURE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Relire particulièrement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KSP-API-001..007
|
||||||
|
KSP-CONFIG-001..018
|
||||||
|
KSP-STORE-001..002
|
||||||
|
KSP-NOTIFY-001..006
|
||||||
|
KSP-PROC-001..008
|
||||||
|
KSP-REL-001..016
|
||||||
|
|
||||||
|
DEP-KSP-001..005
|
||||||
|
DEP-CARGO-001..007
|
||||||
|
DEP-LOG-001..012
|
||||||
|
DEP-STORE-001..010
|
||||||
|
DEP-WORKER-001..003
|
||||||
|
DEP-JOB-001..003
|
||||||
|
```
|
||||||
|
|
||||||
|
Rappels directement structurants :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib -> ksp-store-api
|
||||||
|
ksp-store-lib[postgres] -> ksp-store-postgres-lib
|
||||||
|
ksp-store-postgres-lib -> ksp-store-api
|
||||||
|
ksp-store-postgres-lib -X-> ksp-store-lib
|
||||||
|
Config -> Store autorisé
|
||||||
|
Store -X-> Config
|
||||||
|
Transport -X-> Store
|
||||||
|
Program/Materializer -X-> Store
|
||||||
|
workers/jobs/apps futurs -> ksp-store-lib
|
||||||
|
workers/jobs/apps futurs -X-> ksp-store-postgres-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Architecture durable
|
||||||
|
|
||||||
|
Lire ensuite :
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/architecture/000-README.md
|
||||||
|
docs/architecture/001-PROJECT_OBJECTIVES.md
|
||||||
|
docs/architecture/002-LAYERS_AND_DEPENDENCIES.md
|
||||||
|
docs/architecture/003-COMPONENT_CONTRACTS.md
|
||||||
|
docs/architecture/004-COMPONENT_INVENTORY.md
|
||||||
|
docs/architecture/005-DEPENDENCY_GRAPH.md
|
||||||
|
docs/architecture/007-EXECUTION_AND_POLICY.md
|
||||||
|
docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md
|
||||||
|
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
|
||||||
|
docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Références centrales de cette release :
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/architecture/003-COMPONENT_CONTRACTS.md
|
||||||
|
docs/architecture/005-DEPENDENCY_GRAPH.md
|
||||||
|
docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Préserver notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Store persiste et sert des contrats ; il ne possède pas les policies d'exécution
|
||||||
|
la pagination/cursorisation Store n'impose aucun plafond métier global arbitraire
|
||||||
|
batch-size/priorité/stratégie appartiennent aux futurs workers/jobs/executors
|
||||||
|
Config possède documents/placeholders/.env/secrets
|
||||||
|
backend PostgreSQL possède son driver et ses objets physiques
|
||||||
|
aucun type SQL/backend ne traverse la façade publique
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 État `0.3.1` à relire intégralement
|
||||||
|
|
||||||
|
Lire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
crates/ksp-store-api/Cargo.toml
|
||||||
|
crates/ksp-store-api/src/**
|
||||||
|
crates/ksp-store-api/tests/**
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Créer en `pre.001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md
|
||||||
|
docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Le plan doit contenir la prévision souple recalibrée de la release et rester la référence détaillée des prereleases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Sources externes normatives à réauditer
|
||||||
|
|
||||||
|
La fraîcheur importe pour cette release.
|
||||||
|
|
||||||
|
Réauditer au début de `pre.001` avec les sources primaires actuelles :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgreSQL documentation/release notes courantes
|
||||||
|
tokio-postgres docs.rs / crates.io / repository upstream
|
||||||
|
runtime Tokio réellement requis par tokio-postgres
|
||||||
|
connecteurs TLS réellement compatibles et maintenus
|
||||||
|
solutions de pooling candidates si un pool externe est retenu
|
||||||
|
```
|
||||||
|
|
||||||
|
Référence connue lors de la préparation de ce prompt, le **29 août 2026** :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgreSQL stable courant : 18.6
|
||||||
|
tokio-postgres courant : 0.7.18
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces versions sont des **points de départ d'audit**, pas des pins automatiques. `pre.001` doit vérifier qu'elles sont toujours les versions stables pertinentes au moment réel de l'implémentation.
|
||||||
|
|
||||||
|
Décision déjà acquise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
driver PostgreSQL KSP = tokio-postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
Ne pas rouvrir SQLx comme choix principal sauf contradiction technique nouvelle et documentée.
|
||||||
|
|
||||||
|
Questions externes encore ouvertes à auditer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pooling : pool externe maintenu vs abstraction KSP minimale
|
||||||
|
TLS : connecteur exact, root store, modes supportés, absence de fuite de secrets
|
||||||
|
PostgreSQL major minimal supporté/testé
|
||||||
|
migrations : mécanisme KSP privé vs crate externe réellement justifiée
|
||||||
|
checksums/versioning/locking de migrations
|
||||||
|
timeouts et shutdown propre
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance additionnelle n'est ajoutée seulement parce qu'elle est habituelle dans l'écosystème.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. État validé de `0.3.1` à préserver
|
||||||
|
|
||||||
|
`ksp-store-api` est une fondation stable candidate et ne doit pas être redessinée pour simplifier PostgreSQL.
|
||||||
|
|
||||||
|
Surface acquise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
60 exports crate-root
|
||||||
|
10 capabilities fines object-safe
|
||||||
|
RawTransaction + RawTransactionObservation
|
||||||
|
RawAccountState + RawAccountObservation
|
||||||
|
RawPayload / RawContentHash / RawObservationKey
|
||||||
|
provenance/timestamps/codes bornés
|
||||||
|
queries cursorisées
|
||||||
|
outcomes idempotence/conflit
|
||||||
|
RawRetentionState / tombstone / force-rehydrate
|
||||||
|
ExpectedStateMismatch pour race de rétention
|
||||||
|
```
|
||||||
|
|
||||||
|
Propriétés acquises :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api -> ksp-core-lib uniquement
|
||||||
|
aucun pub mod public
|
||||||
|
aucun SQL / row / pool / runtime DB
|
||||||
|
aucun serde/tokio/config/transport/program/logging
|
||||||
|
aucun modèle event-only
|
||||||
|
aucune surface STRUCTURAL / DECODED / DOMAIN
|
||||||
|
backend externe implémentable sans ksp-store-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
`0.3.2` ne modifie `ksp-store-api` que si un **gap backend-agnostic concret et bloquant** est démontré par l'implémentation de fondation. Une préférence PostgreSQL, un type de pool ou un besoin SQL n'est jamais une raison suffisante.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Décisions acquises et questions réellement ouvertes
|
||||||
|
|
||||||
|
### 6.1 Décisions acquises
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib et ksp-store-postgres-lib sont ouvertes ensemble
|
||||||
|
postgres est la feature backend par défaut de ksp-store-lib
|
||||||
|
tokio-postgres est le driver PostgreSQL retenu
|
||||||
|
ksp-store-postgres-lib dépend de ksp-store-api, jamais de ksp-store-lib
|
||||||
|
ksp-store-lib ne contient aucun SQL/backend physique
|
||||||
|
Config sélectionne le backend parmi ceux compilés
|
||||||
|
backend connu mais non compilé -> erreur explicite, aucun fallback silencieux
|
||||||
|
Config possède URI/secrets/.env ; Store/backend ne lisent aucun environnement directement
|
||||||
|
RawTransaction PostgreSQL est hors 0.3.2
|
||||||
|
RawAccountState PostgreSQL est hors 0.3.2
|
||||||
|
pagination Store != policy de batch executor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Questions ouvertes à fermer par `pre.001`
|
||||||
|
|
||||||
|
```text
|
||||||
|
forme exacte de StoreSettings et StoreBackendKind
|
||||||
|
forme exacte du lifecycle Store open/close
|
||||||
|
reexports API nécessaires depuis ksp-store-lib
|
||||||
|
pool : bibliothèque externe ou implémentation KSP minimale
|
||||||
|
TLS : connecteur/features/modes réellement retenus
|
||||||
|
support PostgreSQL major minimal
|
||||||
|
mécanisme de migration/version/checksum
|
||||||
|
verrouillage concurrent du bootstrap/migrations
|
||||||
|
schema/namespace privé de migration
|
||||||
|
stratégie de rollback après migration échouée
|
||||||
|
health/readiness portable : nécessaire maintenant ou reporté
|
||||||
|
stratégie de test PostgreSQL réel isolé/non destructif
|
||||||
|
forme exacte de std.store et de ses profils/secrets
|
||||||
|
impacts de packaging Config/Tauri lorsque le registre Config gagne std.store
|
||||||
|
```
|
||||||
|
|
||||||
|
Une question ouverte ne doit pas être résolue par imitation de kbot3 ou par habitude ecosystem sans audit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Objectifs et livrables de `0.3.2`
|
||||||
|
|
||||||
|
### 7.1 `ksp-store-lib`
|
||||||
|
|
||||||
|
À la clôture, la crate doit au minimum :
|
||||||
|
|
||||||
|
```text
|
||||||
|
exister comme bibliothèque Rust 2024
|
||||||
|
avoir ksp-store-api comme dépendance normale
|
||||||
|
avoir la feature postgres activée par défaut
|
||||||
|
lier ksp-store-postgres-lib uniquement sous feature postgres
|
||||||
|
rester compilable --no-default-features
|
||||||
|
exposer une façade Store/runtime commune
|
||||||
|
accepter des settings déjà résolus
|
||||||
|
sélectionner un backend compilé explicitement
|
||||||
|
rejeter un backend connu non compilé avec erreur stable
|
||||||
|
ne jamais exposer un handle/type PostgreSQL public
|
||||||
|
réexporter uniquement la surface ksp-store-api réellement utile aux consumers
|
||||||
|
posséder README.md + USAGE.md durables avant fermeture
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 `ksp-store-postgres-lib`
|
||||||
|
|
||||||
|
À la clôture, la crate doit au minimum :
|
||||||
|
|
||||||
|
```text
|
||||||
|
exister comme backend officiel
|
||||||
|
avoir ksp-store-api comme dépendance KSP
|
||||||
|
ne jamais dépendre de ksp-store-lib
|
||||||
|
posséder tokio-postgres en interne
|
||||||
|
posséder connexion/pool/TLS retenus
|
||||||
|
posséder bootstrap/migrations privés
|
||||||
|
ouvrir/fermer proprement ses ressources
|
||||||
|
sanitiser erreurs/Debug/logs
|
||||||
|
ne lire aucun Config/env directement
|
||||||
|
ne créer aucune table RAW métier de 0.3.3/0.3.4
|
||||||
|
posséder README.md + USAGE.md durables avant fermeture
|
||||||
|
```
|
||||||
|
|
||||||
|
Une table/namespace interne de suivi des migrations peut être créée si elle est réellement nécessaire au moteur de migration ; elle reste une primitive d'infrastructure, pas une première table RAW métier.
|
||||||
|
|
||||||
|
### 7.3 Config `std.store`
|
||||||
|
|
||||||
|
La release doit ajouter une Config Store seulement après design `pre.001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
config/std.store.json
|
||||||
|
config/examples/std.store.example.json
|
||||||
|
config/schemas/std.store.schema.json
|
||||||
|
registry Config + file IDs
|
||||||
|
adapter typed ksp-config-lib -> StoreSettings
|
||||||
|
provenance/sensibilité/redaction conformes aux règles Config
|
||||||
|
```
|
||||||
|
|
||||||
|
Les secrets PostgreSQL passent par Config. Ne jamais introduire dans Store/backend :
|
||||||
|
|
||||||
|
```text
|
||||||
|
std::env
|
||||||
|
.env parsing
|
||||||
|
KSP_* / KSPB_* lookup
|
||||||
|
PGHOST / PGPORT / PGUSER / PGPASSWORD / PGDATABASE
|
||||||
|
.pgpass implicite
|
||||||
|
```
|
||||||
|
|
||||||
|
Auditer les bundles/resources Tauri existants : ne modifier les apps que si leur contrat de packaging Config exige réellement la nouvelle ressource. Aucun nouvel écran Store n'est ajouté.
|
||||||
|
|
||||||
|
### 7.4 Migrations et bootstrap
|
||||||
|
|
||||||
|
Le mécanisme retenu doit au minimum couvrir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ordre déterministe
|
||||||
|
identité/version de migration
|
||||||
|
checksum ou preuve équivalente contre modification silencieuse
|
||||||
|
application transactionnelle lorsque PostgreSQL le permet
|
||||||
|
verrouillage/concurrence de bootstrap
|
||||||
|
reprise sûre après échec
|
||||||
|
mismatch explicite
|
||||||
|
aucun SQL dynamique construit depuis input non fiable
|
||||||
|
introspection/version observable sans exposer les SQL internes publiquement
|
||||||
|
```
|
||||||
|
|
||||||
|
Les migrations sont privées à `ksp-store-postgres-lib`.
|
||||||
|
|
||||||
|
Aucune migration `RawTransaction` ou `RawAccountState` n'est créée dans cette release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Hors périmètre strict
|
||||||
|
|
||||||
|
Ne pas ouvrir dans `0.3.2` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
persistence PostgreSQL RawTransaction
|
||||||
|
persistence PostgreSQL RawTransactionObservation
|
||||||
|
queries RawTransaction PostgreSQL
|
||||||
|
retention/tombstone RawTransaction PostgreSQL
|
||||||
|
persistence PostgreSQL RawAccountState
|
||||||
|
persistence PostgreSQL RawAccountObservation
|
||||||
|
queries RawAccountState PostgreSQL
|
||||||
|
processing ledger
|
||||||
|
claims/leases de processing
|
||||||
|
compression/archive RAW physique
|
||||||
|
worker/job/backfill
|
||||||
|
application Store/inspection
|
||||||
|
notification event bus
|
||||||
|
LISTEN/NOTIFY comme mécanisme requis
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3 DECODED
|
||||||
|
N4 DOMAIN
|
||||||
|
Program/Materializer/Execution
|
||||||
|
backend MySQL/SQLite/Oracle/RocksDB/ClickHouse
|
||||||
|
```
|
||||||
|
|
||||||
|
Ne pas créer un faux repository CRUD « exemple » sur une table générique seulement pour démontrer le driver.
|
||||||
|
|
||||||
|
La preuve PostgreSQL de `0.3.2` porte sur la **fondation runtime/migrations**, pas sur une pseudo-entité temporaire qui deviendrait de la dette.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Contraintes sécurité/API/architecture spécifiques
|
||||||
|
|
||||||
|
### 9.1 Secrets et diagnostics
|
||||||
|
|
||||||
|
Aucune surface `Debug`, erreur publique, log ou snapshot ne doit révéler :
|
||||||
|
|
||||||
|
```text
|
||||||
|
URI PostgreSQL complète
|
||||||
|
password
|
||||||
|
userinfo
|
||||||
|
secret placeholder résolu
|
||||||
|
TLS private material
|
||||||
|
SQL parameter sensible
|
||||||
|
contenu brut d'une erreur distante pouvant reproduire une valeur secrète
|
||||||
|
```
|
||||||
|
|
||||||
|
Les erreurs KSP utilisent le contrat Core et un contexte sûr, stable et borné.
|
||||||
|
|
||||||
|
### 9.2 SQL et migrations
|
||||||
|
|
||||||
|
```text
|
||||||
|
requêtes statiques ou paramètres bindés
|
||||||
|
aucune interpolation de valeurs métier dans SQL
|
||||||
|
identifiants physiques non contrôlés par un input utilisateur
|
||||||
|
migrations embarquées/possédées par le backend
|
||||||
|
concurrence de migration sérialisée explicitement
|
||||||
|
checksum mismatch = erreur, jamais réécriture silencieuse
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 Runtime async
|
||||||
|
|
||||||
|
```text
|
||||||
|
async-first
|
||||||
|
aucun runtime global Store imposé
|
||||||
|
aucun spawn orphelin
|
||||||
|
shutdown borné
|
||||||
|
connection-driving futures correctement possédées
|
||||||
|
aucun mutex sync gardé à travers await
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 Logging
|
||||||
|
|
||||||
|
Si `ksp-store-lib` ou `ksp-store-postgres-lib` émettent des événements/spans runtime :
|
||||||
|
|
||||||
|
```text
|
||||||
|
utiliser ksp-logging-lib
|
||||||
|
pas de tracing direct hors façade KSP
|
||||||
|
ajouter constants.rs avec TRACING_TARGET selon les règles KSP
|
||||||
|
aucun credential/URI/SQL parameter dans les champs
|
||||||
|
```
|
||||||
|
|
||||||
|
Une crate qui n'émet aucun log n'ajoute pas Logging artificiellement.
|
||||||
|
|
||||||
|
### 9.5 Backend features
|
||||||
|
|
||||||
|
Le minimum à valider est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo check -p ksp-store-lib
|
||||||
|
cargo check -p ksp-store-lib --no-default-features
|
||||||
|
cargo tree -p ksp-store-lib --edges normal
|
||||||
|
cargo tree -p ksp-store-lib -e features
|
||||||
|
cargo tree -p ksp-store-postgres-lib --edges normal
|
||||||
|
```
|
||||||
|
|
||||||
|
`--no-default-features` doit produire un runtime Store sans backend disponible mais compilable ; tenter d'ouvrir `postgres` dans cet état doit produire l'erreur explicite retenue, pas un panic ni un fallback.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Première mission `0.3.2-pre.001`
|
||||||
|
|
||||||
|
`pre.001` ne code pas le runtime lourd. Il doit produire un dossier de décision exploitable.
|
||||||
|
|
||||||
|
### 10.1 Vérifier la base réelle
|
||||||
|
|
||||||
|
Inventorier :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace members
|
||||||
|
workspace dependencies
|
||||||
|
ksp-store-api exact
|
||||||
|
ksp-config-lib registry/adapters
|
||||||
|
Config Desk/resource packaging
|
||||||
|
Logging ownership
|
||||||
|
architecture Store
|
||||||
|
roadmap 0.3.2/0.3.3/0.3.4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 Réauditer l'héritage kbot3 ciblé
|
||||||
|
|
||||||
|
Relire au minimum dans l'archive historique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ks-store/Cargo.toml
|
||||||
|
ks-store/src/store.rs
|
||||||
|
ks-store/src/postgres/**
|
||||||
|
ks-store/migrations/postgres/**
|
||||||
|
config/store.config.json
|
||||||
|
config/schemas/store.config.schema.json
|
||||||
|
ks-config/src/store.rs
|
||||||
|
docs/architecture/STORAGE_ARCHITECTURE.md
|
||||||
|
docs/guides/POSTGRES_STORAGE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Classer sous :
|
||||||
|
|
||||||
|
```text
|
||||||
|
REPRENDRE
|
||||||
|
REDESSINER
|
||||||
|
REPORTER
|
||||||
|
REJETER
|
||||||
|
```
|
||||||
|
|
||||||
|
Porter l'attention sur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
open options
|
||||||
|
pool/lifecycle
|
||||||
|
TLS
|
||||||
|
migrations/schema version/checksum
|
||||||
|
locking
|
||||||
|
health/readiness
|
||||||
|
redaction/error mapping
|
||||||
|
Config ownership
|
||||||
|
maintenance
|
||||||
|
SQL naming
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 Réauditer PostgreSQL et dependencies actuelles
|
||||||
|
|
||||||
|
Vérifier :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgreSQL stable/current support policy
|
||||||
|
tokio-postgres version/features/MSRV
|
||||||
|
Tokio features réellement nécessaires
|
||||||
|
pool candidates
|
||||||
|
TLS connector candidates
|
||||||
|
migration helper candidates si utile
|
||||||
|
licences
|
||||||
|
versions transitives/doublons
|
||||||
|
```
|
||||||
|
|
||||||
|
Ne pas décider un pool ou TLS connector uniquement parce qu'il est populaire.
|
||||||
|
|
||||||
|
### 10.4 Produire le design de fondation
|
||||||
|
|
||||||
|
Le gate doit proposer puis figer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
graphe Cargo exact
|
||||||
|
features exactes de ksp-store-lib
|
||||||
|
StoreSettings / backend identity
|
||||||
|
Store open/close lifecycle
|
||||||
|
backend dispatch
|
||||||
|
known-but-not-compiled error
|
||||||
|
Postgres backend construction
|
||||||
|
pool/lifecycle
|
||||||
|
TLS policy
|
||||||
|
migration/bootstrap architecture
|
||||||
|
Config std.store shape
|
||||||
|
redaction/error codes
|
||||||
|
real PostgreSQL test strategy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.5 Threat model
|
||||||
|
|
||||||
|
Couvrir au minimum :
|
||||||
|
|
||||||
|
```text
|
||||||
|
credential leak URI/Debug/error/log
|
||||||
|
implicit PG* / .pgpass bypassing Config
|
||||||
|
malicious/invalid connection string
|
||||||
|
connection storm / unbounded pool
|
||||||
|
hung connect / migration / shutdown
|
||||||
|
concurrent migration runners
|
||||||
|
modified historical migration
|
||||||
|
partial migration
|
||||||
|
SQL injection / dynamic identifier injection
|
||||||
|
backend feature/config mismatch
|
||||||
|
connection task dropped/leaked
|
||||||
|
schema incompatible/newer than runtime
|
||||||
|
PostgreSQL server error echoing values
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.6 Sizing
|
||||||
|
|
||||||
|
Le gate doit vérifier que `0.3.2` reste clôturable dans une session avec **fondation uniquement**.
|
||||||
|
|
||||||
|
Si pooling + TLS + Config + migrations + integration réelle dépassent encore la capacité raisonnable d'une session, redécouper **avant** l'implémentation lourde. Ne jamais absorber `RawTransaction` pour « rentabiliser » la release.
|
||||||
|
|
||||||
|
### 10.7 Critères de sortie de `pre.001`
|
||||||
|
|
||||||
|
`pre.001` est terminé seulement si :
|
||||||
|
|
||||||
|
```text
|
||||||
|
sources obligatoires lues
|
||||||
|
base stable vérifiée
|
||||||
|
audit kbot3 ciblé documenté
|
||||||
|
audit PostgreSQL/tokio-postgres actuel documenté
|
||||||
|
pool/TLS/migrations questions tranchées ou explicitement réservées à une tranche précise
|
||||||
|
graphe Cargo exact proposé
|
||||||
|
Config shape candidate bornée
|
||||||
|
threat model complet
|
||||||
|
stratégie de PostgreSQL integration test définie
|
||||||
|
plan 023 créé
|
||||||
|
validation 019 créée
|
||||||
|
prévision souple recalibrée
|
||||||
|
aucun RawTransaction/RawAccountState PostgreSQL tiré dans 0.3.2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Prévision souple initiale des prereleases
|
||||||
|
|
||||||
|
Cette prévision est volontairement fine. `pre.001` peut la scinder/réordonner si l'audit le justifie.
|
||||||
|
|
||||||
|
### `pre.001` — Audit, threat model, dependencies et sizing
|
||||||
|
|
||||||
|
Lecture complète, audit kbot3 ciblé, audit PostgreSQL/tokio-postgres/pool/TLS/migrations, design Config/runtime/backend, graphe exact, tests et plan.
|
||||||
|
|
||||||
|
### `pre.002` — Scaffold des deux crates + feature graph
|
||||||
|
|
||||||
|
Créer `ksp-store-lib` et `ksp-store-postgres-lib`, manifests, modules privés minimaux, dépendances retenues, feature `postgres` par défaut, compilation `--no-default-features`, canaris de direction de dépendances. Pas encore de connexion réelle lourde.
|
||||||
|
|
||||||
|
### `pre.003` — Store settings + backend selection/lifecycle contract
|
||||||
|
|
||||||
|
Matérialiser les settings backend-neutral, identité backend, erreurs stable known/not-compiled, façade `Store` minimale et reexports API utiles. Aucun SQL métier.
|
||||||
|
|
||||||
|
### `pre.004` — Config `std.store`
|
||||||
|
|
||||||
|
Ajouter document/schema/example/registry/adaptor Config, secrets/provenance/redaction et impacts de packaging strictement nécessaires. Store/backend restent incapables de lire l'environnement.
|
||||||
|
|
||||||
|
### `pre.005` — PostgreSQL connection + pool + TLS
|
||||||
|
|
||||||
|
Implémenter la construction backend PostgreSQL, connect/open/close, pool retenu, timeouts et TLS retenu, avec tests déterministes sans table RAW métier.
|
||||||
|
|
||||||
|
### `pre.006` — Migration/bootstrap foundation
|
||||||
|
|
||||||
|
Implémenter ownership des migrations, version/checksum, serialization/locking, transaction/recovery et introspection minimale. Une table/namespace interne de migration est autorisée ; aucune table `RawTransaction`/`RawAccountState`.
|
||||||
|
|
||||||
|
### `pre.007` — Composition façade/backend + diagnostics/health si retenu
|
||||||
|
|
||||||
|
Fermer l'ouverture end-to-end `StoreSettings -> Store -> backend`, shutdown, error mapping, snapshots/health portable seulement si le gate `pre.001` l'a justifié, et feature mismatch.
|
||||||
|
|
||||||
|
### `pre.008` — PostgreSQL integration réelle
|
||||||
|
|
||||||
|
Test opt-in non destructif sur PostgreSQL réel : connexion, bootstrap initial, re-run idempotent, concurrence migration, mismatch/checksum/recovery selon stratégie retenue, close propre. Aucun test ne doit exiger une table RAW métier.
|
||||||
|
|
||||||
|
### `pre.009` — Hardening, completeness et dependency matrix
|
||||||
|
|
||||||
|
Inputs hostiles, redaction, no-env, no-SQL-leak, exact exports/modules, `--no-default-features`, external backend compatibility, graphes/features Cargo et non-régression de `ksp-store-api`.
|
||||||
|
|
||||||
|
### `pre.010` — Gate technique final
|
||||||
|
|
||||||
|
Workspace, tests ciblés, ownership Logging, PostgreSQL integration gate retenu et graphes Cargo. Aucun développement fonctionnel nouveau.
|
||||||
|
|
||||||
|
### `pre.011` — Réconciliation documentaire finale
|
||||||
|
|
||||||
|
README/USAGE des deux crates, plan, validation, architecture/indexes réellement impactés. Ne pas toucher `CHANGELOG.md`, `ROADMAP.md` ni au prompt suivant.
|
||||||
|
|
||||||
|
### `pre.012` — Préparation de publication minimale
|
||||||
|
|
||||||
|
Uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompts/022-V0_3_3_START_PROMPT.md
|
||||||
|
delta pre.012
|
||||||
|
```
|
||||||
|
|
||||||
|
### `rel.001` — Publication stable
|
||||||
|
|
||||||
|
Version finale + delta uniquement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Versionnement, deltas, commits et tags
|
||||||
|
|
||||||
|
Respecter `docs/rules/VERSION_WORKFLOW.md`.
|
||||||
|
|
||||||
|
Rappels :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version prérelease : 0.3.2-pre.N
|
||||||
|
livraison : 0.3.2-pre.NNN
|
||||||
|
fix de code/runtime : Cargo 0.3.2-pre.N.fix.M
|
||||||
|
fix doc-only : version Cargo inchangée
|
||||||
|
chaque delta commité à partir de 0.1.x
|
||||||
|
aucun tag prerelease requis
|
||||||
|
tag stable final : v0.3.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Chaque delta contient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
base requise
|
||||||
|
objectif
|
||||||
|
fichiers ajoutés/modifiés/supprimés
|
||||||
|
validations exécutées
|
||||||
|
validations non exécutées
|
||||||
|
décisions
|
||||||
|
questions ouvertes
|
||||||
|
```
|
||||||
|
|
||||||
|
Une commande non exécutée n'est jamais déclarée PASS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Procédure d'application et validation opérateur
|
||||||
|
|
||||||
|
Après chaque overlay :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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/0.3.2
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis tests ciblés selon la tranche, typiquement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
cargo test -p ksp-store-lib
|
||||||
|
cargo test -p ksp-store-postgres-lib
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Lorsque le graphe/features change :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo check -p ksp-store-lib --no-default-features
|
||||||
|
cargo tree -p ksp-store-lib --edges normal
|
||||||
|
cargo tree -p ksp-store-lib -e features
|
||||||
|
cargo tree -p ksp-store-postgres-lib --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate technique final inclut `cargo test --workspace`.
|
||||||
|
|
||||||
|
Ne pas refaire systématiquement les builds Tauri à chaque tranche. Les exécuter uniquement si les resources/configs desktop ou le packaging réellement touché le justifient, et lors d'un gate final où cette preuve est pertinente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Validations PostgreSQL spécifiques
|
||||||
|
|
||||||
|
La release doit disposer avant fermeture d'un test PostgreSQL réel, opt-in et sûr.
|
||||||
|
|
||||||
|
Le design exact appartient à `pre.001`, mais les invariants sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun credential commité
|
||||||
|
aucune lecture directe env par Store/backend
|
||||||
|
input opérateur explicite ou fixture locale dédiée
|
||||||
|
aucune destruction d'une base/schema non créé par le test
|
||||||
|
cleanup best-effort borné
|
||||||
|
bootstrap initial prouvé
|
||||||
|
second bootstrap idempotent prouvé
|
||||||
|
concurrence de bootstrap/migration prouvée
|
||||||
|
failure/mismatch safe selon stratégie retenue
|
||||||
|
close/shutdown prouvé
|
||||||
|
```
|
||||||
|
|
||||||
|
Si le test utilise stdin comme les smokes credentials KSP existants, ne jamais afficher la valeur fournie.
|
||||||
|
|
||||||
|
Aucune connexion live n'est exigée pour les prereleases purement documentaires.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Critères de clôture de `0.3.2`
|
||||||
|
|
||||||
|
La release stable est prête seulement si :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib existe et dépend de ksp-store-api
|
||||||
|
ksp-store-postgres-lib existe et dépend de ksp-store-api
|
||||||
|
ksp-store-postgres-lib ne dépend pas de ksp-store-lib
|
||||||
|
feature postgres de ksp-store-lib activée par défaut
|
||||||
|
--no-default-features compile
|
||||||
|
backend postgres connu mais non compilé est rejeté explicitement
|
||||||
|
StoreSettings ne dépend pas de Config
|
||||||
|
ksp-config-lib possède std.store + schema/example/adaptor retenus
|
||||||
|
Store/backend ne lisent aucun env/.env/PG*/.pgpass
|
||||||
|
connexion/pool/TLS PostgreSQL sont bornés et redacted
|
||||||
|
migrations/bootstrap privés sont versionnés et concurrency-safe
|
||||||
|
aucune table RAW métier n'est ajoutée
|
||||||
|
aucune capability RawTransaction n'est implémentée par PostgreSQL
|
||||||
|
aucune capability RawAccountState n'est implémentée par PostgreSQL
|
||||||
|
aucune policy batch/backlog n'entre dans Store
|
||||||
|
aucun type PostgreSQL/SQL/pool ne fuit dans la façade publique
|
||||||
|
ksp-store-api reste compatible et sans dépendance backend
|
||||||
|
README/USAGE des deux crates sont durables
|
||||||
|
PostgreSQL integration réelle est verte
|
||||||
|
workspace/clippy/tests/graphes sont verts
|
||||||
|
prompt 0.3.3 réserve clairement la vertical slice RawTransaction
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Release suivante et instruction d'ouverture
|
||||||
|
|
||||||
|
La release suivante envisagée est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.3 — Store/PostgreSQL RawTransaction vertical slice
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle doit réutiliser les **mêmes** :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
Store settings
|
||||||
|
backend dispatch
|
||||||
|
pool/TLS
|
||||||
|
migration engine
|
||||||
|
```
|
||||||
|
|
||||||
|
et ajouter seulement la conformance PostgreSQL `RawTransaction`/observation/query/rétention définie par `ksp-store-api`.
|
||||||
|
|
||||||
|
`0.3.4` restera propriétaire de `RawAccountState` + complétude RAW.
|
||||||
|
|
||||||
|
### Instruction d'ouverture
|
||||||
|
|
||||||
|
À réception de la base stable `v0.3.1` et de l'archive historique requise :
|
||||||
|
|
||||||
|
1. vérifier la base exacte ;
|
||||||
|
2. lire les règles/architecture/plan/validation dans l'ordre indiqué ;
|
||||||
|
3. réauditer les versions et sources PostgreSQL/tokio-postgres actuelles ;
|
||||||
|
4. réauditer kbot3 uniquement sur la fondation physique pertinente ;
|
||||||
|
5. produire brainstorming, threat model, graphe Cargo, décisions pool/TLS/migrations/Config et sizing ;
|
||||||
|
6. créer le plan `023` et la validation `019` ;
|
||||||
|
7. **ne pas commencer l'implémentation lourde avant validation cohérente du gate `pre.001`** ;
|
||||||
|
8. **ne pas implémenter RawTransaction ou RawAccountState PostgreSQL dans `0.3.2`**.
|
||||||
Reference in New Issue
Block a user