Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cc94ebb46 | |||
| 8e8f1f0b4a | |||
| 78e015413b | |||
| 2f0eb316f5 | |||
| 55325ffd32 | |||
| ad02b6f13c | |||
| 47b12e14e6 | |||
| c43d4d7c38 | |||
| b76baf6835 | |||
| c83e3261d0 | |||
| 28ca5bdac5 | |||
| afd4c770f9 | |||
| ecc5607fd4 | |||
| 11e4cde77f | |||
| 62ed72f2b5 | |||
| 1a07574bae | |||
| 29a888d1dc | |||
| 3d307be645 | |||
| 7dfe892467 | |||
| 2cf00e9426 | |||
| 4eba9be48c | |||
| bebfc63b75 | |||
| 0319794e59 | |||
| bcf2f16c05 | |||
| 27d4cb1f36 | |||
| c1f61a380f | |||
| 5e5ed6d1ab |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,8 +1,24 @@
|
|||||||
<!-- file: CHANGELOG.md -->
|
<!-- file: CHANGELOG.md -->
|
||||||
<!-- version: 17 -->
|
<!-- 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` 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.
|
||||||
|
|
||||||
|
Le graphe normal final reste strictement `ksp-program-api -> ksp-core-lib + ksp-interface-lib`, Interface dépendant elle-même de Core. Aucun `ksp-program-lib`, codec, serde, logging, réseau, filesystem, environnement, Store, Materializer, Wallet, Config ou Tauri n’entre dans cette foundation. Les canaris public API, implémentation externe, dependency firewall, release completeness et hardening couvrent notamment l’inventaire exact de dix exports crate-root, les trois modules de production, un Program Pubkey non enregistré, l’input Interface maximal de 255 accounts / 10 240 bytes, l’absence d’echo automatique d’un payload hostile et l’absence de claim `dyn` hétérogène. `pre.005-fix.001` corrige uniquement un faux positif cross-crate du scanner Logging provoqué par le motif de test recherché, sans changement fonctionnel. Les gates `pre.006` et `pre.007` passent ensuite audits Rust/Markdown, `cargo check`, Clippy, les 18 tests Program API, ownership Logging et le workspace complet avant la réconciliation documentaire finale.
|
||||||
|
|
||||||
|
`prompts/020-V0_3_1_START_PROMPT.md` ouvre `0.3.1 — Store RAW foundation` exclusivement depuis le tag stable `v0.2.14`. Le gate `pre.001` exige également l’archive historique `khadhroony-bot3_v0.5.3-pre.005-fix010.zip` afin d’auditer l’ancien `ks-store`, ses migrations PostgreSQL et ses contrats N1/N2/N3 sous une matrice `REPRENDRE / REDESSINER / REPORTER / REJETER`. L’archive reste une source historique uniquement : `0.3.1` doit créer `ksp-store-api` et `ksp-store-lib` avec PostgreSQL de référence et persistence **RAW seulement**, sans aspirer les contrats CORE/DECODE/SPECIALIZED, les jobs/workers, l’Interface `0.3.2` ni la configuration produit.
|
||||||
|
|
||||||
## 0.2.13 — Interface / wire foundation — 2026-08-28
|
## 0.2.13 — Interface / wire foundation — 2026-08-28
|
||||||
|
|
||||||
`0.2.13` introduit `ksp-interface-lib` comme première façade wire officielle KSP, volontairement passive et Program-facing. La surface stable réexporte le `Pubkey` canonique de Core, ajoute `ProgramAccountMeta` et `ProgramInstruction` à champs privés avec accessors explicites, conserve l’ordre et les doublons des account metas, accepte les Program Pubkeys opaques et borne l’admission à **255 account metas** et **10 240 octets** de data. Les deux bornes sont des limites d’admission Interface et ne prétendent pas garantir à elles seules le fit d’une transaction Solana top-level. Les erreurs réutilisent le contrat Core `Error/Result` avec uniquement `field`, `actual_len` et `maximum_len`, tandis que le `Debug` de l’instruction n’expose que `program_id`, `account_count` et `data_len`.
|
`0.2.13` introduit `ksp-interface-lib` comme première façade wire officielle KSP, volontairement passive et Program-facing. La surface stable réexporte le `Pubkey` canonique de Core, ajoute `ProgramAccountMeta` et `ProgramInstruction` à champs privés avec accessors explicites, conserve l’ordre et les doublons des account metas, accepte les Program Pubkeys opaques et borne l’admission à **255 account metas** et **10 240 octets** de data. Les deux bornes sont des limites d’admission Interface et ne prétendent pas garantir à elles seules le fit d’une transaction Solana top-level. Les erreurs réutilisent le contrat Core `Error/Result` avec uniquement `field`, `actual_len` et `maximum_len`, tandis que le `Debug` de l’instruction n’expose que `program_id`, `account_count` et `data_len`.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 312
|
# 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-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.13"
|
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"
|
||||||
|
|||||||
61
ROADMAP.md
61
ROADMAP.md
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: ROADMAP.md -->
|
<!-- file: ROADMAP.md -->
|
||||||
<!-- version: 91 -->
|
<!-- version: 94 -->
|
||||||
|
|
||||||
# Roadmap KSP
|
# Roadmap KSP
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
|
|||||||
- [X] `0.2.11` — Off-chain price transport stable : `ksp-offchain-transport-lib` expose SOL/USD via huit adapters REST `reqwest` sans SDK provider, décimal exact, sémantiques/provenance explicites, registry/availability/rate limits et refresh single/many/all génériques ; Config `std.offchain_transport` construit le service sans dépendance inverse, DexScreener reste lié à une paire explicite sans discovery, aucun consensus/fallback automatique n’est introduit, et le smoke live keyless final passe 7/7 après correction CoinMarketCap V2.
|
- [X] `0.2.11` — Off-chain price transport stable : `ksp-offchain-transport-lib` expose SOL/USD via huit adapters REST `reqwest` sans SDK provider, décimal exact, sémantiques/provenance explicites, registry/availability/rate limits et refresh single/many/all génériques ; Config `std.offchain_transport` construit le service sans dépendance inverse, DexScreener reste lié à une paire explicite sans discovery, aucun consensus/fallback automatique n’est introduit, et le smoke live keyless final passe 7/7 après correction CoinMarketCap V2.
|
||||||
- [X] `0.2.12` — SOL Prices Desk + projection prix Wallet stables : HID provider-neutral avec refresh row/selected/all et batch `1..=64`, observations/timestamps exacts sans polling/consensus, puis refresh balance Wallet enrichi d’une moyenne SOL/USD consumer-owned et d’un équivalent USD exact best-effort ; smoke live de composition, workspace complet et bundles Tauri Linux validés.
|
- [X] `0.2.12` — SOL Prices Desk + projection prix Wallet stables : HID provider-neutral avec refresh row/selected/all et batch `1..=64`, observations/timestamps exacts sans polling/consensus, puis refresh balance Wallet enrichi d’une moyenne SOL/USD consumer-owned et d’un équivalent USD exact best-effort ; smoke live de composition, workspace complet et bundles Tauri Linux validés.
|
||||||
- [X] `0.2.13` — Interface / wire foundation stable : `ksp-interface-lib` expose `Pubkey`, `ProgramAccountMeta` et `ProgramInstruction` passifs, admission bornée à 255 account metas / 10 240 bytes, erreurs et `Debug` sans payload hostile, façade crate-root et consumer externe canaris ; graphe strict `Interface -> Core`, sans serde/codec générique, `solana-instruction`, réseau ni logging runtime.
|
- [X] `0.2.13` — Interface / wire foundation stable : `ksp-interface-lib` expose `Pubkey`, `ProgramAccountMeta` et `ProgramInstruction` passifs, admission bornée à 255 account metas / 10 240 bytes, erreurs et `Debug` sans payload hostile, façade crate-root et consumer externe canaris ; graphe strict `Interface -> Core`, sans serde/codec générique, `solana-instruction`, réseau ni logging runtime.
|
||||||
- [ ] `0.2.14` — Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet ; `pre.001` doit auditer la base stable `v0.2.13` et l’archive historique `khadhroony-bot3_v0.5.3-pre.005-fix010.zip`, puis classer reconnaissance/decode/outcomes/proofs/préparation sous `REPRENDRE / REDESSINER / REPORTER / REJETER` avant de figer la surface.
|
- [X] `0.2.14` — Program API foundation stable : `ksp-program-api` expose une surface instruction-only ouverte avec `ProgramInstructionRecognition`, `ProgramInstructionDecodeOutcome<Decoded>` et `ProgramInstructionDecoder`; implémentation externe avec Program Pubkey opaque validée, 10 exports crate-root / 3 modules de production verrouillés, graphe strict Core + Interface, sans registry/runtime/serde/codec/Store/Materializer/Execution. Le prompt `0.3.1` prépare Store RAW-only.
|
||||||
|
|
||||||
### TODO/IDEAS — providers Yellowstone non planifiés
|
### TODO/IDEAS — providers Yellowstone non planifiés
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
15
crates/ksp-program-api/Cargo.toml
Normal file
15
crates/ksp-program-api/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# file: crates/ksp-program-api/Cargo.toml
|
||||||
|
# version: 1
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "ksp-program-api"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||||
|
ksp-interface-lib = { path = "../ksp-interface-lib" }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
156
crates/ksp-program-api/README.md
Normal file
156
crates/ksp-program-api/README.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<!-- file: crates/ksp-program-api/README.md -->
|
||||||
|
<!-- version: 4 -->
|
||||||
|
|
||||||
|
# ksp-program-api
|
||||||
|
|
||||||
|
`ksp-program-api` est la façade publique ouverte du domaine Program KSP. Elle porte uniquement les contrats communs nécessaires aux implémentations Program officielles futures comme aux crates externes ; elle ne possède aucun decoder concret, registry runtime, payload canonique DECODE ni logique d'exécution.
|
||||||
|
|
||||||
|
La surface candidate de `0.2.14` est volontairement **instruction-only** et reste indépendante des couches runtime supérieures.
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
Le graphe normal est limité à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Core reste propriétaire de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Error
|
||||||
|
ErrorCode
|
||||||
|
ErrorContext
|
||||||
|
Result
|
||||||
|
Pubkey
|
||||||
|
```
|
||||||
|
|
||||||
|
Interface reste propriétaire de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramAccountMeta
|
||||||
|
ProgramInstruction
|
||||||
|
```
|
||||||
|
|
||||||
|
Program API possède :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
ProgramInstructionDecoder
|
||||||
|
```
|
||||||
|
|
||||||
|
`ksp-program-api` réexporte toute sa surface consommable depuis son crate-root. Aucun module interne n'est public.
|
||||||
|
|
||||||
|
## Surface publique candidate
|
||||||
|
|
||||||
|
L'inventaire crate-root de `0.2.14` contient exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Error
|
||||||
|
ErrorCode
|
||||||
|
ErrorContext
|
||||||
|
Result
|
||||||
|
Pubkey
|
||||||
|
ProgramAccountMeta
|
||||||
|
ProgramInstruction
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
ProgramInstructionDecoder
|
||||||
|
```
|
||||||
|
|
||||||
|
Les canaris de release completeness verrouillent cet inventaire ainsi que les trois fichiers/modules Rust de production de la crate.
|
||||||
|
|
||||||
|
## Recognition
|
||||||
|
|
||||||
|
`ProgramInstructionRecognition` est `#[non_exhaustive]` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
NoMatch l'implémentation ne revendique pas l'instruction
|
||||||
|
ProgramMatch le Program correspond, sans reconnaissance instruction-local exacte
|
||||||
|
ExactMatch l'implémentation affirme un match instruction-local exact
|
||||||
|
```
|
||||||
|
|
||||||
|
La reconnaissance ne porte aucun score, priorité, proof, confidence, discriminator textuel ni inventaire central de Programs. `ExactMatch` reste une affirmation de l'implémentation, pas une preuve indépendante produite par KSP.
|
||||||
|
|
||||||
|
## Decode outcome
|
||||||
|
|
||||||
|
`ProgramInstructionDecodeOutcome<Decoded>` est `#[non_exhaustive]` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Decoded(Decoded) valeur typée possédée par l'implémentation
|
||||||
|
Unsupported instruction reconnue mais non supportée par cette capability
|
||||||
|
```
|
||||||
|
|
||||||
|
Les échecs réels utilisent le `Result` Core. Il n'existe aucune variante parallèle `Failed` ou `Ignored`.
|
||||||
|
|
||||||
|
Le `Debug` de l'outcome n'impose pas `Decoded: Debug` et n'affiche jamais la valeur `Decoded`.
|
||||||
|
|
||||||
|
## Decoder instruction-only
|
||||||
|
|
||||||
|
`ProgramInstructionDecoder` est un trait ouvert `Send + Sync` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction) -> ProgramInstructionRecognition
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
```
|
||||||
|
|
||||||
|
`Decoded` ne reçoit aucun bound implicite supplémentaire : une implémentation reste propriétaire de son type de sortie concret. Le trait ne fournit aucun default method et ne promet pas de composition `dyn` hétérogène.
|
||||||
|
|
||||||
|
`program_ids()` expose des `Pubkey` opaques ; une implémentation externe peut prendre en charge un Program ID absent du registry Core. Aucun enum central, `Any`, JSON ou descriptor global n'est nécessaire.
|
||||||
|
|
||||||
|
`program_ids` et `recognize` servent à la sélection explicite. `decode` consomme par référence une `ProgramInstruction` déjà admise/bornée par Interface et déjà sélectionnée pour le decoder.
|
||||||
|
|
||||||
|
## Hardening validé
|
||||||
|
|
||||||
|
La candidate verrouille notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Program Pubkey non enregistré accepté
|
||||||
|
input Interface maximal 255 accounts + 10_240 bytes accepté à la frontière decoder
|
||||||
|
payload hostile aucun echo automatique ajouté par Program API
|
||||||
|
Debug outcome valeur Decoded jamais rendue
|
||||||
|
associated Decoded aucun Debug/Clone/Send/Sync imposé
|
||||||
|
closed-world Program enum absent
|
||||||
|
registry / descriptors / priority absents
|
||||||
|
ProgramExecutionPreparer absent
|
||||||
|
serde / JSON / Any / codecs absents
|
||||||
|
logging / runtime / filesystem / environment / I/O absents
|
||||||
|
```
|
||||||
|
|
||||||
|
Une implémentation tierce reste responsable du contenu des erreurs qu'elle construit explicitement. `ksp-program-api` garantit seulement qu'il n'ajoute aucun canal parallèle ni copie automatique du payload d'entrée.
|
||||||
|
|
||||||
|
## Frontières
|
||||||
|
|
||||||
|
La foundation `0.2.14` ne contient pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
registry runtime
|
||||||
|
identity/version/coverage de decoder
|
||||||
|
payload canonique D3
|
||||||
|
ProgramAccountDecoder
|
||||||
|
ProgramEventDecoder
|
||||||
|
ProgramReturnDataDecoder
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
serde / serde_json
|
||||||
|
borsh / wincode / bincode
|
||||||
|
solana-instruction
|
||||||
|
network / async runtime
|
||||||
|
logging / tracing
|
||||||
|
Wallet / Transport / Store / Materializer / Config / Tauri
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces surfaces restent reportées jusqu'aux vertical slices qui démontreront leurs contrats réels. En particulier, les codecs wire officiels restent possédés par `ksp-interface-lib` et ne sont introduits qu'en présence d'un protocole réel.
|
||||||
|
|
||||||
|
## Références
|
||||||
|
|
||||||
|
- [Usage public](USAGE.md)
|
||||||
|
- [Plan `0.2.14`](../../docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md)
|
||||||
|
- [Validation `0.2.14`](../../docs/validation/017-V0_2_14_PROGRAM_API.md)
|
||||||
|
- [Architecture Wire + Program](../../docs/architecture/006-WIRE_AND_PROGRAM.md)
|
||||||
166
crates/ksp-program-api/USAGE.md
Normal file
166
crates/ksp-program-api/USAGE.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<!-- file: crates/ksp-program-api/USAGE.md -->
|
||||||
|
<!-- version: 4 -->
|
||||||
|
|
||||||
|
# Usage de ksp-program-api
|
||||||
|
|
||||||
|
Cette page décrit la surface publique candidate de `0.2.14`. Utiliser uniquement les exports du crate-root ; aucun module interne ne fait partie du contrat consommable.
|
||||||
|
|
||||||
|
## Construire un input Program avec la façade
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let program_id = ksp_program_api::Pubkey::new_from_array([1_u8; 32]);
|
||||||
|
let account_id = ksp_program_api::Pubkey::new_from_array([2_u8; 32]);
|
||||||
|
let account = ksp_program_api::ProgramAccountMeta::readonly(account_id, true);
|
||||||
|
|
||||||
|
let instruction = ksp_program_api::ProgramInstruction::try_new(
|
||||||
|
program_id,
|
||||||
|
std::vec![account],
|
||||||
|
std::vec![0x01_u8, 0x02, 0x03],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
```
|
||||||
|
|
||||||
|
`Pubkey`, `ProgramAccountMeta` et `ProgramInstruction` conservent leur ownership Core/Interface. Les bornes `255` account metas et `10_240` bytes de data sont appliquées par Interface avant l'entrée dans le decoder.
|
||||||
|
|
||||||
|
## Implémenter un decoder externe
|
||||||
|
|
||||||
|
Le type décodé reste entièrement possédé par la crate d'implémentation :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct ExternalDecodedInstruction {
|
||||||
|
opcode: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExternalDecoder {
|
||||||
|
program_ids: [ksp_program_api::Pubkey; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for ExternalDecoder {
|
||||||
|
type Decoded = ExternalDecodedInstruction;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &self.program_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(
|
||||||
|
&self,
|
||||||
|
instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
if instruction.program_id() != &self.program_ids[0] {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if instruction.data().first() == std::option::Option::Some(&0x2A_u8) {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ExactMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
let opcode = match instruction.data().first() {
|
||||||
|
std::option::Option::Some(value) if *value == 0x2A_u8 => *value,
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Ok(
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return std::result::Result::Ok(
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(
|
||||||
|
ExternalDecodedInstruction { opcode },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `ksp-program-lib`, enum centrale, `Any`, JSON ou codec n'est nécessaire. Le Program ID peut être un `Pubkey` opaque non enregistré par Core.
|
||||||
|
|
||||||
|
## Sélection explicite
|
||||||
|
|
||||||
|
La sélection reste distincte du décodage :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let recognition = ksp_program_api::ProgramInstructionDecoder::recognize(
|
||||||
|
&decoder,
|
||||||
|
&instruction,
|
||||||
|
);
|
||||||
|
|
||||||
|
match recognition {
|
||||||
|
ksp_program_api::ProgramInstructionRecognition::NoMatch => {}
|
||||||
|
ksp_program_api::ProgramInstructionRecognition::ProgramMatch => {}
|
||||||
|
ksp_program_api::ProgramInstructionRecognition::ExactMatch => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
L'enum est `#[non_exhaustive]`. `ExactMatch` exprime l'affirmation du decoder. `decode` n'est pas un substitut à `recognize` : il traite une instruction déjà sélectionnée pour cette implémentation.
|
||||||
|
|
||||||
|
## Outcome et erreur
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(
|
||||||
|
&decoder,
|
||||||
|
&instruction,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match outcome {
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => {
|
||||||
|
let _opcode = value.opcode;
|
||||||
|
}
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Une erreur réelle est un `Err(ksp_program_api::Error)`. `Unsupported` n'est pas une deuxième forme d'erreur : il indique qu'une instruction reconnue n'est volontairement pas décodée par cette capability.
|
||||||
|
|
||||||
|
Program API ne recopie automatiquement ni le payload de l'instruction ni les account metas dans l'erreur. Une implémentation externe reste responsable des messages/contextes qu'elle construit explicitement.
|
||||||
|
|
||||||
|
## Output sans bounds implicites
|
||||||
|
|
||||||
|
L'associated type `Decoded` n'impose pas `Debug`, `Clone`, `Send` ou `Sync`. Les supertraits `Send + Sync` s'appliquent au decoder lui-même, pas à la valeur décodée :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct LocalDecoded(std::rc::Rc<std::cell::Cell<u8>>);
|
||||||
|
```
|
||||||
|
|
||||||
|
Un decoder peut utiliser ce type comme `Decoded` tant que son propre état satisfait `Send + Sync`.
|
||||||
|
|
||||||
|
## Debug sûr
|
||||||
|
|
||||||
|
`ProgramInstructionDecodeOutcome<Decoded>` possède un `Debug` volontairement opaque :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct SecretDecoded;
|
||||||
|
|
||||||
|
let outcome = ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(SecretDecoded);
|
||||||
|
assert_eq!(std::format!("{outcome:?}"), "Decoded");
|
||||||
|
```
|
||||||
|
|
||||||
|
`SecretDecoded` n'a pas besoin d'implémenter `Debug` et sa valeur n'est jamais rendue par l'outcome.
|
||||||
|
|
||||||
|
## Ce qui n'est pas simulé côté consumer
|
||||||
|
|
||||||
|
Il n'existe dans `0.2.14` aucun :
|
||||||
|
|
||||||
|
```text
|
||||||
|
registry de decoders
|
||||||
|
composition dyn hétérogène
|
||||||
|
identity/version/coverage descriptor
|
||||||
|
priority/conflict policy
|
||||||
|
payload canonique D3
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
execution policy
|
||||||
|
serde / JSON / codec
|
||||||
|
logging / runtime réseau
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces surfaces ne doivent pas être recréées localement comme si elles faisaient déjà partie du contrat commun. Elles attendent les vertical slices qui justifieront leurs invariants réels.
|
||||||
37
crates/ksp-program-api/src/lib.rs
Normal file
37
crates/ksp-program-api/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// file: crates/ksp-program-api/src/lib.rs
|
||||||
|
// version: 3
|
||||||
|
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![deny(unreachable_pub)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
//! Open Program contracts shared by KSP and external Program implementations.
|
||||||
|
//!
|
||||||
|
//! The foundation exposes Core/Interface types plus the minimal instruction
|
||||||
|
//! recognition/decode vocabulary and the instruction decoder trait. Registries,
|
||||||
|
//! codecs, runtime logging and execution preparation remain outside this
|
||||||
|
//! foundation until their ownership is justified.
|
||||||
|
|
||||||
|
mod program_instruction_decode;
|
||||||
|
mod program_instruction_decoder;
|
||||||
|
|
||||||
|
/// Result of a successful Program instruction decode attempt.
|
||||||
|
pub use self::program_instruction_decode::ProgramInstructionDecodeOutcome;
|
||||||
|
/// Recognition strength reported by one Program instruction implementation.
|
||||||
|
pub use self::program_instruction_decode::ProgramInstructionRecognition;
|
||||||
|
/// Open contract implemented by one Program instruction decoder.
|
||||||
|
pub use self::program_instruction_decoder::ProgramInstructionDecoder;
|
||||||
|
/// Common KSP error type used by Program-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 owned by `ksp-core-lib`.
|
||||||
|
pub use ksp_core_lib::Pubkey;
|
||||||
|
/// Common KSP result alias using [`Error`].
|
||||||
|
pub use ksp_core_lib::Result;
|
||||||
|
/// Passive account metadata attached to one Program instruction.
|
||||||
|
pub use ksp_interface_lib::ProgramAccountMeta;
|
||||||
|
/// Passive, bounded Program instruction wire contract.
|
||||||
|
pub use ksp_interface_lib::ProgramInstruction;
|
||||||
50
crates/ksp-program-api/src/program_instruction_decode.rs
Normal file
50
crates/ksp-program-api/src/program_instruction_decode.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// file: crates/ksp-program-api/src/program_instruction_decode.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Recognition strength reported by one Program instruction implementation.
|
||||||
|
///
|
||||||
|
/// Recognition is intentionally instruction-local. It does not encode registry
|
||||||
|
/// priority, a persisted proof, a textual discriminator or a global Program
|
||||||
|
/// kind. [`Self::ExactMatch`] is an assertion made by the implementation for
|
||||||
|
/// the current instruction, while [`Self::ProgramMatch`] only establishes the
|
||||||
|
/// Program-level match.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ProgramInstructionRecognition {
|
||||||
|
/// The implementation does not claim the instruction.
|
||||||
|
NoMatch,
|
||||||
|
/// The Program or Program family matches, but the instruction is not proven exact.
|
||||||
|
ProgramMatch,
|
||||||
|
/// The implementation claims an exact instruction-local match.
|
||||||
|
ExactMatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of a successful Program instruction decode attempt.
|
||||||
|
///
|
||||||
|
/// Decode failures are represented by the surrounding KSP [`crate::Result`],
|
||||||
|
/// not by a parallel failure variant. `Unsupported` is reserved for a known
|
||||||
|
/// Program instruction that the implementation deliberately does not decode.
|
||||||
|
///
|
||||||
|
/// The custom [`std::fmt::Debug`] implementation never formats the `Decoded`
|
||||||
|
/// value, so external decoded payloads are not exposed accidentally through
|
||||||
|
/// generic diagnostics.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ProgramInstructionDecodeOutcome<Decoded> {
|
||||||
|
/// The instruction was decoded into the implementation-owned output type.
|
||||||
|
Decoded(Decoded),
|
||||||
|
/// The instruction is known but unsupported by this decode capability.
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Decoded> std::fmt::Debug for ProgramInstructionDecodeOutcome<Decoded> {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Decoded(_) => return formatter.write_str("Decoded"),
|
||||||
|
Self::Unsupported => return formatter.write_str("Unsupported"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/program_instruction_decode.rs"]
|
||||||
|
mod tests;
|
||||||
28
crates/ksp-program-api/src/program_instruction_decoder.rs
Normal file
28
crates/ksp-program-api/src/program_instruction_decoder.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// file: crates/ksp-program-api/src/program_instruction_decoder.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Open contract implemented by one Program instruction decoder.
|
||||||
|
///
|
||||||
|
/// The decoder owns its concrete [`Self::Decoded`] type. No central Program
|
||||||
|
/// enum, erased `Any` payload or serialization contract is required. Program
|
||||||
|
/// identifiers remain opaque [`crate::Pubkey`] values and do not need to be
|
||||||
|
/// registered by Core.
|
||||||
|
///
|
||||||
|
/// Candidate selection is explicit: callers use [`Self::program_ids`] and
|
||||||
|
/// [`Self::recognize`] before invoking [`Self::decode`]. `decode` therefore
|
||||||
|
/// reports only a successful typed value, an intentional unsupported state, or
|
||||||
|
/// a KSP [`crate::Result`] error. The trait defines no default methods and does
|
||||||
|
/// not promise heterogeneous runtime object composition.
|
||||||
|
pub trait ProgramInstructionDecoder: Send + Sync {
|
||||||
|
/// Concrete decoded instruction type owned by the implementation.
|
||||||
|
type Decoded;
|
||||||
|
|
||||||
|
/// Returns the opaque Program identifiers claimed by this decoder.
|
||||||
|
fn program_ids(&self) -> &[crate::Pubkey];
|
||||||
|
|
||||||
|
/// Reports how strongly this decoder recognizes one bounded instruction.
|
||||||
|
fn recognize(&self, instruction: &crate::ProgramInstruction) -> crate::ProgramInstructionRecognition;
|
||||||
|
|
||||||
|
/// Decodes one instruction already selected for this decoder.
|
||||||
|
fn decode(&self, instruction: &crate::ProgramInstruction) -> crate::Result<crate::ProgramInstructionDecodeOutcome<Self::Decoded>>;
|
||||||
|
}
|
||||||
105
crates/ksp-program-api/tests/dependency_boundary.rs
Normal file
105
crates/ksp-program-api/tests/dependency_boundary.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// file: crates/ksp-program-api/tests/dependency_boundary.rs
|
||||||
|
// version: 3
|
||||||
|
|
||||||
|
//! Dependency and declarative-surface canaries for the Program API foundation.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_002_manifest_has_exact_core_and_interface_runtime_dependencies() {
|
||||||
|
let manifest = include_str!("../Cargo.toml");
|
||||||
|
let dependencies_tail = manifest.split("[dependencies]").nth(1);
|
||||||
|
assert!(dependencies_tail.is_some(), "Program 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", "ksp-interface-lib"]);
|
||||||
|
for forbidden in [
|
||||||
|
"ksp-config-lib",
|
||||||
|
"ksp-logging-lib",
|
||||||
|
"ksp-materializer-api",
|
||||||
|
"ksp-materializer-lib",
|
||||||
|
"ksp-offchain-transport-lib",
|
||||||
|
"ksp-onchain-transport-lib",
|
||||||
|
"ksp-program-lib",
|
||||||
|
"ksp-store-api",
|
||||||
|
"ksp-store-lib",
|
||||||
|
"ksp-wallet-lib",
|
||||||
|
"borsh",
|
||||||
|
"bincode",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"solana-instruction",
|
||||||
|
"tauri",
|
||||||
|
"tokio",
|
||||||
|
"tonic",
|
||||||
|
"tracing",
|
||||||
|
"wincode",
|
||||||
|
] {
|
||||||
|
assert!(!dependencies.contains(forbidden), "forbidden Program API dependency detected: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_004_crate_root_adds_decoder_without_runtime_surface() {
|
||||||
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
|
for required in [
|
||||||
|
"ProgramInstructionDecodeOutcome",
|
||||||
|
"ProgramInstructionDecoder",
|
||||||
|
"ProgramInstructionRecognition",
|
||||||
|
"Error",
|
||||||
|
"ErrorCode",
|
||||||
|
"ErrorContext",
|
||||||
|
"Pubkey",
|
||||||
|
"Result",
|
||||||
|
"ProgramAccountMeta",
|
||||||
|
"ProgramInstruction",
|
||||||
|
] {
|
||||||
|
assert!(crate_root.contains(required), "required Program API facade export missing: {required}");
|
||||||
|
}
|
||||||
|
for forbidden in ["pub mod ", "ProgramExecutionPreparer", "TRACING_TARGET", "ksp_logging_lib", "serde", "Any"] {
|
||||||
|
assert!(!crate_root.contains(forbidden), "forbidden pre.004 Program API surface detected: {forbidden}");
|
||||||
|
}
|
||||||
|
let decoder_source = include_str!("../src/program_instruction_decoder.rs");
|
||||||
|
assert!(decoder_source.contains("pub trait ProgramInstructionDecoder: Send + Sync"));
|
||||||
|
assert!(decoder_source.contains("type Decoded;"));
|
||||||
|
assert!(decoder_source.contains("fn program_ids(&self) -> &[crate::Pubkey];"));
|
||||||
|
assert!(decoder_source.contains("fn recognize(&self, instruction: &crate::ProgramInstruction)"));
|
||||||
|
assert!(decoder_source.contains("crate::Result<crate::ProgramInstructionDecodeOutcome<Self::Decoded>>"));
|
||||||
|
assert!(!decoder_source.contains("ProgramExecutionPreparer"));
|
||||||
|
assert!(!decoder_source.contains("serde"));
|
||||||
|
assert!(!decoder_source.contains("std::any::Any"));
|
||||||
|
let outcome_source = include_str!("../src/program_instruction_decode.rs");
|
||||||
|
for forbidden in ["Ignored", "Failed", "serde", "Any"] {
|
||||||
|
assert!(!outcome_source.contains(forbidden), "forbidden recognition/outcome concept detected: {forbidden}");
|
||||||
|
}
|
||||||
|
assert!(!std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/constants.rs").exists());
|
||||||
|
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;
|
||||||
|
}
|
||||||
115
crates/ksp-program-api/tests/external_implementation.rs
Normal file
115
crates/ksp-program-api/tests/external_implementation.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// file: crates/ksp-program-api/tests/external_implementation.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Downstream-style implementation canary for the open Program decoder contract.
|
||||||
|
|
||||||
|
const EXTERNAL_PROGRAM_ID_BYTES: [u8; 32] = [0xE7_u8; 32];
|
||||||
|
const FOREIGN_PROGRAM_ID_BYTES: [u8; 32] = [0xE8_u8; 32];
|
||||||
|
const SUPPORTED_OPCODE: u8 = 0x2A_u8;
|
||||||
|
|
||||||
|
struct ExternalDecodedInstruction {
|
||||||
|
opcode: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExternalProgramDecoder {
|
||||||
|
program_ids: [ksp_program_api::Pubkey; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternalProgramDecoder {
|
||||||
|
fn new() -> Self {
|
||||||
|
return Self { program_ids: [ksp_program_api::Pubkey::new_from_array(EXTERNAL_PROGRAM_ID_BYTES)] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for ExternalProgramDecoder {
|
||||||
|
type Decoded = ExternalDecodedInstruction;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &self.program_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(&self, instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
if instruction.program_id() != &self.program_ids[0] {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
if instruction.data().first() == std::option::Option::Some(&SUPPORTED_OPCODE) {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ExactMatch;
|
||||||
|
}
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
if instruction.program_id() != &self.program_ids[0] {
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported);
|
||||||
|
}
|
||||||
|
let opcode = match instruction.data().first() {
|
||||||
|
std::option::Option::Some(value) if *value == SUPPORTED_OPCODE => *value,
|
||||||
|
_ => return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(ExternalDecodedInstruction { opcode }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_send_sync<T: Send + Sync>(_value: &T) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_004_external_decoder_uses_unregistered_pubkey_and_implementation_owned_output() {
|
||||||
|
let decoder = ExternalProgramDecoder::new();
|
||||||
|
assert_send_sync(&decoder);
|
||||||
|
let external_program_id = ksp_program_api::Pubkey::new_from_array(EXTERNAL_PROGRAM_ID_BYTES);
|
||||||
|
assert!(ksp_core_lib::find_program_pubkey(&external_program_id).is_none());
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::program_ids(&decoder), &[external_program_id]);
|
||||||
|
let exact = ksp_program_api::ProgramInstruction::try_new(external_program_id, std::vec![], std::vec![SUPPORTED_OPCODE]);
|
||||||
|
assert!(exact.is_ok());
|
||||||
|
let exact = match exact {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::recognize(&decoder, &exact), ksp_program_api::ProgramInstructionRecognition::ExactMatch);
|
||||||
|
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &exact);
|
||||||
|
assert!(outcome.is_ok());
|
||||||
|
let outcome = match outcome {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let decoded = match outcome {
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(decoded.opcode, SUPPORTED_OPCODE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_004_external_decoder_distinguishes_program_match_unsupported_and_no_match() {
|
||||||
|
let decoder = ExternalProgramDecoder::new();
|
||||||
|
let external_program_id = ksp_program_api::Pubkey::new_from_array(EXTERNAL_PROGRAM_ID_BYTES);
|
||||||
|
let program_only = ksp_program_api::ProgramInstruction::try_new(external_program_id, std::vec![], std::vec![0x11_u8]);
|
||||||
|
assert!(program_only.is_ok());
|
||||||
|
let program_only = match program_only {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::recognize(&decoder, &program_only), ksp_program_api::ProgramInstructionRecognition::ProgramMatch);
|
||||||
|
let unsupported = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &program_only);
|
||||||
|
assert!(unsupported.is_ok());
|
||||||
|
let unsupported = match unsupported {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(matches!(unsupported, ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported));
|
||||||
|
let foreign_program_id = ksp_program_api::Pubkey::new_from_array(FOREIGN_PROGRAM_ID_BYTES);
|
||||||
|
let foreign = ksp_program_api::ProgramInstruction::try_new(foreign_program_id, std::vec![], std::vec![SUPPORTED_OPCODE]);
|
||||||
|
assert!(foreign.is_ok());
|
||||||
|
let foreign = match foreign {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::recognize(&decoder, &foreign), ksp_program_api::ProgramInstructionRecognition::NoMatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
77
crates/ksp-program-api/tests/public_api.rs
Normal file
77
crates/ksp-program-api/tests/public_api.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// file: crates/ksp-program-api/tests/public_api.rs
|
||||||
|
// version: 3
|
||||||
|
|
||||||
|
//! Integration canaries for the public `ksp-program-api` foundation.
|
||||||
|
|
||||||
|
fn consume_result(value: ksp_program_api::Result<ksp_program_api::Pubkey>) -> ksp_program_api::Result<ksp_program_api::Pubkey> {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_002_core_and_interface_facade_is_available_from_crate_root() {
|
||||||
|
let program_id = ksp_program_api::Pubkey::new_from_array([0xA1_u8; 32]);
|
||||||
|
let account_id = ksp_program_api::Pubkey::new_from_array([0xA2_u8; 32]);
|
||||||
|
let account = ksp_program_api::ProgramAccountMeta::readonly(account_id, true);
|
||||||
|
let instruction = ksp_program_api::ProgramInstruction::try_new(program_id, std::vec![account], std::vec![0xA3_u8]);
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
let forwarded = consume_result(std::result::Result::Ok(program_id));
|
||||||
|
assert!(forwarded.is_ok());
|
||||||
|
let error_code_type: std::option::Option<ksp_program_api::ErrorCode> = std::option::Option::None;
|
||||||
|
let error_context_type: std::option::Option<ksp_program_api::ErrorContext> = std::option::Option::None;
|
||||||
|
let error_type: std::option::Option<ksp_program_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_002_scaffold_does_not_require_private_modules() {
|
||||||
|
let source = include_str!("../src/lib.rs");
|
||||||
|
assert!(!source.contains("pub mod "));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_003_recognition_and_decode_outcome_are_available_from_crate_root() {
|
||||||
|
let recognition = ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
||||||
|
assert_eq!(std::format!("{recognition:?}"), "ProgramMatch");
|
||||||
|
let decoded = ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(31_u16);
|
||||||
|
assert_eq!(std::format!("{decoded:?}"), "Decoded");
|
||||||
|
let decoded_value = match decoded {
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||||
|
_ => 0_u16,
|
||||||
|
};
|
||||||
|
assert_eq!(decoded_value, 31_u16);
|
||||||
|
let unsupported = ksp_program_api::ProgramInstructionDecodeOutcome::<u16>::Unsupported;
|
||||||
|
assert_eq!(std::format!("{unsupported:?}"), "Unsupported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_004_instruction_decoder_trait_is_available_from_crate_root() {
|
||||||
|
let decoder = NeverInstantiatedDecoder;
|
||||||
|
assert!(ksp_program_api::ProgramInstructionDecoder::program_ids(&decoder).is_empty());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NeverInstantiatedDecoder;
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for NeverInstantiatedDecoder {
|
||||||
|
type Decoded = u8;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &[];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(&self, _instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
_instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported);
|
||||||
|
}
|
||||||
|
}
|
||||||
128
crates/ksp-program-api/tests/release_completeness.rs
Normal file
128
crates/ksp-program-api/tests/release_completeness.rs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
// file: crates/ksp-program-api/tests/release_completeness.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
//! Release-level completeness canaries for the `0.2.14` Program API foundation.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_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 self::program_instruction_decode::ProgramInstructionDecodeOutcome;",
|
||||||
|
"pub use self::program_instruction_decode::ProgramInstructionRecognition;",
|
||||||
|
"pub use self::program_instruction_decoder::ProgramInstructionDecoder;",
|
||||||
|
"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 ksp_interface_lib::ProgramAccountMeta;",
|
||||||
|
"pub use ksp_interface_lib::ProgramInstruction;",
|
||||||
|
];
|
||||||
|
expected.sort_unstable();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert!(!crate_root.contains("pub mod "));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_production_module_inventory_is_instruction_only() -> std::io::Result<()> {
|
||||||
|
let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
||||||
|
let entries = match std::fs::read_dir(source_root) {
|
||||||
|
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();
|
||||||
|
assert_eq!(names, std::vec!["lib.rs", "program_instruction_decode.rs", "program_instruction_decoder.rs"]);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_public_enum_and_trait_inventory_remains_open_world() {
|
||||||
|
let sources = [include_str!("../src/lib.rs"), include_str!("../src/program_instruction_decode.rs"), include_str!("../src/program_instruction_decoder.rs")];
|
||||||
|
let mut public_enums = std::vec::Vec::new();
|
||||||
|
let mut public_traits = std::vec::Vec::new();
|
||||||
|
for source in sources {
|
||||||
|
for line in source.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.starts_with("pub enum ") {
|
||||||
|
public_enums.push(trimmed);
|
||||||
|
}
|
||||||
|
if trimmed.starts_with("pub trait ") {
|
||||||
|
public_traits.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public_enums.sort_unstable();
|
||||||
|
public_traits.sort_unstable();
|
||||||
|
assert_eq!(public_enums, std::vec!["pub enum ProgramInstructionDecodeOutcome<Decoded> {", "pub enum ProgramInstructionRecognition {"]);
|
||||||
|
assert_eq!(public_traits, std::vec!["pub trait ProgramInstructionDecoder: Send + Sync {"]);
|
||||||
|
let decode_source = include_str!("../src/program_instruction_decode.rs");
|
||||||
|
assert!(decode_source.contains("#[non_exhaustive]\npub enum ProgramInstructionRecognition"));
|
||||||
|
assert!(decode_source.contains("#[non_exhaustive]\npub enum ProgramInstructionDecodeOutcome<Decoded>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_production_sources_have_no_registry_preparer_codec_or_runtime_creep() {
|
||||||
|
let direct_tracing_path = ["tracing", "::"].concat();
|
||||||
|
let sources = [include_str!("../src/lib.rs"), include_str!("../src/program_instruction_decode.rs"), include_str!("../src/program_instruction_decoder.rs")];
|
||||||
|
for source in sources {
|
||||||
|
for forbidden in [
|
||||||
|
"pub enum ProgramKind",
|
||||||
|
"pub struct ProgramRegistry",
|
||||||
|
"pub trait ProgramAccountDecoder",
|
||||||
|
"pub trait ProgramEventDecoder",
|
||||||
|
"pub trait ProgramReturnDataDecoder",
|
||||||
|
"pub trait ProgramExecutionPreparer",
|
||||||
|
"std::any::Any",
|
||||||
|
"serde::",
|
||||||
|
"serde_json::",
|
||||||
|
"borsh::",
|
||||||
|
"bincode::",
|
||||||
|
"wincode::",
|
||||||
|
"ksp_logging_lib::",
|
||||||
|
"reqwest::",
|
||||||
|
"tokio::",
|
||||||
|
"tonic::",
|
||||||
|
"tauri::",
|
||||||
|
"std::env::",
|
||||||
|
"std::fs::",
|
||||||
|
"std::net::",
|
||||||
|
"dyn ProgramInstructionDecoder",
|
||||||
|
"dyn crate::ProgramInstructionDecoder",
|
||||||
|
] {
|
||||||
|
assert!(!source.contains(forbidden), "forbidden Program API production surface detected: {forbidden}");
|
||||||
|
}
|
||||||
|
assert!(!source.contains(direct_tracing_path.as_str()), "forbidden Program API production direct tracing path detected");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
156
crates/ksp-program-api/tests/security_hardening.rs
Normal file
156
crates/ksp-program-api/tests/security_hardening.rs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// file: crates/ksp-program-api/tests/security_hardening.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Adversarial and bound-safety canaries for the Program API foundation.
|
||||||
|
|
||||||
|
const HOSTILE_MARKER: &str = "PROGRAM-SECRET-CANARY";
|
||||||
|
const MALFORMED_OPCODE: u8 = 0xFF_u8;
|
||||||
|
const PROGRAM_ID_BYTES: [u8; 32] = [0xD1_u8; 32];
|
||||||
|
|
||||||
|
struct BoundsObserved {
|
||||||
|
account_count: usize,
|
||||||
|
data_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BoundedDecoder {
|
||||||
|
program_ids: [ksp_program_api::Pubkey; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoundedDecoder {
|
||||||
|
fn new() -> Self {
|
||||||
|
return Self { program_ids: [ksp_program_api::Pubkey::new_from_array(PROGRAM_ID_BYTES)] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for BoundedDecoder {
|
||||||
|
type Decoded = BoundsObserved;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &self.program_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(&self, instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
if instruction.program_id() == &self.program_ids[0] {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ExactMatch;
|
||||||
|
}
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
if instruction.data().first() == std::option::Option::Some(&MALFORMED_OPCODE) {
|
||||||
|
return std::result::Result::Err(ksp_program_api::Error::new(
|
||||||
|
ksp_program_api::ErrorCode::new("program_test", "malformed_instruction"),
|
||||||
|
"malformed external Program instruction",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundsObserved {
|
||||||
|
account_count: instruction.accounts().len(),
|
||||||
|
data_len: instruction.data().len(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BoundlessDecoded {
|
||||||
|
marker: std::rc::Rc<std::cell::Cell<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BoundlessOutputDecoder;
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for BoundlessOutputDecoder {
|
||||||
|
type Decoded = BoundlessDecoded;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &[];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(&self, _instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
_instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundlessDecoded {
|
||||||
|
marker: std::rc::Rc::new(std::cell::Cell::new(0x5A_u8)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_max_interface_instruction_crosses_decoder_boundary_without_new_contract() {
|
||||||
|
let decoder = BoundedDecoder::new();
|
||||||
|
let account = ksp_program_api::ProgramAccountMeta::readonly(ksp_program_api::Pubkey::new_from_array([0xD2_u8; 32]), false);
|
||||||
|
let accounts = std::vec![account; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS];
|
||||||
|
let data = std::vec![0x5A_u8; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN];
|
||||||
|
let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], accounts, data);
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
let instruction = match instruction {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
|
||||||
|
assert!(outcome.is_ok());
|
||||||
|
let outcome = match outcome {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let observed = match outcome {
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(observed.account_count, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS);
|
||||||
|
assert_eq!(observed.data_len, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_malformed_payload_error_path_does_not_gain_automatic_payload_echo() {
|
||||||
|
let decoder = BoundedDecoder::new();
|
||||||
|
let mut payload = std::vec![MALFORMED_OPCODE];
|
||||||
|
payload.extend_from_slice(HOSTILE_MARKER.as_bytes());
|
||||||
|
let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], std::vec![], payload);
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
let instruction = match instruction {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
|
||||||
|
assert!(outcome.is_err());
|
||||||
|
let error = match outcome {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(error.code().domain(), "program_test");
|
||||||
|
assert_eq!(error.code().code(), "malformed_instruction");
|
||||||
|
assert!(!std::format!("{error}").contains(HOSTILE_MARKER));
|
||||||
|
assert!(!std::format!("{error:?}").contains(HOSTILE_MARKER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_associated_decoded_type_keeps_no_implicit_debug_clone_send_or_sync_bound() {
|
||||||
|
let decoder = BoundlessOutputDecoder;
|
||||||
|
let instruction = ksp_program_api::ProgramInstruction::try_new(ksp_program_api::Pubkey::new_from_array([0xD3_u8; 32]), std::vec![], std::vec![]);
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
let instruction = match instruction {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
|
||||||
|
assert!(outcome.is_ok());
|
||||||
|
let outcome = match outcome {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(std::format!("{outcome:?}"), "Decoded");
|
||||||
|
let decoded = match outcome {
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(decoded.marker.get(), 0x5A_u8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// file: crates/ksp-program-api/unit_tests/program_instruction_decode.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recognition_variants_are_distinct_and_payload_free() {
|
||||||
|
assert_ne!(crate::ProgramInstructionRecognition::NoMatch, crate::ProgramInstructionRecognition::ProgramMatch);
|
||||||
|
assert_ne!(crate::ProgramInstructionRecognition::ProgramMatch, crate::ProgramInstructionRecognition::ExactMatch);
|
||||||
|
assert_eq!(std::format!("{:?}", crate::ProgramInstructionRecognition::NoMatch), "NoMatch");
|
||||||
|
assert_eq!(std::format!("{:?}", crate::ProgramInstructionRecognition::ProgramMatch), "ProgramMatch");
|
||||||
|
assert_eq!(std::format!("{:?}", crate::ProgramInstructionRecognition::ExactMatch), "ExactMatch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_outcome_preserves_decoded_value_and_unsupported_state() {
|
||||||
|
let decoded = crate::ProgramInstructionDecodeOutcome::Decoded(17_u64);
|
||||||
|
let decoded_value = match decoded {
|
||||||
|
crate::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||||
|
crate::ProgramInstructionDecodeOutcome::Unsupported => 0_u64,
|
||||||
|
};
|
||||||
|
assert_eq!(decoded_value, 17_u64);
|
||||||
|
let unsupported = crate::ProgramInstructionDecodeOutcome::<u64>::Unsupported;
|
||||||
|
assert!(matches!(unsupported, crate::ProgramInstructionDecodeOutcome::Unsupported));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_outcome_debug_never_requires_or_renders_decoded_debug() {
|
||||||
|
struct ExternalDecoded {
|
||||||
|
secret_marker: u8,
|
||||||
|
}
|
||||||
|
let decoded = crate::ProgramInstructionDecodeOutcome::Decoded(ExternalDecoded { secret_marker: 0xA7_u8 });
|
||||||
|
assert_eq!(std::format!("{decoded:?}"), "Decoded");
|
||||||
|
let secret_marker = match decoded {
|
||||||
|
crate::ProgramInstructionDecodeOutcome::Decoded(value) => value.secret_marker,
|
||||||
|
crate::ProgramInstructionDecodeOutcome::Unsupported => 0_u8,
|
||||||
|
};
|
||||||
|
assert_eq!(secret_marker, 0xA7_u8);
|
||||||
|
assert_eq!(std::format!("{:?}", crate::ProgramInstructionDecodeOutcome::<ExternalDecoded>::Unsupported), "Unsupported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
201
deltas/0.2.14/pre.001.md
Normal file
201
deltas/0.2.14/pre.001.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.001` — audit Program API, héritage kbot3 et sizing
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Base directe attendue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.13
|
||||||
|
workspace.package.version = 0.2.13
|
||||||
|
```
|
||||||
|
|
||||||
|
Sources obligatoires réellement disponibles à l'ouverture :
|
||||||
|
|
||||||
|
```text
|
||||||
|
archive opérateur khadhroony-solana-project-v0.2.13.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.13` ne peut donc pas être interrogé localement. La version Cargo, `deltas/0.2.13/rel.001.md`, le prompt 019 et la surface Interface publiée concordent avec la base stable attendue.
|
||||||
|
|
||||||
|
Commit attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.001
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Ouvrir `0.2.14 — Program API foundation` uniquement par le gate prévu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
lecture règles + architecture
|
||||||
|
audit Core + Interface stables
|
||||||
|
audit historique kbot3 réel
|
||||||
|
matrice d'héritage
|
||||||
|
ownership
|
||||||
|
API candidate
|
||||||
|
threat/API model
|
||||||
|
dependency graph
|
||||||
|
stratégie de tests
|
||||||
|
sizing et prévision souple recalibrée
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun scaffold `ksp-program-api`, decoder concret, registry runtime ou execution preparer n'est ajouté dans cette livraison.
|
||||||
|
|
||||||
|
## 3. Décisions principales
|
||||||
|
|
||||||
|
Le scope est réduit à une foundation instruction-only :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
ProgramInstructionDecoder avec associated output
|
||||||
|
```
|
||||||
|
|
||||||
|
Le candidat trait utilise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
&ProgramInstruction
|
||||||
|
&[Pubkey]
|
||||||
|
ksp_core_lib::Result
|
||||||
|
Send + Sync
|
||||||
|
```
|
||||||
|
|
||||||
|
L'output concret appartient à l'implémentation externe ou au futur `ksp-program-lib`.
|
||||||
|
|
||||||
|
Sont explicitement reportés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
payload canonique D3
|
||||||
|
registry runtime hétérogène
|
||||||
|
identity/version/coverage génériques
|
||||||
|
diagnostics/proofs Program dédiés
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
```
|
||||||
|
|
||||||
|
Le design n'ajoute aucune dépendance externe et ne requiert donc aucun audit web/Solana supplémentaire en `pre.001`.
|
||||||
|
|
||||||
|
## 4. Héritage kbot3
|
||||||
|
|
||||||
|
L'archive historique a été réellement extraite et les contrats prioritaires ont été relus.
|
||||||
|
|
||||||
|
Conclusions :
|
||||||
|
|
||||||
|
```text
|
||||||
|
REPRENDRE séparation recognition/decode, trait instruction, Send+Sync, préparation pure conceptuelle
|
||||||
|
REDESSINER recognition, outcomes, execution capability/prepared plan
|
||||||
|
REPORTER identity/version, coverage, diagnostics, proofs, replay context, decoded event, registry, preparer
|
||||||
|
REJETER Program IDs/Pubkeys String, generic JSON payload, generic protocol monolith, executor, policy dans Program, ks-lib monolithique
|
||||||
|
```
|
||||||
|
|
||||||
|
Les decoders Solana Core, SPL Token et SPL Token-2022 confirment que l'ancien trait contextualisé dépendait réellement de signature/slot/path/transaction status/hash et JSON. Ces champs appartiennent aux futures couches CORE/DECODE et ne sont pas reconstruits dans Program API.
|
||||||
|
|
||||||
|
## 5. Sizing recalibré
|
||||||
|
|
||||||
|
Prévision active :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.001 audit/design/sizing
|
||||||
|
pre.002 scaffold + facade + dependency firewall
|
||||||
|
pre.003 recognition + outcome
|
||||||
|
pre.004 decoder trait + external implementation
|
||||||
|
pre.005 adversarial/API hardening + completeness
|
||||||
|
pre.006 gate technique final
|
||||||
|
pre.007 réconciliation documentaire
|
||||||
|
pre.008 préparation de publication minimale
|
||||||
|
rel.001 publication stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Registry, canonical payload et execution preparation ne sont plus des tranches de `0.2.14`.
|
||||||
|
|
||||||
|
## 6. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
deltas/0.2.14/pre.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/plans/000-README.md
|
||||||
|
docs/validation/000-README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Version Cargo
|
||||||
|
|
||||||
|
La prerelease non-fix synchronise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.14-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune crate Program n'existe encore; le changement Cargo sert uniquement à identifier la tranche `pre.001` conformément au workflow.
|
||||||
|
|
||||||
|
## 10. Validations exécutées pendant la préparation
|
||||||
|
|
||||||
|
Le journal opérateur fourni à l'ouverture sur la base stable `v0.2.13` montre un baseline complet vert avant application de ce delta, notamment audits Rust/Markdown, `cargo check --workspace`, Clippy, tests ciblés des crates et `cargo test --workspace`. Cette preuve de base ne remplace pas le gate 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.2.14
|
||||||
|
Markdown table audit: clean (170 tables, 116 files)
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo` n'est pas installé dans l'environnement de génération utilisé pour préparer l'archive. Les commandes Cargo de validation après application n'ont donc pas été rejouées ici et ne sont pas déclarées PASS.
|
||||||
|
|
||||||
|
## 11. Validations non requises dans cette tranche
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo test -p ksp-program-api crate encore absente
|
||||||
|
cargo tree -p ksp-program-api crate encore absente
|
||||||
|
smokes réseau/live hors scope d'une API déclarative
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question ne bloque le passage à `pre.002`.
|
||||||
|
|
||||||
|
Les sujets suivants restent ouverts volontairement pour des releases ultérieures, pas pour compléter `0.2.14` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
canonical DECODE/D3 payload
|
||||||
|
runtime registry / conflict policy / erased composition
|
||||||
|
processor identity/version durable
|
||||||
|
coverage descriptors
|
||||||
|
contextual proofs/diagnostics
|
||||||
|
ProgramExecutionPreparer exact
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. 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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun développement fonctionnel Program ne doit être ajouté à ce delta.
|
||||||
235
deltas/0.2.14/pre.002.md
Normal file
235
deltas/0.2.14/pre.002.md
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.002` — scaffold `ksp-program-api` + façade + firewall
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement sur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.13
|
||||||
|
+ 0.2.14-pre.001
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.001` est vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS — 170 tables / 116 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS sans warning
|
||||||
|
```
|
||||||
|
|
||||||
|
La version workspace passe de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.002
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Matérialiser uniquement le scaffold de `ksp-program-api` et verrouiller sa façade/dependency firewall avant toute capability de décodage.
|
||||||
|
|
||||||
|
La tranche ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
nouveau membre workspace ksp-program-api
|
||||||
|
manifest Core + Interface uniquement
|
||||||
|
façade crate-root explicite
|
||||||
|
réexports Core retenus
|
||||||
|
réexports Interface retenus
|
||||||
|
README/USAGE initiaux
|
||||||
|
canari public API
|
||||||
|
canari dependency firewall
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle n'ajoute pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome
|
||||||
|
ProgramInstructionDecoder
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
registry runtime
|
||||||
|
identity/version/coverage
|
||||||
|
payload canonique D3
|
||||||
|
serde / JSON / Any
|
||||||
|
codec
|
||||||
|
runtime logging
|
||||||
|
network / async runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Façade publique de scaffold
|
||||||
|
|
||||||
|
Le crate-root réexporte explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-core-lib
|
||||||
|
Error
|
||||||
|
ErrorCode
|
||||||
|
ErrorContext
|
||||||
|
Result
|
||||||
|
Pubkey
|
||||||
|
|
||||||
|
ksp-interface-lib
|
||||||
|
ProgramAccountMeta
|
||||||
|
ProgramInstruction
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces réexports ne changent pas l'ownership des types. Ils fournissent la façade retenue par le plan afin qu'une future implémentation externe puisse dépendre du contrat Program sans reconstruire les types Core/Interface ni accéder à leurs modules privés.
|
||||||
|
|
||||||
|
Aucun `pub mod` n'est exposé.
|
||||||
|
|
||||||
|
## 4. Dependency firewall
|
||||||
|
|
||||||
|
Le manifest membre possède exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
[dependencies]
|
||||||
|
ksp-core-lib
|
||||||
|
ksp-interface-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari `tests/dependency_boundary.rs` verrouille notamment l'absence de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
Transport / Config / Wallet / Store / Materializer
|
||||||
|
serde / serde_json
|
||||||
|
borsh / bincode / wincode
|
||||||
|
solana-instruction
|
||||||
|
reqwest / tokio / tonic / tauri / tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
Il verrouille aussi l'absence de `src/constants.rs`, `TRACING_TARGET`, recognition/outcome/decoder/preparer et de module public pendant `pre.002`.
|
||||||
|
|
||||||
|
## 5. Logging
|
||||||
|
|
||||||
|
La crate reste une API déclarative sans comportement runtime. Aucun span/event n'est émis et aucun logging n'est nécessaire.
|
||||||
|
|
||||||
|
Restent donc volontairement absents :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-logging-lib
|
||||||
|
src/constants.rs
|
||||||
|
TRACING_TARGET
|
||||||
|
tracing direct
|
||||||
|
```
|
||||||
|
|
||||||
|
Si une future surface Program comportementale nécessite réellement une instrumentation, elle devra respecter les règles Logging KSP dans la crate qui possède ce comportement. Le scaffold API ne doit pas tirer un runtime logging par anticipation.
|
||||||
|
|
||||||
|
## 6. Tests ajoutés
|
||||||
|
|
||||||
|
`tests/public_api.rs` vérifie que la façade crate-root permet de construire `Pubkey`, `ProgramAccountMeta` et `ProgramInstruction`, et que les types d'erreur Core sont adressables sans module privé.
|
||||||
|
|
||||||
|
`tests/dependency_boundary.rs` vérifie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
dépendances normales exactes = Core + Interface
|
||||||
|
absence des dépendances interdites
|
||||||
|
absence de pub mod
|
||||||
|
absence des contrats réservés à pre.003/pre.004
|
||||||
|
absence de logging/runtime/codec
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun unit test métier n'est ajouté : la crate ne possède encore aucun comportement propre.
|
||||||
|
|
||||||
|
## 7. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
crates/ksp-program-api/src/lib.rs
|
||||||
|
crates/ksp-program-api/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-program-api/tests/public_api.rs
|
||||||
|
deltas/0.2.14/pre.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
README.md
|
||||||
|
RULES.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
docs/architecture/**
|
||||||
|
docs/rules/**
|
||||||
|
crates/ksp-core-lib/**
|
||||||
|
crates/ksp-interface-lib/**
|
||||||
|
crates/ksp-logging-lib/**
|
||||||
|
crates/ksp-*-transport-lib/**
|
||||||
|
crates/ksp-config-lib/**
|
||||||
|
crates/ksp-wallet-lib/**
|
||||||
|
crates/ksp-app-*/**
|
||||||
|
prompts/**
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune architecture durable n'est rouverte : la direction `Program API -> Core + Interface` est déjà normative.
|
||||||
|
|
||||||
|
## 10. Validations de génération
|
||||||
|
|
||||||
|
Les audits Python sont rejoués sur l'arbre matérialisé avant livraison. L'environnement de génération ne fournit pas `cargo`, `rustc` ou `rustfmt`; aucun PASS Cargo nouveau n'est revendiqué localement.
|
||||||
|
|
||||||
|
## 11. Gate opérateur attendu
|
||||||
|
|
||||||
|
Après application :
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe normal doit confirmer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Une correction découverte par ce gate reste un `0.2.14-pre.002-fix.NNN` et n'avance pas `pre.003`.
|
||||||
|
|
||||||
|
## 12. Suite
|
||||||
|
|
||||||
|
Après gate vert, `pre.003` pourra matérialiser uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
invariants / Debug sûr associés
|
||||||
|
unit/public canaries correspondants
|
||||||
|
```
|
||||||
|
|
||||||
|
`ProgramInstructionDecoder` et l'implémentation externe restent réservés à `pre.004`.
|
||||||
285
deltas/0.2.14/pre.003.md
Normal file
285
deltas/0.2.14/pre.003.md
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.003` — recognition + outcome minimal
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement sur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.13
|
||||||
|
+ 0.2.14-pre.001
|
||||||
|
+ 0.2.14-pre.002
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.002` est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS — 170 tables / 119 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté
|
||||||
|
```
|
||||||
|
|
||||||
|
La version workspace passe de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.2
|
||||||
|
```
|
||||||
|
|
||||||
|
à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Matérialiser uniquement le vocabulaire minimal nécessaire au futur decoder d'instruction :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche ne crée toujours aucun comportement de décodage, registry ou contrat d'exécution.
|
||||||
|
|
||||||
|
## 3. Recognition
|
||||||
|
|
||||||
|
`ProgramInstructionRecognition` est public depuis le crate-root, `#[non_exhaustive]` et possède exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
NoMatch
|
||||||
|
ProgramMatch
|
||||||
|
ExactMatch
|
||||||
|
```
|
||||||
|
|
||||||
|
Sémantique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
NoMatch l'implémentation ne revendique pas l'instruction
|
||||||
|
ProgramMatch le Program ou la famille correspond sans preuve instruction-locale exacte
|
||||||
|
ExactMatch l'implémentation affirme un match instruction-local exact
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun score, priorité, confidence, proof, surface code ou discriminator textuel n'est introduit.
|
||||||
|
|
||||||
|
## 4. Decode outcome
|
||||||
|
|
||||||
|
`ProgramInstructionDecodeOutcome<Decoded>` est public depuis le crate-root, `#[non_exhaustive]` et possède exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Decoded(Decoded)
|
||||||
|
Unsupported
|
||||||
|
```
|
||||||
|
|
||||||
|
La valeur `Decoded` reste possédée par l'implémentation future. Il n'existe aucun `Any`, JSON, payload D3 ou enum centrale pour l'effacer.
|
||||||
|
|
||||||
|
`Failed` est volontairement absent : le futur `ProgramInstructionDecoder::decode` retournera le `Result` Core. Une erreur réelle sera donc `Err`, sans deuxième canal de failure.
|
||||||
|
|
||||||
|
`Ignored` reste absent : une capability de décodage doit produire une valeur, déclarer l'instruction connue mais unsupported, ou échouer.
|
||||||
|
|
||||||
|
## 5. Debug sûr
|
||||||
|
|
||||||
|
`ProgramInstructionRecognition` ne transporte aucun payload.
|
||||||
|
|
||||||
|
`ProgramInstructionDecodeOutcome<Decoded>` possède une implémentation `Debug` manuelle :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Decoded(_) -> "Decoded"
|
||||||
|
Unsupported -> "Unsupported"
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette implémentation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
n'impose pas Decoded: Debug
|
||||||
|
ne formate jamais la valeur Decoded
|
||||||
|
ne copie aucun payload externe
|
||||||
|
reste bornée à un nom de variante fixe
|
||||||
|
```
|
||||||
|
|
||||||
|
Le test unitaire utilise volontairement un type externe sans implémentation `Debug` pour prouver cette propriété à la compilation.
|
||||||
|
|
||||||
|
## 6. Structure et façade
|
||||||
|
|
||||||
|
Un seul module privé est ajouté :
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/program_instruction_decode.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
Le crate-root réexporte :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `pub mod` n'est introduit. Les réexports Core/Interface de `pre.002` restent inchangés.
|
||||||
|
|
||||||
|
## 7. Dependency firewall
|
||||||
|
|
||||||
|
Le manifest de `ksp-program-api` est inchangé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-core-lib
|
||||||
|
ksp-interface-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Restent interdits et absents :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
Transport / Config / Wallet / Store / Materializer
|
||||||
|
serde / serde_json
|
||||||
|
borsh / bincode / wincode
|
||||||
|
solana-instruction
|
||||||
|
reqwest / tokio / tonic / tauri / tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `constants.rs` ou `TRACING_TARGET` n'est justifié pour ces types déclaratifs.
|
||||||
|
|
||||||
|
## 8. Tests
|
||||||
|
|
||||||
|
### Unit
|
||||||
|
|
||||||
|
`unit_tests/program_instruction_decode.rs` vérifie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
variants Recognition distincts
|
||||||
|
Debug Recognition exact et payload-free
|
||||||
|
Decoded conserve sa valeur
|
||||||
|
Unsupported reste distinct
|
||||||
|
Debug outcome sans Decoded: Debug
|
||||||
|
Debug outcome ne rend pas la valeur externe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Public API
|
||||||
|
|
||||||
|
`tests/public_api.rs` ajoute un canari `pre.003` consommant uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_program_api::ProgramInstructionRecognition
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boundary
|
||||||
|
|
||||||
|
`tests/dependency_boundary.rs` est avancé pour autoriser uniquement recognition/outcome tout en maintenant l'absence de trait decoder, preparer, logging, serde et module public.
|
||||||
|
|
||||||
|
## 9. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/src/program_instruction_decode.rs
|
||||||
|
crates/ksp-program-api/unit_tests/program_instruction_decode.rs
|
||||||
|
deltas/0.2.14/pre.003.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
crates/ksp-program-api/src/lib.rs
|
||||||
|
crates/ksp-program-api/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-program-api/tests/public_api.rs
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
README.md
|
||||||
|
RULES.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
docs/architecture/**
|
||||||
|
docs/rules/**
|
||||||
|
crates/ksp-core-lib/**
|
||||||
|
crates/ksp-interface-lib/**
|
||||||
|
crates/ksp-logging-lib/**
|
||||||
|
crates/ksp-*-transport-lib/**
|
||||||
|
crates/ksp-config-lib/**
|
||||||
|
crates/ksp-wallet-lib/**
|
||||||
|
crates/ksp-app-*/**
|
||||||
|
prompts/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Scope négatif maintenu
|
||||||
|
|
||||||
|
Cette tranche n'introduit pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionDecoder
|
||||||
|
program_ids(...)
|
||||||
|
recognize(...) sur un trait
|
||||||
|
decode(...) sur un trait
|
||||||
|
associated output contract du trait
|
||||||
|
external implementation fixture
|
||||||
|
registry runtime
|
||||||
|
identity/version/coverage
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
payload canonique D3
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces éléments ne doivent pas être anticipés dans un fix de `pre.003`.
|
||||||
|
|
||||||
|
## 13. Gate opérateur attendu
|
||||||
|
|
||||||
|
Après application :
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe normal doit rester :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Une correction découverte par ce gate reste un `0.2.14-pre.003-fix.NNN` et n'avance pas `pre.004`.
|
||||||
|
|
||||||
|
## 14. Suite
|
||||||
|
|
||||||
|
Après gate vert, `pre.004` pourra introduire uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
associated type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction)
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
external implementation canary avec Pubkey non enregistré
|
||||||
|
```
|
||||||
|
|
||||||
|
Registry runtime, payload canonique D3 et `ProgramExecutionPreparer` resteront hors scope.
|
||||||
264
deltas/0.2.14/pre.004.md
Normal file
264
deltas/0.2.14/pre.004.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.004` — decoder instruction-only + implémentation externe
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement sur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.13
|
||||||
|
+ 0.2.14-pre.001
|
||||||
|
+ 0.2.14-pre.002
|
||||||
|
+ 0.2.14-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.003` est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS — 171 tables / 120 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 8 tests Rust
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté
|
||||||
|
```
|
||||||
|
|
||||||
|
La version workspace passe de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.3
|
||||||
|
```
|
||||||
|
|
||||||
|
à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.4
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.004
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Matérialiser le contrat instruction-only ouvert retenu par `pre.001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
associated type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction) -> ProgramInstructionRecognition
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis prouver qu'une crate consommatrice séparée peut l'implémenter avec son propre type décodé et un Program `Pubkey` absent du registry Core.
|
||||||
|
|
||||||
|
## 3. Trait `ProgramInstructionDecoder`
|
||||||
|
|
||||||
|
Le trait est défini dans un module privé et réexporté depuis le crate-root.
|
||||||
|
|
||||||
|
Propriétés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Send + Sync requis sur l'implémentation
|
||||||
|
Decoded associated type sans bound imposé
|
||||||
|
program_ids slice de Pubkey opaques
|
||||||
|
recognize sélection instruction-local explicite
|
||||||
|
decode Result Core + outcome générique
|
||||||
|
méthodes par défaut aucune
|
||||||
|
registry / object composition aucune promesse
|
||||||
|
```
|
||||||
|
|
||||||
|
`decode` est destiné à une instruction déjà sélectionnée via `program_ids` / `recognize`; il ne remplace pas le signal de reconnaissance.
|
||||||
|
|
||||||
|
## 4. Open-world Program IDs
|
||||||
|
|
||||||
|
Aucun `ProgramKind`, enum centrale ou validation contre le registry Core n'est introduit.
|
||||||
|
|
||||||
|
Le canari externe utilise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Pubkey::new_from_array([0xE7; 32])
|
||||||
|
```
|
||||||
|
|
||||||
|
et vérifie explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_core_lib::find_program_pubkey(&external_program_id) == None
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette consultation du registry est limitée au test négatif. `ksp-program-api` ne réexporte pas `find_program_pubkey` et le decoder externe n'a besoin que de la façade Program API pour son implémentation.
|
||||||
|
|
||||||
|
## 5. Associated output externe
|
||||||
|
|
||||||
|
Le test d'intégration définit hors du code de production :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ExternalDecodedInstruction
|
||||||
|
ExternalProgramDecoder
|
||||||
|
```
|
||||||
|
|
||||||
|
`ExternalProgramDecoder` implémente le trait et produit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ExactMatch pour l'opcode supporté
|
||||||
|
ProgramMatch pour le Program connu avec opcode non supporté
|
||||||
|
NoMatch pour un autre Program
|
||||||
|
Decoded(...) pour l'opcode supporté
|
||||||
|
Unsupported pour le Program connu mais non supporté
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `Any`, JSON, serde, codec ou enum centrale n'intervient dans le transport du type décodé.
|
||||||
|
|
||||||
|
## 6. Dependency firewall
|
||||||
|
|
||||||
|
Le manifest de `ksp-program-api` reste inchangé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-core-lib
|
||||||
|
ksp-interface-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Toujours absents :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
Transport / Config / Wallet / Store / Materializer
|
||||||
|
serde / serde_json
|
||||||
|
borsh / bincode / wincode
|
||||||
|
solana-instruction
|
||||||
|
reqwest / tokio / tonic / tauri / tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Tests
|
||||||
|
|
||||||
|
### Public API
|
||||||
|
|
||||||
|
`tests/public_api.rs` prouve que le trait est implémentable depuis la façade crate-root sans module privé.
|
||||||
|
|
||||||
|
### External implementation
|
||||||
|
|
||||||
|
`tests/external_implementation.rs` est compilé par Cargo comme crate d'intégration séparée et vérifie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Send + Sync de l'implémentation
|
||||||
|
associated output tiers
|
||||||
|
Program Pubkey non enregistré
|
||||||
|
program_ids
|
||||||
|
NoMatch / ProgramMatch / ExactMatch
|
||||||
|
Decoded / Unsupported
|
||||||
|
absence de ksp-program-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boundary
|
||||||
|
|
||||||
|
`tests/dependency_boundary.rs` avance l'inventaire autorisé jusqu'au trait et maintient l'absence de preparer, logging, serde, `Any` et module public.
|
||||||
|
|
||||||
|
## 8. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/src/program_instruction_decoder.rs
|
||||||
|
crates/ksp-program-api/tests/external_implementation.rs
|
||||||
|
deltas/0.2.14/pre.004.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
crates/ksp-program-api/src/lib.rs
|
||||||
|
crates/ksp-program-api/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-program-api/tests/public_api.rs
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
README.md
|
||||||
|
RULES.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
docs/architecture/**
|
||||||
|
docs/rules/**
|
||||||
|
crates/ksp-core-lib/**
|
||||||
|
crates/ksp-interface-lib/**
|
||||||
|
crates/ksp-logging-lib/**
|
||||||
|
crates/ksp-*-transport-lib/**
|
||||||
|
crates/ksp-config-lib/**
|
||||||
|
crates/ksp-wallet-lib/**
|
||||||
|
crates/ksp-app-*/**
|
||||||
|
prompts/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Scope négatif maintenu
|
||||||
|
|
||||||
|
Cette tranche n'introduit pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime decoder registry
|
||||||
|
Vec<Box<dyn ProgramInstructionDecoder>>
|
||||||
|
object-safety promise
|
||||||
|
identity/version/coverage descriptor
|
||||||
|
priority/conflict policy
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
payload canonique D3
|
||||||
|
serde / JSON / Any
|
||||||
|
proof/confidence contextuels
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
ExecutionPolicy / Execution
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Gate opérateur attendu
|
||||||
|
|
||||||
|
Après application :
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe normal doit rester :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Une correction découverte par ce gate reste un `0.2.14-pre.004-fix.NNN` et n'avance pas `pre.005`.
|
||||||
|
|
||||||
|
## 13. Suite
|
||||||
|
|
||||||
|
Après gate vert, `pre.005` doit rester une tranche de hardening/API completeness :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bounds et sécurité Debug/error
|
||||||
|
inventaire exact des exports publics
|
||||||
|
absence de closed-world Program enum
|
||||||
|
absence de serde/codec/logging/runtime
|
||||||
|
scope négatif registry/preparer/payload D3
|
||||||
|
test de complétude release
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun nouveau contrat fonctionnel n'est prévu dans `pre.005`.
|
||||||
128
deltas/0.2.14/pre.005-fix.001.md
Normal file
128
deltas/0.2.14/pre.005-fix.001.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.005-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.005-fix.001` — faux positif du canari logging
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Ce correctif s'applique exclusivement après :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.005
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur du 28 août 2026 confirme :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 173 tables / 122 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test --workspace FAIL — ksp-logging-lib/tests/ownership.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
L'unique échec observé est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/tests/release_completeness.rs bypasses ksp-logging-lib via tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Diagnostic
|
||||||
|
|
||||||
|
`release_completeness.rs` doit vérifier que les sources de production Program API ne contiennent aucun chemin direct vers tracing. Le canari écrivait cependant lui-même ce chemin sous forme de chaîne littérale dans sa liste de motifs interdits.
|
||||||
|
|
||||||
|
Le scanner workspace de `ksp-logging-lib/tests/ownership.rs` inspecte tous les fichiers Rust des autres crates, tests d'intégration compris, et recherche textuellement ce chemin. Il classait donc le motif de test lui-même comme un bypass alors qu'aucune dépendance, importation ou utilisation de tracing n'existe dans `ksp-program-api`.
|
||||||
|
|
||||||
|
Le graphe normal observé reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Correction
|
||||||
|
|
||||||
|
Le canari conserve exactement la même interdiction sur les sources de production, mais construit désormais le motif recherché à partir de deux fragments. Son propre fichier ne contient donc plus le chemin direct littéral que le scanner logging doit légitimement refuser.
|
||||||
|
|
||||||
|
Aucune règle logging n'est contournée ou affaiblie. Le scanner de `ksp-logging-lib` reste inchangé.
|
||||||
|
|
||||||
|
Comme un fichier `.rs` est modifié, `VER-ID-007` et `VER-ID-010` imposent :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.14-pre.5.fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
La livraison est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.005-fix.001
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-program-api/tests/release_completeness.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Fichier ajouté
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.2.14/pre.005-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/src/**
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
crates/ksp-program-api/tests/security_hardening.rs
|
||||||
|
crates/ksp-logging-lib/**
|
||||||
|
docs/plans/**
|
||||||
|
docs/validation/**
|
||||||
|
README.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
prompts/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Scope négatif
|
||||||
|
|
||||||
|
Ce fix n'ajoute aucun :
|
||||||
|
|
||||||
|
```text
|
||||||
|
contrat Program
|
||||||
|
trait ou type public
|
||||||
|
dependency
|
||||||
|
logging runtime
|
||||||
|
registry
|
||||||
|
descriptor
|
||||||
|
codec
|
||||||
|
payload D3
|
||||||
|
preparer
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Validations de génération
|
||||||
|
|
||||||
|
Les audits statiques disponibles dans l'environnement de génération doivent rester propres. Cargo n'y est pas disponible ; aucun PASS Cargo n'est revendiqué par ce delta.
|
||||||
|
|
||||||
|
## 9. Gate opérateur attendu
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test -p ksp-logging-lib --test ownership
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Si ce gate est vert, `pre.005` est considéré corrigé et `pre.006` peut ouvrir le gate technique final sans développement fonctionnel.
|
||||||
217
deltas/0.2.14/pre.005.md
Normal file
217
deltas/0.2.14/pre.005.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.005.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.005` — adversarial/API hardening + completeness
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement sur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.13
|
||||||
|
+ 0.2.14-pre.001
|
||||||
|
+ 0.2.14-pre.002
|
||||||
|
+ 0.2.14-pre.003
|
||||||
|
+ 0.2.14-pre.004
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.004` est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS — 172 tables / 121 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 11 tests Rust
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté
|
||||||
|
```
|
||||||
|
|
||||||
|
La version workspace passe de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.4
|
||||||
|
```
|
||||||
|
|
||||||
|
à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.005
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Fermer la surface Program API déjà matérialisée sans ajouter de contrat fonctionnel :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bounds et sécurité Debug/error
|
||||||
|
inventaire exact des exports publics
|
||||||
|
inventaire exact des modules de production
|
||||||
|
absence de closed-world Program enum
|
||||||
|
absence de registry/descriptors/preparer/payload D3
|
||||||
|
absence de serde/codec/logging/runtime
|
||||||
|
associated output sans bound implicite
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Release completeness
|
||||||
|
|
||||||
|
Le nouveau `tests/release_completeness.rs` verrouille :
|
||||||
|
|
||||||
|
```text
|
||||||
|
10 réexports crate-root exacts
|
||||||
|
aucun pub mod
|
||||||
|
3 fichiers Rust de production exacts
|
||||||
|
2 enums publics exacts, tous deux non_exhaustive
|
||||||
|
1 trait public exact : ProgramInstructionDecoder
|
||||||
|
aucun ProgramKind / registry / autre famille decoder / preparer
|
||||||
|
aucun Any / serde / codec / logging / réseau / IO runtime
|
||||||
|
aucun dyn ProgramInstructionDecoder ou registry hétérogène anticipé
|
||||||
|
```
|
||||||
|
|
||||||
|
La surface publique reste donc ouverte par `Pubkey` et associated output, pas par un inventaire central fermé.
|
||||||
|
|
||||||
|
## 4. Hardening adversarial
|
||||||
|
|
||||||
|
Le nouveau `tests/security_hardening.rs` couvre trois propriétés.
|
||||||
|
|
||||||
|
### Input Interface maximal
|
||||||
|
|
||||||
|
Une instruction au maximum déjà admis par Interface :
|
||||||
|
|
||||||
|
```text
|
||||||
|
255 account metas
|
||||||
|
10 240 bytes de data
|
||||||
|
```
|
||||||
|
|
||||||
|
traverse `ProgramInstructionDecoder::decode` par référence et le decoder observe les tailles exactes. Program API n'introduit aucun second bound ni nouveau type d'input.
|
||||||
|
|
||||||
|
### Payload hostile et erreur Core
|
||||||
|
|
||||||
|
Un decoder de test reçoit un payload contenant un marqueur hostile puis retourne un `ksp_program_api::Error` sûr. Le contrat Program API ne copie automatiquement ni l'instruction ni ses bytes dans l'erreur, son `Display` ou son `Debug`.
|
||||||
|
|
||||||
|
L'hygiène des messages/contextes qu'une implémentation tierce construit volontairement reste la responsabilité de cette implémentation ; Program API n'ajoute aucun canal `Failed` parallèle susceptible de dupliquer le payload.
|
||||||
|
|
||||||
|
### Associated output sans bound implicite
|
||||||
|
|
||||||
|
Un second decoder de test utilise un output contenant `Rc<Cell<u8>>`. Ce type n'implémente donc pas `Send`/`Sync` et aucun `Debug`/`Clone` n'est dérivé. La compilation prouve que `type Decoded` reste sans bound implicite ; le `Debug` de l'outcome demeure `Decoded` sans rendre la valeur.
|
||||||
|
|
||||||
|
## 5. Dependency/runtime firewall
|
||||||
|
|
||||||
|
Le manifest de production reste inchangé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-core-lib
|
||||||
|
ksp-interface-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Les canaris refusent toujours :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
Config / Transport / Wallet / Store / Materializer
|
||||||
|
serde / serde_json / Any
|
||||||
|
borsh / bincode / wincode
|
||||||
|
reqwest / tokio / tonic / tauri / tracing
|
||||||
|
std::env / std::fs / std::net dans la production Program API
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/tests/release_completeness.rs
|
||||||
|
crates/ksp-program-api/tests/security_hardening.rs
|
||||||
|
deltas/0.2.14/pre.005.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
crates/ksp-program-api/src/**
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
crates/ksp-program-api/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-program-api/tests/external_implementation.rs
|
||||||
|
crates/ksp-program-api/tests/public_api.rs
|
||||||
|
README.md
|
||||||
|
RULES.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
docs/architecture/**
|
||||||
|
docs/rules/**
|
||||||
|
crates/ksp-core-lib/**
|
||||||
|
crates/ksp-interface-lib/**
|
||||||
|
crates/ksp-logging-lib/**
|
||||||
|
crates/ksp-*-transport-lib/**
|
||||||
|
crates/ksp-config-lib/**
|
||||||
|
crates/ksp-wallet-lib/**
|
||||||
|
crates/ksp-app-*/**
|
||||||
|
prompts/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Scope négatif maintenu
|
||||||
|
|
||||||
|
Cette tranche n'introduit pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
nouveau type ou trait de production
|
||||||
|
runtime decoder registry
|
||||||
|
Vec<Box<dyn ProgramInstructionDecoder>>
|
||||||
|
object-safety heterogeneous promise
|
||||||
|
identity/version/coverage descriptor
|
||||||
|
priority/conflict policy
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
payload canonique D3
|
||||||
|
serde / JSON / Any
|
||||||
|
proof/confidence contextuels
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
ExecutionPolicy / Execution
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Gate opérateur attendu
|
||||||
|
|
||||||
|
Après application :
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe normal doit rester :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Une correction découverte par ce gate reste un `0.2.14-pre.005-fix.NNN` et n'avance pas `pre.006`.
|
||||||
|
|
||||||
|
## 11. Suite
|
||||||
|
|
||||||
|
Après gate vert, `pre.006` est un **gate technique final sans développement fonctionnel**. Il ne doit ajouter ni contrat, ni decoder officiel, ni registry, ni preparer.
|
||||||
147
deltas/0.2.14/pre.006.md
Normal file
147
deltas/0.2.14/pre.006.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.006.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.006` — gate technique final Program API
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement après `0.2.14-pre.005-fix.001`.
|
||||||
|
|
||||||
|
Le gate opérateur du fix est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS / clean
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS / clean — 173 tables / 123 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2/2
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal Core + Interface uniquement
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
Le faux positif logging de `pre.005` est donc fermé sans changement de production ni de dépendances.
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Préparer le gate technique final de `0.2.14` sans rouvrir la surface fonctionnelle.
|
||||||
|
|
||||||
|
Aucun nouveau type, trait, test métier, registry, codec, runtime ou comportement n'est ajouté. Les canaris existants couvrent déjà :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
associated Decoded sans bounds implicites
|
||||||
|
extension externe avec Pubkey non enregistré
|
||||||
|
NoMatch / ProgramMatch / ExactMatch
|
||||||
|
Decoded / Unsupported / Err Core
|
||||||
|
input Interface maximal 255 comptes / 10 240 bytes
|
||||||
|
Debug outcome opaque
|
||||||
|
façade crate-root exacte
|
||||||
|
inventaire de production exact
|
||||||
|
absence de ProgramKind / registry / preparer / payload D3
|
||||||
|
absence de serde / JSON / Any / codecs
|
||||||
|
absence de logging runtime / IO
|
||||||
|
firewall Core + Interface
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Décision de tranche
|
||||||
|
|
||||||
|
`pre.006` reste un gate pur. Ajouter un nouveau test uniquement pour matérialiser le numéro de prerelease augmenterait la surface de changement sans renforcer une propriété non couverte.
|
||||||
|
|
||||||
|
La tranche modifie donc seulement la version workspace, le plan, la validation et le présent delta.
|
||||||
|
|
||||||
|
## 4. Version
|
||||||
|
|
||||||
|
La prerelease non-fix synchronise la version Cargo conformément à `VER-ID-009` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.5.fix.1
|
||||||
|
→
|
||||||
|
0.2.14-pre.6
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu après validation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.006
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun tag prerelease.
|
||||||
|
|
||||||
|
## 5. Fichier ajouté
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.2.14/pre.006.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## 8. Surface explicitement inchangée
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/src/**
|
||||||
|
crates/ksp-program-api/tests/**
|
||||||
|
crates/ksp-program-api/unit_tests/**
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompts/**
|
||||||
|
architectures / règles
|
||||||
|
```
|
||||||
|
|
||||||
|
La réconciliation README/USAGE et des documents durables reste réservée à `pre.007`. La préparation du prompt suivant, du CHANGELOG et du ROADMAP reste réservée à `pre.008`.
|
||||||
|
|
||||||
|
## 9. Validations exécutées à la génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS / clean
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS / clean — 174 tables / 124 fichiers
|
||||||
|
contrôle du diff PASS / 3 modifiés + 1 ajouté
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `cargo` n'est disponible dans l'environnement de génération ; aucun PASS Cargo local n'est revendiqué.
|
||||||
|
|
||||||
|
## 10. 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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test -p ksp-logging-lib --test ownership
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Décisions prises
|
||||||
|
|
||||||
|
- le scope fonctionnel `0.2.14` est figé ;
|
||||||
|
- aucun nouveau contrat Program n'est introduit ;
|
||||||
|
- aucun registry, payload D3 ou `ProgramExecutionPreparer` n'est anticipé ;
|
||||||
|
- Program API reste déclarative et sans logging runtime ;
|
||||||
|
- Core + Interface restent les seules dépendances normales ;
|
||||||
|
- aucun smoke réseau n'est pertinent pour cette crate instruction-only sans I/O ;
|
||||||
|
- `pre.007` ne commence qu'après gate `pre.006` intégralement vert.
|
||||||
|
|
||||||
|
## 12. Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question technique bloquante pour `0.2.14`. Après gate vert, la seule étape suivante est la réconciliation documentaire finale.
|
||||||
194
deltas/0.2.14/pre.007.md
Normal file
194
deltas/0.2.14/pre.007.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.007.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.007` — réconciliation documentaire finale Program API
|
||||||
|
|
||||||
|
## 1. Base
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.006
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.006`, exécuté le **28 août 2026**, est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audits Rust / export completeness / workspace PASS
|
||||||
|
audit Markdown PASS — 174 tables / 124 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2/2
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
La lane technique est donc fermée avant cette tranche.
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
`pre.007` est exclusivement le couloir de réconciliation documentaire finale de `0.2.14` défini par `VER-LIFECYCLE-006`.
|
||||||
|
|
||||||
|
Aucun fichier Rust, test, manifest de crate, dépendance, codec, runtime ou comportement n'est modifié. La tranche transforme les README/USAGE initiaux de Program API en références durables et aligne plan, validation, séquence fonctionnelle et index documentaires sur la surface réellement validée.
|
||||||
|
|
||||||
|
## 3. Version technique
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.6
|
||||||
|
->
|
||||||
|
0.2.14-pre.7
|
||||||
|
```
|
||||||
|
|
||||||
|
Le bump est mécanique pour une prerelease non-fix. Aucun autre manifest/version applicative n'est synchronisé artificiellement.
|
||||||
|
|
||||||
|
Commit/tag prerelease attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.007
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Surface finale candidate documentée
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
NoMatch / ProgramMatch / ExactMatch
|
||||||
|
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
Decoded(Decoded) / Unsupported
|
||||||
|
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction) -> ProgramInstructionRecognition
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
```
|
||||||
|
|
||||||
|
La façade crate-root conserve exactement 10 exports et trois modules/fichiers Rust de production. Le type `Decoded` reste possédé par l'implémentation et sans bound implicite `Debug/Clone/Send/Sync`.
|
||||||
|
|
||||||
|
## 5. Open-world et hardening final
|
||||||
|
|
||||||
|
Les références durables enregistrent les preuves acquises :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Program Pubkey non enregistré accepté
|
||||||
|
external implementation canary PASS
|
||||||
|
input Interface maximal 255 accounts + 10_240 bytes
|
||||||
|
payload hostile aucun echo automatique ajouté par Program API
|
||||||
|
Debug outcome valeur Decoded non rendue
|
||||||
|
closed-world Program enum absent
|
||||||
|
registry / descriptor / priority absents
|
||||||
|
ProgramExecutionPreparer absent
|
||||||
|
serde / JSON / Any / codecs absents
|
||||||
|
logging / runtime / filesystem / environment / I/O absents
|
||||||
|
```
|
||||||
|
|
||||||
|
Une implémentation tierce reste responsable du contenu des erreurs qu'elle produit explicitement ; Program API n'ajoute aucun canal parallèle ni copie automatique du payload hostile.
|
||||||
|
|
||||||
|
## 6. Dependency firewall final
|
||||||
|
|
||||||
|
Le graphe normal confirmé par l'opérateur reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
│ └── solana-pubkey
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `ksp-program-lib`, runtime, codec, serde, logging, Transport, Store, Wallet, Materializer, Config ou Tauri n'est introduit.
|
||||||
|
|
||||||
|
## 7. Références durables réconciliées
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
docs/000-README.md
|
||||||
|
docs/plans/000-README.md
|
||||||
|
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/000-README.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Le README fixe l'ownership, l'inventaire public exact, les semantics Recognition/Outcome, le trait externe, le hardening et les frontières. `USAGE.md` documente uniquement la façade crate-root, l'implémentation externe, la sélection explicite, le decode outcome, les erreurs et le Debug sûr.
|
||||||
|
|
||||||
|
La séquence fonctionnelle enregistre le contenu effectivement livré par `0.2.14` et maintient les reports de `ksp-program-lib`, du payload D3, du registry runtime, des autres familles de decoder et du preparer jusqu'aux vertical slices réels.
|
||||||
|
|
||||||
|
## 8. Fichiers ajoutés/modifiés
|
||||||
|
|
||||||
|
Ajouté :
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.2.14/pre.007.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifiés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
docs/000-README.md
|
||||||
|
docs/plans/000-README.md
|
||||||
|
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/000-README.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun fichier supprimé.
|
||||||
|
|
||||||
|
## 9. Surfaces volontairement inchangées
|
||||||
|
|
||||||
|
Cette tranche ne touche pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/src/**
|
||||||
|
crates/ksp-program-api/tests/**
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
docs/architecture/**
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompts/**
|
||||||
|
```
|
||||||
|
|
||||||
|
L'architecture durable `006-WIRE_AND_PROGRAM.md` reste compatible avec la foundation réellement livrée : ses registry/preparer/autres familles sont des surfaces futures explicitement non matérialisées par `0.2.14`.
|
||||||
|
|
||||||
|
## 10. Gate opérateur
|
||||||
|
|
||||||
|
La tranche étant documentaire hors bump workspace, le gate demandé reste déterministe :
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test -p ksp-logging-lib --test ownership
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun smoke réseau/live ni nouveau `cargo tree` n'est requis si l'overlay est appliqué exactement : ces preuves appartiennent à la lane technique fermée par `pre.006`.
|
||||||
|
|
||||||
|
## 11. Suite
|
||||||
|
|
||||||
|
Si le gate est vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.008 — préparation minimale de publication
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.008` reste limitée à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompt de démarrage 0.3.1
|
||||||
|
deltas/0.2.14/pre.008.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun code, test, README/USAGE, plan, validation ou architecture ne doit être rouvert dans cette lane de publication minimale sauf défaut réel découvert au gate.
|
||||||
259
deltas/0.2.14/pre.008.md
Normal file
259
deltas/0.2.14/pre.008.md
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.008.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.008` — préparation de publication et prompt `0.3.1`
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.007
|
||||||
|
workspace.package.version = 0.2.14-pre.7
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.007` fourni le 28 août 2026 est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 175 tables / 125 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2 tests
|
||||||
|
cargo test --workspace PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tests live/bench explicitement `ignored` restent volontairement hors de ce gate documentaire.
|
||||||
|
|
||||||
|
`pre.007` a fermé la réconciliation durable de `ksp-program-api`, des indexes, du plan et de la validation sans rouvrir `CHANGELOG.md`, `ROADMAP.md` ni le prompt suivant.
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Dernière prerelease avant `rel.001`, strictement limitée à la préparation de publication :
|
||||||
|
|
||||||
|
- ajouter l'entrée préparatoire stable `0.2.14` dans `CHANGELOG.md` ;
|
||||||
|
- fermer `0.2.14` dans `ROADMAP.md` ;
|
||||||
|
- produire `prompts/020-V0_3_1_START_PROMPT.md` ;
|
||||||
|
- bump mécanique de la version workspace vers `0.2.14-pre.8` ;
|
||||||
|
- ne rouvrir aucun code, test, README/USAGE, plan, validation, architecture ou règle normative.
|
||||||
|
|
||||||
|
## 3. Version Cargo
|
||||||
|
|
||||||
|
Publication non-fix de prerelease :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.7
|
||||||
|
-> 0.2.14-pre.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune crate membre ne redéfinit localement la version.
|
||||||
|
|
||||||
|
## 4. Changelog / Roadmap
|
||||||
|
|
||||||
|
`CHANGELOG.md` résume la surface stable candidate `0.2.14` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api instruction-only
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
10 exports crate-root / 3 modules de production
|
||||||
|
implémentation externe avec Program Pubkey opaque
|
||||||
|
Core + Interface uniquement
|
||||||
|
aucun registry/runtime/serde/codec/Store/Materializer/Execution
|
||||||
|
18 tests Program API au gate final
|
||||||
|
```
|
||||||
|
|
||||||
|
`ROADMAP.md` passe `0.2.14` à `[X]` et conserve `0.3.1` comme prochaine release ouverte :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api + ksp-store-lib
|
||||||
|
PostgreSQL de référence
|
||||||
|
RAW seulement
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Prompt `0.3.1`
|
||||||
|
|
||||||
|
Le nouveau prompt a été 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/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/008-DATA_MATERIALIZATION_AND_STORE.md
|
||||||
|
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
|
||||||
|
|
||||||
|
prompts/018-V0_2_13_START_PROMPT.md
|
||||||
|
prompts/019-V0_2_14_START_PROMPT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
L'archive historique suivante a également été reconnue pour déterminer si elle devait être requise dans la prochaine session :
|
||||||
|
|
||||||
|
```text
|
||||||
|
khadhroony-bot3_v0.5.3-pre.005-fix010.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle contient notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ks-store/**
|
||||||
|
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
|
||||||
|
docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Conclusion : **l'archive est obligatoire pour `0.3.1-pre.001`**, mais seulement comme source historique à classer sous :
|
||||||
|
|
||||||
|
```text
|
||||||
|
REPRENDRE / REDESSINER / REPORTER / REJETER
|
||||||
|
```
|
||||||
|
|
||||||
|
L'ancien `ks-store` couvre RAW + CORE + DECODE/materialization et ne doit donc pas être recopié dans `0.3.1`.
|
||||||
|
|
||||||
|
Le prompt protège explicitement les frontières :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1 Store API + PostgreSQL RAW-only
|
||||||
|
0.3.2 wires génériques Interface acquisition/CORE
|
||||||
|
0.3.3 Job API + backfill RAW
|
||||||
|
0.3.4 application backfill/inspection RAW
|
||||||
|
plus tard CORE -> DECODE -> SPECIALIZED
|
||||||
|
```
|
||||||
|
|
||||||
|
Il impose en `pre.001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
audit base stable
|
||||||
|
audit kbot3 Store
|
||||||
|
audit PostgreSQL + dépendance Rust candidate avec sources actuelles
|
||||||
|
inventaire RAW minimal
|
||||||
|
ownership API/impl/composition
|
||||||
|
dependency graph
|
||||||
|
API backend-agnostic candidate
|
||||||
|
schema PostgreSQL RAW candidate
|
||||||
|
migration strategy
|
||||||
|
threat model
|
||||||
|
PostgreSQL integration strategy
|
||||||
|
sizing + prévision souple recalibrée
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Prévision souple intégrée au prompt
|
||||||
|
|
||||||
|
La prévision initiale réserve distinctement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.001 audit/sizing
|
||||||
|
pre.002 scaffold API + lib
|
||||||
|
pre.003 contrats RAW
|
||||||
|
pre.004 Store API + backend externe canari
|
||||||
|
pre.005 PostgreSQL runtime foundation
|
||||||
|
pre.006 schema/migrations RAW
|
||||||
|
pre.007 writes/idempotence/atomicité
|
||||||
|
pre.008 reads/pagination/notification si retenue
|
||||||
|
pre.009 hardening/completeness
|
||||||
|
pre.010 gate PostgreSQL réel
|
||||||
|
pre.011 réconciliation documentaire
|
||||||
|
pre.012 préparation de publication
|
||||||
|
rel.001 publication stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette prévision reste souple et doit être recalibrée par `0.3.1-pre.001` selon le scope RAW réel.
|
||||||
|
|
||||||
|
## 7. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/020-V0_3_1_START_PROMPT.md
|
||||||
|
deltas/0.2.14/pre.008.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## 10. Surfaces explicitement non rouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
README.md
|
||||||
|
RULES.md
|
||||||
|
.env.example
|
||||||
|
config/**
|
||||||
|
crates/**
|
||||||
|
docs/**
|
||||||
|
prompts/001..019
|
||||||
|
```
|
||||||
|
|
||||||
|
En particulier :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/**
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
docs/architecture/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Validations de préparation
|
||||||
|
|
||||||
|
À exécuter sur l'arbre reconstruit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
audit Rust workspace
|
||||||
|
audit Markdown
|
||||||
|
contrôle exact du payload overlay
|
||||||
|
contrôle des 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 ce delta.
|
||||||
|
|
||||||
|
## 12. 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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test -p ksp-logging-lib --test ownership
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun smoke réseau/live ni nouveau graphe Cargo n'est requis : cette lane ne modifie ni code, ni dépendance, ni runtime.
|
||||||
|
|
||||||
|
## 13. Étape suivante
|
||||||
|
|
||||||
|
Si le gate reste vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-rel.001
|
||||||
|
```
|
||||||
|
|
||||||
|
La publication stable devra être strictement mécanique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.14
|
||||||
|
delta deltas/0.2.14/rel.001.md
|
||||||
|
commit de publication
|
||||||
|
tag stable v0.2.14 après validation opérateur
|
||||||
|
```
|
||||||
|
|
||||||
|
`rel.001` ne doit modifier ni `CHANGELOG.md`, ni `ROADMAP.md`, ni le prompt `0.3.1`, sauf anomalie découverte qui renverrait d'abord vers une prerelease dédiée conformément au workflow.
|
||||||
307
deltas/0.2.14/rel.001.md
Normal file
307
deltas/0.2.14/rel.001.md
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
<!-- file: deltas/0.2.14/rel.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-rel.001` — publication stable Program API foundation
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Base directe attendue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.008
|
||||||
|
workspace.package.version = 0.2.14-pre.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu pour cette livraison :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-rel.001
|
||||||
|
```
|
||||||
|
|
||||||
|
Tag stable attendu après validation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Responsabilité de `rel.001`
|
||||||
|
|
||||||
|
Cette livraison effectue uniquement la mécanique de publication stable définie par le cycle KSP et `VER-LIFECYCLE-012`.
|
||||||
|
|
||||||
|
Elle :
|
||||||
|
|
||||||
|
- passe `workspace.package.version` de `0.2.14-pre.8` à `0.2.14` ;
|
||||||
|
- 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 opérateur du gate technique final `0.2.14-pre.006`, exécuté le **28 août 2026**, a validé :
|
||||||
|
|
||||||
|
```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 — 174 tables / 124 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2 tests
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS / inspecté
|
||||||
|
cargo tree --duplicates PASS / inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe normal ciblé est resté strictement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
│ └── solana-pubkey
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de la réconciliation documentaire `0.2.14-pre.007`, exécuté le **28 août 2026**, a ensuite validé :
|
||||||
|
|
||||||
|
```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 — 175 tables / 125 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Enfin, le gate opérateur de la préparation minimale de publication `0.2.14-pre.008`, exécuté le **28 août 2026**, a validé :
|
||||||
|
|
||||||
|
```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 — 175 tables / 127 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.008` n'a rouvert aucun code, test, README/USAGE, plan, validation ou architecture. Elle a préparé uniquement `Cargo.toml`, `CHANGELOG.md`, `ROADMAP.md`, le prompt suivant et son delta.
|
||||||
|
|
||||||
|
Le seul incident technique de la release, le faux positif cross-crate du scanner Logging observé en `pre.005`, a été corrigé dans `pre.005-fix.001` sans changement fonctionnel ; son gate ciblé puis le workspace complet sont passés avant l'ouverture de `pre.006`.
|
||||||
|
|
||||||
|
## 4. Version stable publiée
|
||||||
|
|
||||||
|
La version Cargo devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.14
|
||||||
|
```
|
||||||
|
|
||||||
|
La release stable publiée est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14 — Program API foundation
|
||||||
|
```
|
||||||
|
|
||||||
|
La surface stable comprend :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
NoMatch
|
||||||
|
ProgramMatch
|
||||||
|
ExactMatch
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
Decoded(Decoded)
|
||||||
|
Unsupported
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
associated type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction) -> ProgramInstructionRecognition
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
10 exports crate-root exacts
|
||||||
|
3 modules/fichiers Rust de production exacts
|
||||||
|
Program Pubkey opaque non enregistré supporté
|
||||||
|
output Decoded possédé par l'implémentation externe
|
||||||
|
aucun bound implicite Debug / Clone / Send / Sync sur Decoded
|
||||||
|
```
|
||||||
|
|
||||||
|
La dependency direction stable reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
-> ksp-core-lib
|
||||||
|
-> ksp-interface-lib
|
||||||
|
-> ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Sont volontairement absents de cette foundation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
registry runtime / dyn registry
|
||||||
|
ProgramKind / descriptor global
|
||||||
|
identity / version / coverage
|
||||||
|
proofs / diagnostics de replay
|
||||||
|
ProgramAccountDecoder
|
||||||
|
ProgramEventDecoder
|
||||||
|
ProgramReturnDataDecoder
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
payload canonique D3
|
||||||
|
serde / serde_json / Any
|
||||||
|
borsh / wincode / bincode
|
||||||
|
ksp-logging-lib / tracing runtime
|
||||||
|
réseau / filesystem / environnement
|
||||||
|
Store / Materializer / Wallet / Config / Tauri
|
||||||
|
```
|
||||||
|
|
||||||
|
`ProgramInstructionRecognition::ExactMatch` reste une assertion de reconnaissance du decoder et non une preuve indépendante. Les erreurs réelles restent transportées par le `Result` Core ; `Unsupported` représente un état de décodage non supporté et non une erreur artificielle.
|
||||||
|
|
||||||
|
## 5. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.2.14/rel.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Validations exécutées pour préparer le delta
|
||||||
|
|
||||||
|
Contrôles statiques de l'overlay :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml version 0.2.14
|
||||||
|
Cargo.toml header version 322
|
||||||
|
payload rel.001 2 fichiers exactement
|
||||||
|
CHANGELOG.md non rouvert
|
||||||
|
ROADMAP.md non rouvert
|
||||||
|
prompt 020 non rouvert
|
||||||
|
README/USAGE non rouverts
|
||||||
|
plan / validation non rouverts
|
||||||
|
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. Le gate stable final reste à exécuter après application du delta et avant commit/tag.
|
||||||
|
|
||||||
|
## 9. Validations non exécutées pendant la préparation
|
||||||
|
|
||||||
|
Ne sont volontairement pas rejoués pour construire l'overlay :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test -p ksp-logging-lib --test ownership
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
smokes réseau/live
|
||||||
|
```
|
||||||
|
|
||||||
|
Le changement est limité au signal de version Cargo et au présent delta ; ces contrôles doivent uniquement être rejoués dans le gate stable défini ci-dessous lorsque requis.
|
||||||
|
|
||||||
|
## 10. Validation stable après application
|
||||||
|
|
||||||
|
Exécuter avant publication :
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun smoke réseau/live ni nouveau graphe Cargo n'est requis : `rel.001` ne modifie ni runtime, ni code métier, ni dépendances.
|
||||||
|
|
||||||
|
## 11. Décisions de publication
|
||||||
|
|
||||||
|
Aucune nouvelle décision fonctionnelle ou architecturale n'est introduite par `rel.001`.
|
||||||
|
|
||||||
|
La publication confirme uniquement que :
|
||||||
|
|
||||||
|
1. `0.2.14-pre.008` est la candidate directe de la release stable ;
|
||||||
|
2. les lanes technique, documentaire et préparation de publication sont closes ;
|
||||||
|
3. `CHANGELOG.md`, `ROADMAP.md` et `prompts/020-V0_3_1_START_PROMPT.md` restent inchangés dans `rel.001` ;
|
||||||
|
4. `ksp-program-api` reste instruction-only et sans runtime `ksp-program-lib` ;
|
||||||
|
5. l'output typé reste possédé par l'implémentation et aucun registry hétérogène n'est promis par la foundation ;
|
||||||
|
6. l'archive historique `khadhroony-bot3_v0.5.3-pre.005-fix010.zip` est obligatoire pour l'audit `0.3.1-pre.001`, mais reste une source historique non normative ;
|
||||||
|
7. `0.3.1` ouvre `ksp-store-api` + `ksp-store-lib` avec PostgreSQL de référence et persistence RAW-only ;
|
||||||
|
8. la prochaine release s'ouvre exclusivement depuis le tag stable `v0.2.14`.
|
||||||
|
|
||||||
|
## 12. Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question ouverte ne bloque la publication de `0.2.14`.
|
||||||
|
|
||||||
|
Les choix de registry/runtime Program, payload canonique D3, decoders account/event/return-data et préparation d'exécution restent explicitement reportés. Ils ne constituent pas une dette bloquante de cette foundation et ne doivent pas être réintroduits pendant la publication stable.
|
||||||
|
|
||||||
|
Le détail du modèle RAW, de l'API backend-agnostic Store, du schéma PostgreSQL et des migrations appartient à l'audit/sizing de `0.3.1-pre.001`.
|
||||||
|
|
||||||
|
## 13. Commit et tag stable
|
||||||
|
|
||||||
|
Après succès du gate stable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commit : v0.2.14-rel.001
|
||||||
|
tag : v0.2.14
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun tag intermédiaire `rel.001` n'est requis.
|
||||||
|
|
||||||
|
Commandes Git typiques :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add Cargo.toml deltas/0.2.14/rel.001.md
|
||||||
|
git commit -m "v0.2.14-rel.001"
|
||||||
|
git tag v0.2.14
|
||||||
|
git push
|
||||||
|
git push origin v0.2.14
|
||||||
|
```
|
||||||
|
|
||||||
|
## 14. Suite
|
||||||
|
|
||||||
|
Après publication du tag stable `v0.2.14`, ouvrir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.001 — Store RAW foundation
|
||||||
|
```
|
||||||
|
|
||||||
|
avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/020-V0_3_1_START_PROMPT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
La session suivante doit recevoir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
archive opérateur exacte de v0.2.14
|
||||||
|
khadhroony-bot3_v0.5.3-pre.005-fix010.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`0.3.1-pre.001` reste une tranche d'audit/brainstorming/sizing avant toute implémentation lourde ; elle doit auditer l'ancien `ks-store` et PostgreSQL actuel, définir le minimum RAW réellement replayable, choisir le modèle API/backend extensible et préserver les reports `0.3.2`/`0.3.3`/`0.3.4` ainsi que CORE/DECODE/SPECIALIZED.
|
||||||
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: 65 -->
|
<!-- version: 68 -->
|
||||||
|
|
||||||
# Plans KSP
|
# Plans KSP
|
||||||
|
|
||||||
@@ -28,7 +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 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.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
|
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
|
||||||
<!-- version: 94 -->
|
<!-- version: 95 -->
|
||||||
|
|
||||||
# Séquence des releases fonctionnelles KSP
|
# Séquence des releases fonctionnelles KSP
|
||||||
|
|
||||||
@@ -528,9 +528,29 @@ Aucune `ksp-interface-api` séparée n'est retenue pour l'instant. Les codecs/la
|
|||||||
|
|
||||||
### `0.2.14` — Program API foundation
|
### `0.2.14` — Program API foundation
|
||||||
|
|
||||||
Introduire `ksp-program-api`, sans suffixe `-lib`, comme contrat d'extension Program.
|
`ksp-program-api`, sans suffixe `-lib`, matérialise la première façade publique ouverte du domaine Program. La candidate reste volontairement instruction-only et dépend uniquement de Core + Interface.
|
||||||
|
|
||||||
`ksp-program-lib` et les vertical slices réels arrivent plus tard.
|
La surface commune est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition
|
||||||
|
NoMatch / ProgramMatch / ExactMatch
|
||||||
|
|
||||||
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
Decoded(Decoded) / Unsupported
|
||||||
|
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction) -> ProgramInstructionRecognition
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
```
|
||||||
|
|
||||||
|
L'output concret reste possédé par l'implémentation et ne reçoit aucun bound implicite `Debug/Clone/Send/Sync`. Une crate externe peut implémenter le trait pour un Program Pubkey absent du registry Core ; aucun enum central de Programs, `Any`, JSON, registry runtime ou descriptor global n'est requis.
|
||||||
|
|
||||||
|
Le hardening final verrouille l'inventaire crate-root exact, le passage d'une `ProgramInstruction` Interface maximale par référence, l'absence d'echo automatique de payload hostile, l'absence de default methods et le firewall `Program API -> Core + Interface`. `pre.006` ferme le gate technique avec audits/check/Clippy/tests workspace et graphe Cargo verts. [`../../crates/ksp-program-api/README.md`](../../crates/ksp-program-api/README.md) et [`../../crates/ksp-program-api/USAGE.md`](../../crates/ksp-program-api/USAGE.md) deviennent les références durables de la candidate.
|
||||||
|
|
||||||
|
Restent explicitement reportés : `ksp-program-lib`, payload canonique D3, registry runtime, identity/version/coverage génériques, autres familles de decoder et `ProgramExecutionPreparer`. Ils seront introduits uniquement par les vertical slices qui démontreront leurs contrats réels.
|
||||||
|
|
||||||
## Architecture durable : RAW -> CORE -> DECODE -> SPECIALIZED
|
## Architecture durable : RAW -> CORE -> DECODE -> SPECIALIZED
|
||||||
|
|
||||||
|
|||||||
801
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
Normal file
801
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
Normal file
@@ -0,0 +1,801 @@
|
|||||||
|
<!-- file: docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md -->
|
||||||
|
<!-- version: 7 -->
|
||||||
|
|
||||||
|
# Plan `0.2.14` — Program API foundation
|
||||||
|
|
||||||
|
## 1. Statut et base
|
||||||
|
|
||||||
|
Ce plan est établi par `0.2.14-pre.001` à partir de la release stable `v0.2.13` et de l'archive historique obligatoire `khadhroony-bot3_v0.5.3-pre.005-fix010.zip`.
|
||||||
|
|
||||||
|
La base KSP vérifiée à l'ouverture est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.13
|
||||||
|
deltas/0.2.13/rel.001.md présent
|
||||||
|
prompts/019-V0_2_14_START_PROMPT.md présent
|
||||||
|
ksp-interface-lib présent
|
||||||
|
ksp-program-api absent
|
||||||
|
ksp-program-lib absent
|
||||||
|
```
|
||||||
|
|
||||||
|
L'archive opérateur ne contient pas de metadata Git exploitable ; le tag `v0.2.13` ne peut donc pas être revérifié localement. L'identité stable est établie par la version Cargo, le delta `rel.001`, le prompt suivant et la surface Interface publiée. Aucun écart bloquant n'a été trouvé entre la base réelle et le prompt.
|
||||||
|
|
||||||
|
Après application de `pre.001`, la version Cargo cible est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Mission recalibrée
|
||||||
|
|
||||||
|
`0.2.14` introduit `ksp-program-api` comme première crate publique extensible du domaine Program.
|
||||||
|
|
||||||
|
Le scope est volontairement réduit à une **foundation de décodage d'instruction typée** :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Core
|
||||||
|
-> Pubkey + Error/Result
|
||||||
|
|
||||||
|
Interface
|
||||||
|
-> ProgramAccountMeta + ProgramInstruction
|
||||||
|
|
||||||
|
Program API
|
||||||
|
-> reconnaissance instruction-local
|
||||||
|
-> outcome de décodage minimal
|
||||||
|
-> trait ProgramInstructionDecoder ouvert
|
||||||
|
-> output associé possédé par l'implémentation
|
||||||
|
```
|
||||||
|
|
||||||
|
La release ne crée pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
payload canonique D3
|
||||||
|
registry runtime hétérogène
|
||||||
|
identity/version runtime de decoder
|
||||||
|
coverage matrix générique
|
||||||
|
ProgramAccountDecoder
|
||||||
|
ProgramEventDecoder
|
||||||
|
ProgramReturnDataDecoder
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
ksp-program-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce réduction évite de figer des contrats appartenant aux futures couches CORE, DECODE, Materializer, Store ou Execution.
|
||||||
|
|
||||||
|
## 3. Sources KSP relues
|
||||||
|
|
||||||
|
Le gate `pre.001` a relu les sources prescrites par le prompt :
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
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/006-WIRE_AND_PROGRAM.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
|
||||||
|
```
|
||||||
|
|
||||||
|
Les règles structurantes sont notamment `KSP-NAME-002..003`, `KSP-API-001..007`, `KSP-PROGRAM-001..006`, `DEP-LOG-005`, `DEP-PROGRAM-001..004`, `DEP-WIRE-001..007`, `DEP-PIPE-007..008`, `DEP-SOL-001..006`, `DEP-PROTO-001..005` et `DEP-CARGO-001..007`.
|
||||||
|
|
||||||
|
Le résultat normatif est sans ambiguïté : `ksp-program-api` porte des contrats ouverts, reste principalement déclaratif, doit être implémentable depuis une crate externe, utilise les types KSP existants et ne dépend pas des couches runtime supérieures.
|
||||||
|
|
||||||
|
## 4. Inventaire KSP actuel
|
||||||
|
|
||||||
|
### 4.1 Core
|
||||||
|
|
||||||
|
`ksp-core-lib` fournit déjà :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Error
|
||||||
|
ErrorCode
|
||||||
|
ErrorContext
|
||||||
|
Result
|
||||||
|
Pubkey
|
||||||
|
registry KSP des Program IDs fondamentaux
|
||||||
|
```
|
||||||
|
|
||||||
|
Le registry Core est un inventaire KSP de Program IDs fondamentaux. Il n'est pas un registry d'implémentations Program et ne doit pas devenir un closed-world gate : un decoder externe peut viser un `Pubkey` absent de ce registry.
|
||||||
|
|
||||||
|
### 4.2 Interface
|
||||||
|
|
||||||
|
La surface stable `0.2.13` fournit exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Pubkey
|
||||||
|
ProgramAccountMeta
|
||||||
|
ProgramInstruction
|
||||||
|
MAX_PROGRAM_INSTRUCTION_ACCOUNTS = 255
|
||||||
|
MAX_PROGRAM_INSTRUCTION_DATA_LEN = 10_240
|
||||||
|
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
|
||||||
|
```
|
||||||
|
|
||||||
|
`ProgramInstruction` apporte déjà l'input minimal nécessaire au premier decoder :
|
||||||
|
|
||||||
|
```text
|
||||||
|
program_id: Pubkey
|
||||||
|
accounts: ordered bounded ProgramAccountMeta slice
|
||||||
|
data: bounded opaque byte slice
|
||||||
|
```
|
||||||
|
|
||||||
|
Les comptes conservent ordre et doublons. Les Program IDs sont opaques. Le `Debug` de l'instruction ne copie ni accounts ni bytes. Il n'existe donc aucune lacune Interface à combler pour le scope instruction-only.
|
||||||
|
|
||||||
|
### 4.3 Pipeline futur
|
||||||
|
|
||||||
|
La frontière durable reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RAW -> CORE -> DECODE -> SPECIALIZED
|
||||||
|
```
|
||||||
|
|
||||||
|
`RAW -> CORE` ne dépend pas de Program. Le futur `CORE -> DECODE` pourra fournir une instruction Interface accompagnée d'un contexte CORE séparé lorsque ce contexte existera. `0.2.14` ne crée pas ce contexte par anticipation.
|
||||||
|
|
||||||
|
## 5. Audit historique kbot3
|
||||||
|
|
||||||
|
### 5.1 Fichiers audités
|
||||||
|
|
||||||
|
Les contrats et usages historiques ont été relus dans :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ks-lib/src/decoder/api/contracts.rs
|
||||||
|
ks-lib/src/decoder/api/decoder.rs
|
||||||
|
ks-lib/src/decoder/api.rs
|
||||||
|
ks-lib/src/model/decoded.rs
|
||||||
|
ks-lib/src/model/replay.rs
|
||||||
|
ks-lib/src/model/solana.rs
|
||||||
|
ks-lib/src/executor/api/execution.rs
|
||||||
|
ks-lib/src/executor/api/executor.rs
|
||||||
|
ks-lib/src/executor/api.rs
|
||||||
|
ks-lib/src/decoder/solana/core/decoder.rs
|
||||||
|
ks-lib/src/decoder/spl/token/decoder.rs
|
||||||
|
ks-lib/src/decoder/spl/token_2022/decoder.rs
|
||||||
|
ks-lib/src/lib.rs
|
||||||
|
ks-lib/Cargo.toml
|
||||||
|
ks-lib/README.md
|
||||||
|
ks-lib/USAGE.md
|
||||||
|
docs/OPERATION_NAMING_CONVENTION.md
|
||||||
|
docs/IDL_AUDIT.md
|
||||||
|
docs/IDL_TO_KB_LIB_NOMENCLATURE.md
|
||||||
|
docs/architecture/ARCHITECTURE.md
|
||||||
|
docs/architecture/CRATE_MAP.md
|
||||||
|
docs/architecture/PIPELINE_ARCHITECTURE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Les trois decoders concrets montrent que l'ancien `DcApiInstructionDecoder` était réellement utilisé pour `identity`, `surfaces`, `coverage`, `recognize` puis `decode`, mais contre un input contextualisé contenant signature, slot, instruction path, transaction failure, hashes, logs, balance changes et JSON. Ces dépendances contextuelles ne sont pas présentes dans la foundation KSP actuelle.
|
||||||
|
|
||||||
|
`ks-lib` regroupait en outre modèles, decoders, executors et materializers avec un graphe comprenant codecs, serde/JSON, interfaces Solana et tracing. Cette ownership monolithique est incompatible avec les frontières KSP actuelles.
|
||||||
|
|
||||||
|
### 5.2 Matrice d'héritage
|
||||||
|
|
||||||
|
| Concept historique | Observation réelle kbot3 | Décision | Application KSP `0.2.14` |
|
||||||
|
|-----------------------------------|----------------------------------------------------------------------|------------|-------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `DcApiDecoderIdentity` | nom et version alloués en `String` | REPORTER | aucune identity runtime tant qu'aucun registry ou replay persistant ne la consomme |
|
||||||
|
| `DcApiDecoderSurface` | Program ID textuel, surface code et priorité | REDESSINER | Program IDs typés; surface code et priorité reportés avec le registry |
|
||||||
|
| `DcApiDecoderCoverageDeclaration` | matrice sérialisable instruction/event/discriminator | REPORTER | couverture machine-readable reportée au premier besoin d'inventaire/runtime |
|
||||||
|
| `DcApiDecoderRecognition` | compatible, exact, priority et codes textuels | REPRENDRE | reconnaissance conservée mais réduite à trois niveaux sans priorité ni strings |
|
||||||
|
| `DcApiDecoderOutcomeStatus` | `Decoded`, `Ignored`, `Unsupported`, `Failed` | REDESSINER | `Decoded(T)` ou `Unsupported`; `Failed` devient `Err`, `Ignored` n'est pas justifié pour un decoder d'instruction |
|
||||||
|
| `DcApiDecoderDiagnostic` | code, message et retriable sérialisables | REPORTER | `ksp_core_lib::Error/Result` suffit à la foundation; diagnostic Program dédié reporté |
|
||||||
|
| `DcApiDecoderProof` | preuve et confidence, dont logs/balances/heuristique | REPORTER | aucun proof sans contexte CORE réel; l'exactitude de recognition reste une assertion du decoder |
|
||||||
|
| `DcApiInstructionDecoder` | trait `Send + Sync` avec identity, coverage, recognize, decode | REPRENDRE | trait instruction-only conservé, input remplacé par `ProgramInstruction`, output devient associated type |
|
||||||
|
| `DcApiProtocolDecoder` | trait générique parallèle avec support `No/Maybe/Yes` | REJETER | pas de second trait monolithique; les capabilities restent séparées par trait |
|
||||||
|
| `MdCoreInstructionReplayInput` | input JSON riche de transaction/CPI/logs/balances | REPORTER | futur CORE; aucune reconstruction en `0.2.14` |
|
||||||
|
| `MdDecodedProtocolEvent` | identité événement liée à signature, slot, path et strings protocole | REPORTER | futur contrat DECODE/Materializer; absent de la foundation |
|
||||||
|
| `MdProgramId` / `MdPubkey` | wrappers `String` | REJETER | `ksp_core_lib::Pubkey` reste canonique |
|
||||||
|
| `MdInstructionPath` | path CPI textuel | REPORTER | futur CORE; absent de `ProgramInstruction` |
|
||||||
|
| `ExApiExecutionRequest` | operation code + `payload_json` générique | REJETER | ne pas créer de JSON générique pour masquer un contrat d'intent non conçu |
|
||||||
|
| `ExApiExecutionCapability` | supported/unsupported avec strings | REDESSINER | concept utile pour un futur preparer, mais aucun contrat execution en `0.2.14` |
|
||||||
|
| `ExApiPreparedExecutionPlan` | instructions, signers, policy, spend et metadata | REDESSINER | séparer plus tard préparation technique et policy; réutiliser `ProgramInstruction` pour le wire |
|
||||||
|
| `ExApiInstructionExecutor` | support + plan générique JSON | REJETER | concept `ProgramExecutor` abandonné |
|
||||||
|
| `ExApiTypedInstructionExecutor` | associated `Intent` et build pur d'un plan | REPRENDRE | idée de préparation pure conservée conceptuellement pour futur `ProgramExecutionPreparer` |
|
||||||
|
| policy intégrée au plan | cluster, simulation, spend, blockhash, post-validation | REJETER | owner futur `ksp-execution-policy-api` / `ksp-execution-lib` |
|
||||||
|
| `ks-lib` monolithique | decoder + executor + materializer + model + codecs | REJETER | frontières `Interface`, `Program API`, futurs `Program Lib`, `Materializer`, `Execution` séparées |
|
||||||
|
|
||||||
|
## 6. Matrice d'ownership
|
||||||
|
|
||||||
|
| Concept | Owner | Décision `0.2.14` |
|
||||||
|
|------------------------------------|----------------------------------------------|--------------------------------------------|
|
||||||
|
| `Pubkey` | `ksp-core-lib` | réutilisé; jamais dupliqué |
|
||||||
|
| Program IDs fondamentaux KSP | `ksp-core-lib` | inchangés; non exhaustifs du monde externe |
|
||||||
|
| `ProgramAccountMeta` | `ksp-interface-lib` | réutilisé |
|
||||||
|
| `ProgramInstruction` | `ksp-interface-lib` | input du premier decoder |
|
||||||
|
| bornes wire instruction | `ksp-interface-lib` | inchangées |
|
||||||
|
| recognition instruction-local | `ksp-program-api` | introduite |
|
||||||
|
| capability decoder instruction | `ksp-program-api` | introduite |
|
||||||
|
| output décodé protocolaire concret | extension externe ou futur `ksp-program-lib` | associated type de l'implémentation |
|
||||||
|
| payload canonique DECODE/D3 | future frontière DECODE/Materializer/Store | reporté |
|
||||||
|
| decoder officiel | futur `ksp-program-lib` | hors scope |
|
||||||
|
| contexte transaction/CPI/logs | future couche CORE | reporté |
|
||||||
|
| registry runtime d'implémentations | future composition Program/DECODE | reporté |
|
||||||
|
| wire codec officiel | `ksp-interface-lib` | hors Program API |
|
||||||
|
| execution preparation | `ksp-program-api` futur | reporté de cette release |
|
||||||
|
| execution policy | futur `ksp-execution-policy-api` | hors scope |
|
||||||
|
| signature/simulation/send/confirm | futur `ksp-execution-lib` + Wallet/Transport | hors scope |
|
||||||
|
| persistence/materialization | futurs Store/Materializer | hors scope |
|
||||||
|
|
||||||
|
## 7. API candidate retenue
|
||||||
|
|
||||||
|
Le design cible à matérialiser dans les tranches suivantes est volontairement petit.
|
||||||
|
|
||||||
|
### 7.1 Façade héritée
|
||||||
|
|
||||||
|
`ksp-program-api` dépendra de Core et Interface et pourra réexporter explicitement depuis son crate root les types nécessaires à une implémentation externe sans module privé :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub use ksp_core_lib::{Error, ErrorCode, ErrorContext, Pubkey, Result};
|
||||||
|
pub use ksp_interface_lib::{ProgramAccountMeta, ProgramInstruction};
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces réexports ne changent pas l'ownership : Error/Pubkey restent Core-owned et les structures wire restent Interface-owned.
|
||||||
|
|
||||||
|
Les constantes d'admission Interface ne sont pas réexportées par défaut : l'input reçu a déjà franchi ces bornes et Program API ne les possède pas.
|
||||||
|
|
||||||
|
### 7.2 Recognition
|
||||||
|
|
||||||
|
Candidat retenu :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ProgramInstructionRecognition {
|
||||||
|
NoMatch,
|
||||||
|
ProgramMatch,
|
||||||
|
ExactMatch,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sémantique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
NoMatch l'implémentation ne revendique pas cette instruction
|
||||||
|
ProgramMatch le Program ID ou la famille est reconnue, mais l'entrée n'est pas prouvée exacte
|
||||||
|
ExactMatch l'implémentation affirme une reconnaissance instruction-locale exacte
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun score flottant, confidence, priority, surface code ou discriminator textuel n'entre dans la foundation.
|
||||||
|
|
||||||
|
Alternatives rejetées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bool trop pauvre pour distinguer Program-only et exact
|
||||||
|
No / Maybe / Yes sémantique moins explicite et héritée du generic protocol decoder
|
||||||
|
priority inutile sans registry conflict policy
|
||||||
|
entry code String crée un contrat textuel non consommé
|
||||||
|
proof enum dépend en partie du futur contexte CORE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Outcome
|
||||||
|
|
||||||
|
Candidat retenu :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ProgramInstructionDecodeOutcome<Decoded> {
|
||||||
|
Decoded(Decoded),
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`decode` retourne un `ksp_core_lib::Result`. Un échec de validation/décodage est donc `Err`, pas un doublon `Failed` dans l'outcome.
|
||||||
|
|
||||||
|
`Ignored` n'est pas retenu : pour une capability qui décode une instruction en une valeur typée, une entrée connue doit soit produire la valeur, soit être unsupported, soit échouer. Une politique de filtrage ou de matérialisation n'appartient pas à ce contrat.
|
||||||
|
|
||||||
|
Le `Debug` de cet outcome, s'il est exposé, devra être borné et ne pas rendre automatiquement le contenu `Decoded`.
|
||||||
|
|
||||||
|
### 7.4 Decoder instruction
|
||||||
|
|
||||||
|
Candidat retenu :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait ProgramInstructionDecoder: Send + Sync {
|
||||||
|
type Decoded;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[Pubkey];
|
||||||
|
|
||||||
|
fn recognize(
|
||||||
|
&self,
|
||||||
|
instruction: &ProgramInstruction,
|
||||||
|
) -> ProgramInstructionRecognition;
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
instruction: &ProgramInstruction,
|
||||||
|
) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Propriétés recherchées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
input entièrement KSP-owned et déjà borné
|
||||||
|
Program IDs typés
|
||||||
|
aucun serde/JSON/codec
|
||||||
|
aucun contexte transactionnel inventé
|
||||||
|
aucun I/O
|
||||||
|
aucun default method
|
||||||
|
Send + Sync sur l'implémentation
|
||||||
|
output concret possédé par la crate externe
|
||||||
|
```
|
||||||
|
|
||||||
|
Le `program_ids()` peut contenir un `Pubkey` absent du registry Core. Il sert uniquement à déclarer la portée Program de l'implémentation; il n'introduit aucun enum central.
|
||||||
|
|
||||||
|
### 7.5 Associated output et composition
|
||||||
|
|
||||||
|
L'associated type est retenu précisément parce que `0.2.14` ne possède pas encore le payload canonique D3.
|
||||||
|
|
||||||
|
Une extension peut définir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ExternalDecodedInstruction
|
||||||
|
```
|
||||||
|
|
||||||
|
sans forcer KSP à utiliser `Any`, JSON ou une enum centrale.
|
||||||
|
|
||||||
|
Conséquence assumée : le trait n'est **pas** une promesse de registry hétérogène `dyn ProgramInstructionDecoder` sans fixer `Decoded`. `0.2.14` ne crée donc aucun registry runtime et n'annonce aucun object-safe erased decoder.
|
||||||
|
|
||||||
|
Quand la frontière DECODE/D3 réelle existera, KSP pourra introduire un contrat séparé de composition/erasure ou un envelope canonique réellement justifié, sans transformer cette foundation typée en faux format persistant.
|
||||||
|
|
||||||
|
## 8. Décisions sur les questions ouvertes du prompt
|
||||||
|
|
||||||
|
### Capability initiale
|
||||||
|
|
||||||
|
Retenu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
instruction decoder uniquement
|
||||||
|
```
|
||||||
|
|
||||||
|
`ProgramAccountDecoder` est reporté : aucun input account Program-facing stable n'existe encore dans Interface/CORE.
|
||||||
|
|
||||||
|
### Input
|
||||||
|
|
||||||
|
Retenu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
&ProgramInstruction
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun signature, slot, CPI path, logs, return data, balance delta ou transaction error n'est ajouté.
|
||||||
|
|
||||||
|
### Identity / descriptors / coverage
|
||||||
|
|
||||||
|
Reportés. Le trait lui-même représente la capability; `program_ids()` suffit au besoin immédiat. Nom/version d'implémentation et coverage matrix ne sont pas consommés par la foundation.
|
||||||
|
|
||||||
|
### Payload ouvert
|
||||||
|
|
||||||
|
Retenu : output associé à l'implémentation. Aucun payload canonique commun n'est créé.
|
||||||
|
|
||||||
|
### Proof / confidence
|
||||||
|
|
||||||
|
Reportés. `ExactMatch` est une assertion instruction-locale du decoder, pas une preuve persistée. Logs, balance deltas et audit contextuel appartiennent à CORE/DECODE ultérieur.
|
||||||
|
|
||||||
|
### Diagnostics
|
||||||
|
|
||||||
|
Aucun diagnostic Program dédié. `Error/Result` Core suffit à la foundation. Les erreurs d'une extension doivent rester bornées et ne pas recopier le payload hostile.
|
||||||
|
|
||||||
|
### Registry
|
||||||
|
|
||||||
|
Reporté. `0.2.14` prouve l'extension par une crate externe, pas par un `Vec<Box<dyn ...>>` runtime.
|
||||||
|
|
||||||
|
### Object safety
|
||||||
|
|
||||||
|
Aucun gate object-safety n'est requis pour la surface retenue puisque le registry hétérogène est explicitement hors scope. Le trait reste `Send + Sync`, mais son associated output est intentionnellement typé.
|
||||||
|
|
||||||
|
### `ProgramExecutionPreparer`
|
||||||
|
|
||||||
|
Reporté au premier vertical slice qui possède un intent technique réel. L'ancien bot démontre qu'un preparer pur est utile, mais son ancien plan mélangeait encore policy, wallet/signers, blockhash, simulation et post-replay.
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
Aucun `ksp-logging-lib`, `tracing`, `constants.rs` ou `TRACING_TARGET` dans l'API déclarative.
|
||||||
|
|
||||||
|
### Sérialisation
|
||||||
|
|
||||||
|
Aucun `serde` ou `serde_json`.
|
||||||
|
|
||||||
|
## 9. Dependency graph cible
|
||||||
|
|
||||||
|
Le graphe normal final visé est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
│ └── solana-pubkey
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Dépendances de production interdites pour cette release :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-onchain-transport-lib
|
||||||
|
ksp-offchain-transport-lib
|
||||||
|
ksp-config-lib
|
||||||
|
ksp-wallet-lib
|
||||||
|
ksp-store-api
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-materializer-api
|
||||||
|
ksp-materializer-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
serde
|
||||||
|
serde_json
|
||||||
|
borsh
|
||||||
|
wincode
|
||||||
|
bincode
|
||||||
|
solana-instruction
|
||||||
|
reqwest
|
||||||
|
tokio
|
||||||
|
tonic
|
||||||
|
tauri
|
||||||
|
tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune nouvelle dépendance externe n'est nécessaire. L'audit externe ciblé de `pre.001` est donc `N/A` : le design ne dépend d'aucune nouvelle sémantique Solana, crate protocolaire ou registry dyn actuel.
|
||||||
|
|
||||||
|
## 10. Threat / API model
|
||||||
|
|
||||||
|
| Risque | Traitement de la foundation |
|
||||||
|
|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| payload hostile | input data déjà borné par Interface à `10_240` bytes; aucun Debug Program ne doit recopier ces bytes |
|
||||||
|
| account vector hostile | input déjà borné à `255` metas; ordre et doublons restent visibles au decoder |
|
||||||
|
| Program Pubkey inconnu | accepté; aucun lookup Core obligatoire |
|
||||||
|
| closed-world enum | interdit; aucun `ProgramKind` central |
|
||||||
|
| implémentation externe incorrecte | trait in-process non sandboxé; KSP ne prétend pas contenir un code tiers arbitraire |
|
||||||
|
| panic externe | aucun default method Program; un panic d'une implémentation tierce reste un défaut de cette implémentation |
|
||||||
|
| false `ExactMatch` | assertion du decoder; pas de proof contextuel inventé; futures compositions peuvent auditer les conflits |
|
||||||
|
| strings non bornées | aucun nouveau String dans l'API candidate |
|
||||||
|
| Debug leak | Recognition sans payload; outcome ne doit pas rendre automatiquement `Decoded`; input Interface a déjà un Debug borné |
|
||||||
|
| status incohérent | `Failed` supprimé au profit de `Err`; outcome minimal réduit les combinaisons invalides |
|
||||||
|
| priority ambiguity | aucun priority sans registry; les conflits sont reportés au contrat de composition futur |
|
||||||
|
| registry conflicts | registry absent de la release |
|
||||||
|
| object-safety impossible | aucune promesse de dyn hétérogène; associated output assumé |
|
||||||
|
| accidental serialization | aucune dépendance/derive serde |
|
||||||
|
| network/policy creep | aucune dépendance runtime et aucun client/wallet/context réseau dans les signatures |
|
||||||
|
|
||||||
|
## 11. Stratégie de tests
|
||||||
|
|
||||||
|
### Unit tests
|
||||||
|
|
||||||
|
Prévoir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Recognition variants distincts
|
||||||
|
outcome Decoded / Unsupported
|
||||||
|
Debug outcome borné si implémenté
|
||||||
|
unknown Program Pubkey
|
||||||
|
empty/max-boundary ProgramInstruction déjà garanti par Interface et réutilisé sans recopie
|
||||||
|
```
|
||||||
|
|
||||||
|
### Public API
|
||||||
|
|
||||||
|
Le canari `tests/public_api.rs` devra utiliser uniquement les exports crate-root de `ksp-program-api`.
|
||||||
|
|
||||||
|
### External implementation
|
||||||
|
|
||||||
|
Un test d'intégration doit construire une crate consommatrice séparée qui :
|
||||||
|
|
||||||
|
```text
|
||||||
|
dépend uniquement de ksp-program-api pour le contrat Program
|
||||||
|
implémente ProgramInstructionDecoder
|
||||||
|
définit son propre type ExternalDecodedInstruction
|
||||||
|
utilise un Pubkey opaque absent du registry Core
|
||||||
|
reconnaît et décode une instruction
|
||||||
|
n'accède à aucun module privé
|
||||||
|
ne dépend pas de ksp-program-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
La technique exacte de crate fixture temporaire sera choisie lors de la tranche d'implémentation, sans ajouter de dépendance runtime.
|
||||||
|
|
||||||
|
### Dependency firewall
|
||||||
|
|
||||||
|
Canaris manifest/source pour interdire les dépendances listées en section 9.
|
||||||
|
|
||||||
|
### Release completeness
|
||||||
|
|
||||||
|
Verrouiller la surface décidée, l'absence de registry/preparer/payload canonique et l'absence de `pub mod`.
|
||||||
|
|
||||||
|
### Aucun smoke réseau
|
||||||
|
|
||||||
|
Aucun HTTP/WS/gRPC/live smoke n'est pertinent pour une API in-memory déclarative.
|
||||||
|
|
||||||
|
## 12. Sizing
|
||||||
|
|
||||||
|
Le scope initial du prompt contenait decoder, descriptors, registry, payload ouvert et possiblement execution preparation. L'audit montre que stabiliser ces surfaces ensemble obligerait à anticiper D3 et Execution.
|
||||||
|
|
||||||
|
Le gate réduit donc la release à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crate + facade + firewall
|
||||||
|
recognition + outcome minimal
|
||||||
|
decoder instruction avec associated output
|
||||||
|
external implementation canary
|
||||||
|
hardening/completeness
|
||||||
|
gate technique
|
||||||
|
documentation
|
||||||
|
publication
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce périmètre reste compatible avec une release clôturable dans une session et avec des tranches intermédiaires bornées.
|
||||||
|
|
||||||
|
## 13. Prévision souple recalibrée
|
||||||
|
|
||||||
|
### `pre.001` — Audit KSP + kbot3 + API model + sizing
|
||||||
|
|
||||||
|
**Statut : réalisé ; gate opérateur intégralement PASS.**
|
||||||
|
|
||||||
|
Baseline, règles/architecture, héritage, ownership, API candidate, dependency graph, threat model, tests et scope réduit. Aucun code Program.
|
||||||
|
|
||||||
|
### `pre.002` — Scaffold `ksp-program-api` + façade + firewall
|
||||||
|
|
||||||
|
**Statut : réalisé ; gate opérateur intégralement PASS.**
|
||||||
|
|
||||||
|
La crate est membre du workspace avec exactement `ksp-core-lib` et `ksp-interface-lib` comme dépendances normales. Le crate-root réexporte `Error`, `ErrorCode`, `ErrorContext`, `Result`, `Pubkey`, `ProgramAccountMeta` et `ProgramInstruction`. README/USAGE initiaux et canaris `public_api` / `dependency_boundary` sont présents.
|
||||||
|
|
||||||
|
Aucun trait decoder, recognition, outcome, registry, codec, runtime logging ou execution preparer n'est avancé.
|
||||||
|
|
||||||
|
### `pre.003` — Recognition + outcome minimal
|
||||||
|
|
||||||
|
**Statut : réalisé ; gate opérateur intégralement PASS.**
|
||||||
|
|
||||||
|
`ProgramInstructionRecognition` et `ProgramInstructionDecodeOutcome<Decoded>` sont ajoutés dans un module privé puis réexportés depuis le crate-root. Les deux enums sont `#[non_exhaustive]`. Le `Debug` de l'outcome est manuel, ne requiert pas `Decoded: Debug` et n'affiche jamais la valeur décodée. Aucun descriptor, registry ou trait decoder n'est avancé.
|
||||||
|
|
||||||
|
### `pre.004` — `ProgramInstructionDecoder` + external implementation
|
||||||
|
|
||||||
|
**Statut : réalisé ; gate opérateur intégralement PASS.**
|
||||||
|
|
||||||
|
Le trait `ProgramInstructionDecoder: Send + Sync` expose l'associated type `Decoded`, `program_ids`, `recognize` et `decode`. Un test d'intégration downstream-style l'implémente avec un type décodé tiers et un `Pubkey` explicitement absent du registry Core. L'implémentation ne requiert ni `ksp-program-lib`, ni enum centrale, ni `Any`, JSON ou codec.
|
||||||
|
|
||||||
|
### `pre.005` — Adversarial/API hardening + completeness
|
||||||
|
|
||||||
|
**Statut : réalisé ; `pre.005-fix.001` validé, gate opérateur intégralement PASS.**
|
||||||
|
|
||||||
|
Deux canaris de fermeture sont ajoutés : `release_completeness.rs` verrouille l'inventaire exact des exports/modules et l'absence de surface closed-world/runtime ; `security_hardening.rs` couvre input Interface maximal, erreur sûre sur payload hostile et associated output sans bound implicite. Aucun contrat fonctionnel n'est ajouté.
|
||||||
|
|
||||||
|
### `pre.006` — Gate technique final
|
||||||
|
|
||||||
|
**Statut : matérialisé ; gate opérateur à confirmer.**
|
||||||
|
|
||||||
|
Aucun développement fonctionnel. Audits Rust/Markdown, check, Clippy, `cargo test -p ksp-program-api`, ownership Logging ciblé, workspace complet et graphes Cargo. Aucun README/USAGE final ni préparation de publication n’est mélangé à cette tranche.
|
||||||
|
|
||||||
|
### `pre.007` — Réconciliation documentaire finale
|
||||||
|
|
||||||
|
README/USAGE, plan, validation, index et références durables réellement concernées. Aucun `CHANGELOG.md`, `ROADMAP.md` ou prompt suivant.
|
||||||
|
|
||||||
|
### `pre.008` — Préparation de publication minimale
|
||||||
|
|
||||||
|
Uniquement `Cargo.toml`, `CHANGELOG.md`, `ROADMAP.md`, prompt `0.3.1` et delta `pre.008`.
|
||||||
|
|
||||||
|
### `rel.001` — Publication stable
|
||||||
|
|
||||||
|
Mécanique de publication uniquement.
|
||||||
|
|
||||||
|
La numérotation reste souple : une anomalie peut insérer une tranche dédiée, mais les couloirs `gate technique -> réconciliation documentaire -> publication minimale` restent séparés.
|
||||||
|
|
||||||
|
## 13.1 État préparé après `pre.002`
|
||||||
|
|
||||||
|
Le scaffold strict attendu pour ouvrir `pre.003` est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api membre workspace
|
||||||
|
normal dependencies ksp-core-lib + ksp-interface-lib uniquement
|
||||||
|
crate-root facade Core/Interface réexportés explicitement
|
||||||
|
public modules aucun
|
||||||
|
ProgramInstructionRecognition absent par contrat pre.002
|
||||||
|
ProgramInstructionDecodeOutcome absent par contrat pre.002
|
||||||
|
ProgramInstructionDecoder absent par contrat pre.002
|
||||||
|
ProgramExecutionPreparer absent
|
||||||
|
serde / JSON / codecs absents
|
||||||
|
ksp-logging-lib / tracing absents
|
||||||
|
README / USAGE initiaux
|
||||||
|
public API canary présent
|
||||||
|
dependency firewall canary présent
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.003` reste limité à `ProgramInstructionRecognition` et `ProgramInstructionDecodeOutcome<Decoded>` avec leur sémantique et leur Debug sûr. Le trait decoder reste réservé à `pre.004`.
|
||||||
|
|
||||||
|
## 13.2 État préparé après `pre.003`
|
||||||
|
|
||||||
|
Le gate opérateur `pre.002` fourni le 28 août 2026 est intégralement vert : audits Rust/Markdown, check, Clippy, tests de `ksp-program-api`, workspace complet et graphes Cargo ont été exécutés. Le graphe normal ciblé reste exactement `Core + Interface`.
|
||||||
|
|
||||||
|
La tranche `pre.003` matérialise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionRecognition NoMatch / ProgramMatch / ExactMatch
|
||||||
|
ProgramInstructionDecodeOutcome<T> Decoded(T) / Unsupported
|
||||||
|
non_exhaustive oui sur les deux enums
|
||||||
|
Debug recognition payload-free par construction
|
||||||
|
Debug outcome opaque, sans bound T: Debug
|
||||||
|
Failed / Ignored absents
|
||||||
|
priority / confidence / proof absents de la surface
|
||||||
|
ProgramInstructionDecoder absent par contrat pre.003
|
||||||
|
registry / canonical payload / preparer absents
|
||||||
|
normal dependencies inchangées : Core + Interface
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tests unitaires vérifient notamment qu'un type décodé externe dépourvu de `Debug` peut être contenu et formaté via l'outcome sans exposer sa valeur. Le gate opérateur `pre.003` fourni le 28 août 2026 est intégralement vert et autorise `pre.004`.
|
||||||
|
|
||||||
|
## 13.3 État préparé après `pre.004`
|
||||||
|
|
||||||
|
La tranche matérialise exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionDecoder public depuis le crate-root
|
||||||
|
supertraits Send + Sync
|
||||||
|
associated output type Decoded possédé par l'implémentation
|
||||||
|
program_ids &[Pubkey] opaque/open-world
|
||||||
|
recognize &ProgramInstruction -> Recognition
|
||||||
|
decode &ProgramInstruction -> Result<Outcome<Self::Decoded>>
|
||||||
|
default methods aucun
|
||||||
|
external implementation canary présent comme crate d'intégration séparée
|
||||||
|
external decoded type défini hors code de production KSP
|
||||||
|
external Program Pubkey explicitement absent du registry Core
|
||||||
|
central Program enum / Any / JSON absents
|
||||||
|
registry dyn / descriptor / D3 payload absents
|
||||||
|
ProgramExecutionPreparer absent
|
||||||
|
normal dependencies inchangées : Core + Interface
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari externe utilise uniquement la façade `ksp_program_api::*` pour l'implémentation du trait ; l'accès direct à `ksp_core_lib::find_program_pubkey` est limité à l'assertion de test prouvant que le Program choisi n'est pas enregistré. Aucune API de registry n'est réexportée par Program API.
|
||||||
|
|
||||||
|
`pre.005` reste une tranche de hardening/completeness : elle ne doit pas élargir le contrat fonctionnel.
|
||||||
|
|
||||||
|
## 13.4 État préparé après `pre.005`
|
||||||
|
|
||||||
|
Le gate opérateur `pre.004` fourni le 28 août 2026 est intégralement vert : audits Rust/Markdown, check, Clippy, tests ciblés, workspace complet, canari externe et graphes Cargo passent.
|
||||||
|
|
||||||
|
La tranche `pre.005` ajoute uniquement des preuves de fermeture :
|
||||||
|
|
||||||
|
```text
|
||||||
|
exact crate-root exports 10 exports explicitement verrouillés
|
||||||
|
production modules lib + decode vocabulary + decoder trait uniquement
|
||||||
|
public enums Recognition + DecodeOutcome uniquement
|
||||||
|
public traits ProgramInstructionDecoder uniquement
|
||||||
|
closed-world Program enum absent
|
||||||
|
registry / descriptors / preparer absents
|
||||||
|
serde / JSON / Any / codecs absents
|
||||||
|
logging / runtime / IO absents
|
||||||
|
max Interface instruction consommable par référence
|
||||||
|
malformed hostile payload Err Core sûr sans copie automatique du payload
|
||||||
|
associated Decoded bounds aucun bound implicite ajouté
|
||||||
|
dyn heterogeneous registry aucune promesse
|
||||||
|
normal dependencies inchangées : Core + Interface
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.006` reste un gate technique final sans développement fonctionnel.
|
||||||
|
|
||||||
|
## 13.5 Gate opérateur `pre.005-fix.001` et ouverture de `pre.006`
|
||||||
|
|
||||||
|
Le gate opérateur du 28 août 2026 ferme le correctif de `pre.005` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 173 tables / 123 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2 tests
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté, inventaire workspace observé
|
||||||
|
```
|
||||||
|
|
||||||
|
Le faux positif du canari logging est donc fermé sans changement de production ni de dépendances. Le scope fonctionnel `0.2.14` est figé avant `pre.006`.
|
||||||
|
|
||||||
|
La tranche `pre.006` ne matérialise aucun nouveau code ou test : elle synchronise seulement la version workspace, le plan, la validation et son delta afin de rejouer le gate technique final sur la surface candidate déjà durcie.
|
||||||
|
|
||||||
|
## 13.6 Gate technique final `pre.006` et réconciliation `pre.007`
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.006`, fourni le 28 août 2026, ferme intégralement la lane technique sans modification de production ni de tests :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audits Rust / export completeness / workspace PASS
|
||||||
|
audit Markdown PASS — 174 tables / 124 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2/2
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
La surface technique candidate est donc figée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crate-root exports 10 exacts
|
||||||
|
production modules 3 exacts
|
||||||
|
public enums Recognition + DecodeOutcome uniquement
|
||||||
|
public trait ProgramInstructionDecoder uniquement
|
||||||
|
normal dependencies Core + Interface uniquement
|
||||||
|
Program Pubkey hors registry accepté par canari externe
|
||||||
|
max Interface instruction admise à la frontière decoder par référence
|
||||||
|
registry / descriptors / preparer absents
|
||||||
|
serde / JSON / Any / codecs absents
|
||||||
|
logging / runtime / IO absents
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.007` ne rouvre aucun fichier Rust, test, manifest de crate, dépendance ou comportement. La version workspace avance mécaniquement à `0.2.14-pre.7` et les références durables suivantes sont réconciliées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
docs/000-README.md
|
||||||
|
docs/plans/000-README.md
|
||||||
|
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/000-README.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
La documentation finale fixe l'ownership, l'inventaire crate-root, les semantics Recognition/Outcome, le trait externe, les preuves open-world/adversariales et le dependency firewall. Les index passent au statut candidat réconcilié sans annoncer prématurément la release stable.
|
||||||
|
|
||||||
|
Cette tranche ne touche explicitement pas `CHANGELOG.md`, `ROADMAP.md`, le prompt suivant, l'architecture ni les surfaces techniques. Ces responsabilités appartiennent à `pre.008`, sauf découverte d'un défaut documentaire réel imposant une nouvelle tranche de réconciliation.
|
||||||
|
|
||||||
|
## 14. Hors périmètre confirmé
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
decoder officiel Solana/SPL
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
registry runtime
|
||||||
|
priority/conflict policy
|
||||||
|
descriptor identity/version/coverage
|
||||||
|
payload canonique D3
|
||||||
|
serde/JSON/Any
|
||||||
|
proof/confidence contextuels
|
||||||
|
CORE replay input
|
||||||
|
Materializer / Store
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
ExecutionPolicy / Execution
|
||||||
|
Wallet / Transport / Config / Tauri
|
||||||
|
IDL runtime / Anchor generic decoder
|
||||||
|
```
|
||||||
|
|
||||||
|
## 15. Critères de clôture
|
||||||
|
|
||||||
|
La candidate satisfait les critères techniques et documentaires suivants avant préparation de publication :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api existe
|
||||||
|
le trait instruction-only est public et documenté
|
||||||
|
une extension externe l'implémente avec un Pubkey non enregistré
|
||||||
|
aucun enum central Program n'est requis
|
||||||
|
aucun payload canonique prématuré n'est figé
|
||||||
|
aucun registry/preparer n'est anticipé
|
||||||
|
Core/Interface sont les seules dépendances KSP normales
|
||||||
|
aucun runtime/codec/logging/serde n'est tiré
|
||||||
|
public API / external implementation / dependency firewall / completeness passent
|
||||||
|
cargo test -p ksp-program-api passe
|
||||||
|
cargo test --workspace passe
|
||||||
|
graphes Cargo inspectés
|
||||||
|
documentation finale réconciliée
|
||||||
|
```
|
||||||
|
|
||||||
|
## 16. Suite
|
||||||
|
|
||||||
|
Après `0.2.14`, la séquence active reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1 ksp-store-api + ksp-store-lib, RAW only
|
||||||
|
0.3.2 ksp-interface-lib, wires génériques acquisition/CORE
|
||||||
|
0.3.3 ksp-job-api + backfill
|
||||||
|
0.3.4 application backfill/RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
Le payload DECODE, les materializers et la préparation d'exécution ne sont pas déplacés dans `0.3.1`; ils attendent les vertical slices qui démontreront leurs contrats réels.
|
||||||
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: 29 -->
|
<!-- version: 32 -->
|
||||||
|
|
||||||
# Validations KSP
|
# Validations KSP
|
||||||
|
|
||||||
@@ -24,4 +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 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+`.
|
||||||
|
|||||||
346
docs/validation/017-V0_2_14_PROGRAM_API.md
Normal file
346
docs/validation/017-V0_2_14_PROGRAM_API.md
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
<!-- file: docs/validation/017-V0_2_14_PROGRAM_API.md -->
|
||||||
|
<!-- version: 7 -->
|
||||||
|
|
||||||
|
# Validation `0.2.14` — Program API foundation
|
||||||
|
|
||||||
|
## 1. Objet
|
||||||
|
|
||||||
|
Cette matrice, ouverte par `0.2.14-pre.001`, est réconciliée par `pre.007` après fermeture intégralement verte du gate technique `pre.006`. Elle conserve l'historique des preuves par tranche et la surface candidate finale.
|
||||||
|
|
||||||
|
Le scope final reste une foundation instruction-only avec output associé à l'implémentation. Registry runtime, payload canonique D3 et `ProgramExecutionPreparer` sont explicitement reportés.
|
||||||
|
|
||||||
|
## 2. Gate `pre.001`
|
||||||
|
|
||||||
|
| Critère | Statut | Preuve |
|
||||||
|
|----------------------------|---------|--------------------------------------------------------------------------------|
|
||||||
|
| base stable `0.2.13` | PASS | Cargo `0.2.13`, `rel.001` et prompt 019 présents |
|
||||||
|
| metadata Git/tag | N/A | archive opérateur sans metadata Git exploitable |
|
||||||
|
| `ksp-interface-lib` stable | PASS | surface et canaris `0.2.13` relus |
|
||||||
|
| `ksp-program-api` absent | PASS | aucun répertoire de crate sur la base |
|
||||||
|
| `ksp-program-lib` absent | PASS | aucun répertoire de crate sur la base |
|
||||||
|
| archive kbot3 disponible | PASS | archive historique réellement extraite et auditée |
|
||||||
|
| règles Program/API relues | PASS | règles KSP/Dependencies/Workflow prescrites relues |
|
||||||
|
| architecture durable relue | PASS | `002` à `010`, avec `006-WIRE_AND_PROGRAM.md` central |
|
||||||
|
| audit Core + Interface | PASS | input `ProgramInstruction` jugé suffisant pour instruction-only |
|
||||||
|
| matrice héritage kbot3 | PASS | `REPRENDRE / REDESSINER / REPORTER / REJETER` dans le plan 021 |
|
||||||
|
| ownership matrix | PASS | Core/Interface/Program/CORE/Execution séparés dans le plan 021 |
|
||||||
|
| API candidate | PASS | recognition + generic outcome + associated-output decoder |
|
||||||
|
| payload canonique | REPORTÉ | aucune représentation D3 inventée en `0.2.14` |
|
||||||
|
| registry runtime | REPORTÉ | aucune composition dyn hétérogène dans la release |
|
||||||
|
| execution preparer | REPORTÉ | attend un intent technique réel de vertical slice |
|
||||||
|
| dependency graph | PASS | cible Core + Interface uniquement |
|
||||||
|
| threat/API model | PASS | payload, Debug, open-world, externe hostile, exactness et scope creep couverts |
|
||||||
|
| stratégie de tests | PASS | unit/public/external/firewall/completeness, aucun live smoke |
|
||||||
|
| sizing | PASS | release réduite à decoder instruction-only et couloirs de fermeture séparés |
|
||||||
|
|
||||||
|
## 3. Décisions API à prouver par le code
|
||||||
|
|
||||||
|
| Contrat | Décision `pre.001` | Gate futur |
|
||||||
|
|--------------------------------------------|---------------------------------------------------------|--------------------------------|
|
||||||
|
| `ProgramInstructionRecognition` | `NoMatch / ProgramMatch / ExactMatch`, non exhaustif | PASS `pre.003` |
|
||||||
|
| `ProgramInstructionDecodeOutcome<Decoded>` | `Decoded(Decoded) / Unsupported`, non exhaustif | PASS `pre.003` |
|
||||||
|
| decoder failure | `ksp_core_lib::Result`, aucun statut `Failed` parallèle | `pre.003` / `pre.004` |
|
||||||
|
| `ProgramInstructionDecoder` | `Send + Sync`, associated `Decoded` | PASS `pre.004` |
|
||||||
|
| input | `&ProgramInstruction` | PASS `pre.004` |
|
||||||
|
| Program IDs déclarés | `&[Pubkey]`, opaque et open-world | PASS `pre.004` |
|
||||||
|
| output | type concret de l'implémentation | PASS `pre.004` |
|
||||||
|
| registry dyn | absent | completeness `pre.005` |
|
||||||
|
| identity/version/coverage | absents | completeness `pre.005` |
|
||||||
|
| Program Account/Event/ReturnData decoder | absents | completeness `pre.005` |
|
||||||
|
| ProgramExecutionPreparer | absent | completeness `pre.005` |
|
||||||
|
| serde/JSON/codec | absents | firewall `pre.002` / `pre.005` |
|
||||||
|
| runtime/logging | absents | firewall `pre.002` / `pre.005` |
|
||||||
|
|
||||||
|
## 4. External implementation canary
|
||||||
|
|
||||||
|
La preuve matérialisée en `pre.004` utilise une crate d'intégration consommatrice séparée et vérifie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
implementation de ProgramInstructionDecoder
|
||||||
|
associated output défini hors KSP
|
||||||
|
Program Pubkey absent du registry Core
|
||||||
|
construction de ProgramInstruction via la facade publique retenue
|
||||||
|
recognition ProgramMatch ou ExactMatch déterministe
|
||||||
|
decode vers Decoded(external value)
|
||||||
|
aucun ksp-program-lib
|
||||||
|
aucun module privé
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce canari remplace toute affirmation documentaire non exécutable d'extensibilité. Son gate opérateur `pre.004` est confirmé intégralement vert.
|
||||||
|
|
||||||
|
## 5. Dependency firewall cible
|
||||||
|
|
||||||
|
Graphe normal attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
│ └── solana-pubkey
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Interdits :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Transport
|
||||||
|
Store
|
||||||
|
Wallet
|
||||||
|
Materializer
|
||||||
|
Config
|
||||||
|
Tauri
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
serde / serde_json
|
||||||
|
borsh / wincode / bincode
|
||||||
|
solana-instruction
|
||||||
|
reqwest / tokio / tonic
|
||||||
|
tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Threat/API gates futurs
|
||||||
|
|
||||||
|
| Gate | Attendu | Statut initial |
|
||||||
|
|---------------------------|--------------------------------------------------------------------------|----------------|
|
||||||
|
| unknown Program Pubkey | utilisable sans registry Core | PASS `pre.004` |
|
||||||
|
| max Interface input | decoder consomme l'input déjà borné sans nouvelle allocation obligatoire | PASS `pre.005` |
|
||||||
|
| malformed program payload | `Err` ou `Unsupported`; aucun echo n'est ajouté par le contrat | PASS `pre.005` |
|
||||||
|
| Debug recognition | aucun payload | PASS |
|
||||||
|
| Debug outcome | contenu `Decoded` non rendu automatiquement | PASS |
|
||||||
|
| default methods | aucun default method susceptible de masquer panic/policy | PASS `pre.004` |
|
||||||
|
| closed-world enum | aucun inventaire central de Program kinds | PASS `pre.004` |
|
||||||
|
| serde accidental | aucune dependency/derive | PASS `pre.004` |
|
||||||
|
| runtime creep | aucun réseau, wallet, store, logging ou UI | PASS `pre.004` |
|
||||||
|
| external output | type tiers accepté sans `Any`/JSON central | PASS `pre.004` |
|
||||||
|
| dyn claim | aucune assertion d'object-safety hétérogène dans cette release | PASS `pre.004` |
|
||||||
|
|
||||||
|
## 7. Gates de fermeture
|
||||||
|
|
||||||
|
### Gate technique final
|
||||||
|
|
||||||
|
Le gate technique dédié `pre.006` est fermé intégralement vert. Les commandes de référence étaient :
|
||||||
|
|
||||||
|
```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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test -p ksp-logging-lib --test ownership
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
### Réconciliation documentaire
|
||||||
|
|
||||||
|
`pre.007` ferme :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api README/USAGE
|
||||||
|
plan 021
|
||||||
|
validation 017
|
||||||
|
indexes/références durables concernées
|
||||||
|
```
|
||||||
|
|
||||||
|
Sans `CHANGELOG.md`, `ROADMAP.md` ni prompt suivant.
|
||||||
|
|
||||||
|
### Préparation de publication
|
||||||
|
|
||||||
|
Doit rester limitée à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompt de démarrage 0.3.1
|
||||||
|
delta de la prerelease
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Gate opérateur `pre.002`
|
||||||
|
|
||||||
|
Le gate fourni le 28 août 2026 est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 170 tables / 119 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 4 tests d'intégration
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté, inventaire workspace observé
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce gate autorise l'ouverture de `pre.003`.
|
||||||
|
|
||||||
|
## 9. État préparé `pre.003`
|
||||||
|
|
||||||
|
| Critère | Statut | Preuve |
|
||||||
|
|--------------------------------------|--------|----------------------------------------------------------------------|
|
||||||
|
| `ProgramInstructionRecognition` | PASS | enum non exhaustif `NoMatch / ProgramMatch / ExactMatch` |
|
||||||
|
| `ProgramInstructionDecodeOutcome<T>` | PASS | enum non exhaustif `Decoded(T) / Unsupported` |
|
||||||
|
| absence de `Failed` / `Ignored` | PASS | échec réservé au futur `Result`; aucun statut parallèle |
|
||||||
|
| Debug recognition | PASS | enum sans payload; Debug dérivé |
|
||||||
|
| Debug outcome | PASS | implémentation manuelle sans `T: Debug`, valeur `Decoded` non rendue |
|
||||||
|
| dépendances normales | PASS | manifest inchangé : `ksp-core-lib` + `ksp-interface-lib` |
|
||||||
|
| module public | PASS | aucun `pub mod`; exports crate-root uniquement |
|
||||||
|
| `ProgramInstructionDecoder` | ABSENT | réservé à `pre.004` |
|
||||||
|
| registry / descriptor / payload D3 | ABSENT | hors scope maintenu |
|
||||||
|
| serde / codec / logging / runtime | ABSENT | dependency firewall maintenu |
|
||||||
|
|
||||||
|
Le gate opérateur `pre.003` est confirmé intégralement vert : audits Rust/Markdown, check, Clippy, tests ciblés, workspace complet et graphes Cargo passent.
|
||||||
|
|
||||||
|
## 10. Gate opérateur `pre.003`
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 171 tables / 120 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 8 tests Rust
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté, inventaire workspace observé
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce gate autorise l'ouverture de `pre.004`.
|
||||||
|
|
||||||
|
## 11. État préparé `pre.004`
|
||||||
|
|
||||||
|
| Critère | Statut | Preuve |
|
||||||
|
|------------------------------------|--------|----------------------------------------------------------------------|
|
||||||
|
| `ProgramInstructionDecoder` | PASS | trait public `Send + Sync` |
|
||||||
|
| associated `Decoded` | PASS | type sans bound imposé, possédé par l'implémentation |
|
||||||
|
| `program_ids` | PASS | `&[Pubkey]`, aucune validation registry dans l'API |
|
||||||
|
| `recognize` | PASS | input `&ProgramInstruction`, outcome `ProgramInstructionRecognition` |
|
||||||
|
| `decode` | PASS | `Result<ProgramInstructionDecodeOutcome<Self::Decoded>>` |
|
||||||
|
| default methods | ABSENT | toutes les méthodes sont obligatoires |
|
||||||
|
| external implementation canary | PASS | test d'intégration downstream-style séparé |
|
||||||
|
| Program Pubkey non enregistré | PASS | assertion `ksp_core_lib::find_program_pubkey(...) == None` |
|
||||||
|
| output externe concret | PASS | `ExternalDecodedInstruction` défini dans le consumer canary |
|
||||||
|
| `ksp-program-lib` | ABSENT | aucune dépendance ni implémentation officielle |
|
||||||
|
| registry / descriptor / payload D3 | ABSENT | hors scope maintenu |
|
||||||
|
| serde / codec / logging / runtime | ABSENT | dependency firewall inchangé |
|
||||||
|
|
||||||
|
Le canari d'implémentation consomme la façade `ksp_program_api` pour le trait, les types et les outcomes. Le registry Core n'est utilisé que par l'assertion de test négative et n'est pas réexporté par Program API. Le gate opérateur de `pre.004` est confirmé intégralement vert et autorise `pre.005`.
|
||||||
|
|
||||||
|
## 12. Gate opérateur `pre.004`
|
||||||
|
|
||||||
|
Le gate fourni le 28 août 2026 est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 172 tables / 121 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 11 tests Rust
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté, inventaire workspace observé
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce gate autorise l'ouverture de `pre.005`.
|
||||||
|
|
||||||
|
## 13. État préparé `pre.005`
|
||||||
|
|
||||||
|
| Critère | Statut | Preuve |
|
||||||
|
|--------------------------------------|--------|----------------------------------------------------------------------|
|
||||||
|
| exact crate-root export inventory | PASS | test `release_completeness` sur les 10 réexports |
|
||||||
|
| exact production module inventory | PASS | `lib.rs`, decode vocabulary et decoder trait uniquement |
|
||||||
|
| public enum inventory | PASS | Recognition + DecodeOutcome uniquement |
|
||||||
|
| closed-world Program enum | ABSENT | aucun `ProgramKind`/inventaire public central |
|
||||||
|
| registry / descriptors | ABSENT | aucune collection/runtime selection/identity-version-coverage |
|
||||||
|
| `ProgramExecutionPreparer` | ABSENT | scope négatif maintenu |
|
||||||
|
| serde / JSON / Any / codecs | ABSENT | manifest + source canaries |
|
||||||
|
| logging / runtime / IO | ABSENT | aucune dépendance ou primitive runtime/FS/env/network |
|
||||||
|
| max Interface input | PASS | 255 accounts + 10 240 bytes traversent le trait par référence |
|
||||||
|
| malformed hostile payload | PASS | `Result::Err` Core reste sûr sans echo automatique du payload |
|
||||||
|
| Debug outcome | PASS | valeur décodée jamais formatée |
|
||||||
|
| associated `Decoded` implicit bounds | ABSENT | canari avec output `Rc<Cell<_>>`, donc non-`Send`/non-`Sync` accepté |
|
||||||
|
| dyn heterogeneous claim | ABSENT | aucun `dyn ProgramInstructionDecoder`/registry |
|
||||||
|
| dépendances normales | PASS | `ksp-core-lib` + `ksp-interface-lib` uniquement |
|
||||||
|
|
||||||
|
La sécurité des messages/contextes produits volontairement par une implémentation tierce reste sa responsabilité ; `ksp-program-api` garantit seulement qu'il n'ajoute aucun canal parallèle ni copie automatique du payload hostile.
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.005` a révélé uniquement le faux positif cross-crate logging corrigé par `pre.005-fix.001`. Le gate du fix est désormais intégralement vert et autorise le gate technique final `pre.006`.
|
||||||
|
|
||||||
|
## 14. Gate opérateur `pre.005-fix.001`
|
||||||
|
|
||||||
|
Le gate fourni le 28 août 2026 est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 173 tables / 123 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2/2
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
Le faux positif `tracing` du canari `release_completeness.rs` est fermé sans ajout de dépendance, sans modification de production et sans affaiblissement du scanner logging workspace.
|
||||||
|
|
||||||
|
## 15. Gate technique final préparé `pre.006`
|
||||||
|
|
||||||
|
`pre.006` ne modifie aucun fichier de production ni de test. Le gate final doit confirmer la candidate suivante sans élargissement :
|
||||||
|
|
||||||
|
| Critère | Attendu `pre.006` |
|
||||||
|
|--------------------------------------|-------------------------------------------|
|
||||||
|
| API Program | exactement la surface fermée en `pre.005` |
|
||||||
|
| tests `ksp-program-api` | 18 tests Rust PASS |
|
||||||
|
| ownership Logging | 2 tests PASS |
|
||||||
|
| workspace | PASS |
|
||||||
|
| graphe normal `ksp-program-api` | Core + Interface uniquement |
|
||||||
|
| registry / preparer / payload D3 | ABSENTS |
|
||||||
|
| serde / codec / logging runtime / IO | ABSENTS |
|
||||||
|
| README / USAGE | non réconciliés ici |
|
||||||
|
| CHANGELOG / ROADMAP / prompt suivant | non modifiés |
|
||||||
|
|
||||||
|
Aucun smoke réseau/live n'est requis : `ksp-program-api` reste une API déclarative instruction-only sans I/O.
|
||||||
|
|
||||||
|
## 16. Gate technique final `pre.006`
|
||||||
|
|
||||||
|
Le gate opérateur fourni le 28 août 2026 confirme la candidate technique complète :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audits Rust / export completeness / workspace PASS
|
||||||
|
audit Markdown PASS — 174 tables / 124 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 18 tests Rust
|
||||||
|
cargo test -p ksp-logging-lib --test ownership PASS — 2/2
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
Les duplicates observés sont ceux du workspace global et ne proviennent d'aucune dépendance ajoutée par Program API. Aucun smoke live n'est applicable à cette foundation déclarative sans I/O.
|
||||||
|
|
||||||
|
La validation technique de la release candidate est fermée.
|
||||||
|
|
||||||
|
## 17. Réconciliation documentaire `pre.007`
|
||||||
|
|
||||||
|
La documentation finale candidate est alignée sur les preuves techniques :
|
||||||
|
|
||||||
|
| Référence durable | Statut `pre.007` | Contrat réconcilié |
|
||||||
|
|-------------------------------------------------|------------------|---------------------------------------------------------------------------|
|
||||||
|
| `crates/ksp-program-api/README.md` | PASS structurel | ownership, surface exacte, semantics, hardening, firewall et frontières |
|
||||||
|
| `crates/ksp-program-api/USAGE.md` | PASS structurel | usage crate-root, implémentation externe, sélection, outcome et Debug sûr |
|
||||||
|
| `docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md` | PASS structurel | contenu réel `0.2.14` + reports runtime/D3/preparer |
|
||||||
|
| `docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md` | PASS structurel | gate `pre.006` fermé + lane documentaire finale |
|
||||||
|
| `docs/validation/017-V0_2_14_PROGRAM_API.md` | PASS structurel | statuts finaux et preuves opérateur |
|
||||||
|
| index `docs/000`, plans et validation | PASS structurel | candidate réconciliée sans publication stable prématurée |
|
||||||
|
|
||||||
|
La tranche ne modifie aucun code/test/manifest de crate/dépendance et ne touche ni `CHANGELOG.md`, ni `ROADMAP.md`, ni le prompt `0.3.1`. Ces surfaces restent réservées à la préparation minimale de publication `pre.008`.
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.007` doit rester déterministe : audits, check, Clippy, tests ciblés Program/Logging et workspace suffisent ; aucun smoke live ni nouveau `cargo tree` n'est requis si l'overlay documentaire est appliqué exactement.
|
||||||
|
|
||||||
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 |
|
||||||
1463
prompts/020-V0_3_1_START_PROMPT.md
Normal file
1463
prompts/020-V0_3_1_START_PROMPT.md
Normal file
File diff suppressed because it is too large
Load Diff
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