38 Commits

Author SHA1 Message Date
6cc94ebb46 v0.3.1-rel.001 2026-08-29 12:33:09 +02:00
8e8f1f0b4a v0.3.1-pre.011 2026-08-29 12:17:41 +02:00
78e015413b v0.3.1-pre.010 2026-08-29 11:39:50 +02:00
2f0eb316f5 v0.3.1-pre.009 2026-08-29 11:22:29 +02:00
55325ffd32 v0.3.1-pre.008 2026-08-29 11:05:22 +02:00
ad02b6f13c v0.3.1-pre.007-fix.001 2026-08-29 09:54:55 +02:00
47b12e14e6 v0.3.1-pre.007 2026-08-29 09:29:58 +02:00
c43d4d7c38 v0.3.1-pre.006-fix.001 2026-08-29 09:12:38 +02:00
b76baf6835 v0.3.1-pre.006 2026-08-29 09:10:07 +02:00
c83e3261d0 v0.3.1-pre.005 2026-08-29 08:45:21 +02:00
28ca5bdac5 v0.3.1-pre.004 2026-08-29 08:20:16 +02:00
afd4c770f9 v0.3.1-pre.003-fix.001 2026-08-29 08:18:45 +02:00
ecc5607fd4 v0.3.1-pre.003 2026-08-29 07:51:09 +02:00
11e4cde77f v0.3.1-pre.002 2026-08-29 07:38:30 +02:00
62ed72f2b5 v0.3.1-pre.001-fix-002 2026-08-29 07:28:45 +02:00
1a07574bae v0.3.1-pre.001-fix-001 2026-08-29 06:39:25 +02:00
29a888d1dc v0.3.1-pre.001 2026-08-28 21:31:08 +02:00
3d307be645 v0.2.14-rel.001 2026-08-28 16:56:06 +02:00
7dfe892467 v0.2.14-pre.008 2026-08-28 16:50:48 +02:00
2cf00e9426 v0.2.14-pre.007 2026-08-28 16:16:15 +02:00
4eba9be48c v0.2.14-pre.006 2026-08-28 15:53:15 +02:00
bebfc63b75 v0.2.14-pre.005-fix.001 2026-08-28 15:30:31 +02:00
0319794e59 v0.2.14-pre.005 2026-08-28 15:12:59 +02:00
bcf2f16c05 v0.2.14-pre.004 2026-08-28 14:21:21 +02:00
27d4cb1f36 v0.2.14-pre.003 2026-08-28 14:10:49 +02:00
c1f61a380f v0.2.14-pre.002 2026-08-28 11:07:16 +02:00
5e5ed6d1ab v0.2.14-pre.001 2026-08-28 10:50:43 +02:00
31461cf9be v0.2.13-rel.001 2026-08-28 08:53:11 +02:00
8c8b7ee986 v0.2.13-pre.008 2026-08-28 08:50:19 +02:00
5bfe6f0820 v0.2.13-pre.007 2026-08-28 07:05:02 +02:00
8c4e835fdd v0.2.13-pre.006 2026-08-28 06:40:14 +02:00
695d1080bd v0.2.13-pre.006 2026-08-28 06:36:52 +02:00
e774a065b4 v0.2.13-pre.005-fix.001 2026-08-28 06:27:26 +02:00
29f27ae109 v0.2.13-pre.005 2026-08-28 05:24:26 +02:00
0ee28eeb95 v0.2.13-pre.004 2026-08-28 05:11:44 +02:00
900444bff5 v0.2.13-pre.003 2026-08-28 05:04:10 +02:00
99477d0d2b v0.2.13-pre.002 2026-08-28 04:31:32 +02:00
9148920f42 v0.2.13-pre.001 2026-08-27 23:33:01 +02:00
108 changed files with 20819 additions and 95 deletions

View File

@@ -1,8 +1,32 @@
<!-- file: CHANGELOG.md -->
<!-- version: 16 -->
<!-- version: 19 -->
# 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 dacquisition 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 didempotence/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 nentre dans la crate.
Les canaris de clôture verrouillent 60 exports crate-root, 10 capabilities, linventaire exact des modules RAW, limplémentabilité par un backend externe, les bornes adversariales, la redaction des `Debug`, la frontière Interface/Store et labsence 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 dinstructions 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`. Loutput `Decoded` reste possédé par limplé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 nentre dans cette foundation. Les canaris public API, implémentation externe, dependency firewall, release completeness et hardening couvrent notamment linventaire exact de dix exports crate-root, les trois modules de production, un Program Pubkey non enregistré, linput Interface maximal de 255 accounts / 10 240 bytes, labsence decho automatique dun payload hostile et labsence 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 larchive historique `khadhroony-bot3_v0.5.3-pre.005-fix010.zip` afin dauditer lancien `ks-store`, ses migrations PostgreSQL et ses contrats N1/N2/N3 sous une matrice `REPRENDRE / REDESSINER / REPORTER / REJETER`. Larchive 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, lInterface `0.3.2` ni la configuration produit.
## 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 lordre et les doublons des account metas, accepte les Program Pubkeys opaques et borne ladmission à **255 account metas** et **10 240 octets** de data. Les deux bornes sont des limites dadmission Interface et ne prétendent pas garantir à elles seules le fit dune 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 linstruction nexpose que `program_id`, `account_count` et `data_len`.
La dependency direction finale reste strictement `ksp-interface-lib -> ksp-core-lib -> solana-pubkey`. Aucun `serde`, codec générique (`borsh`, `wincode`, `bincode`), `solana-instruction`, runtime réseau ou logging nest ajouté à cette foundation. Les canaris public API, consumer externe, dependency firewall et release completeness verrouillent les six exports crate-root, les quatre modules de production, labsence de narrowing/codec caché, le transfert des `Vec` admis sans réallocation interne et la non-répétition de matériel hostile dans les erreurs. `pre.005-fix.001` corrige uniquement un faux positif du scanner Logging provoqué par le littéral de test `tracing::`, sans changement de production ni de dépendance. Les gates `pre.006` et `pre.007` passent ensuite audits Rust/Markdown, `cargo check`, Clippy, tests Interface, ownership Logging, workspace complet et graphes Cargo avant la réconciliation documentaire finale.
`prompts/019-V0_2_14_START_PROMPT.md` ouvre `0.2.14 — Program API foundation` exclusivement depuis le tag stable `v0.2.13`. Le gate `pre.001` de cette prochaine release exige aussi larchive historique `khadhroony-bot3_v0.5.3-pre.005-fix010.zip` afin dauditer les anciens contrats decoder/executor sous une matrice `REPRENDRE / REDESSINER / REPORTER / REJETER`, sans faire de kbot3 une autorité architecturale ni recréer son monolithe `ks-lib`.
## 0.2.12 — SOL Prices Desk + projection prix Wallet — 2026-08-27
`0.2.12` ajoute `ksp-app-solprices-desk`, troisième Desk Tauri spécialisé de KSP, comme HID strictement provider-neutral au-dessus de `ksp-offchain-transport-lib`. Son composite dédié sélectionne `std.logging` et `std.offchain_transport`; la vue Prices expose linventaire des huit providers SOL/USD, disponibilité, sémantique, auth, prix exact sous forme de chaîne, timestamps provider/KSP distincts et retry deadline sans inventer de fraîcheur. Les refresh manuel individuel, sélectionné et global passent exclusivement par `MarketPriceService`; les batchs sont bornés à `1..=64`, les états `in_flight` restent backend-owned et aucun polling, scheduler, fallback, moyenne ou prix canonique nest introduit dans le Desk. Le frontend conserve labsence de réseau/persistence navigateur et journalise les interactions sans prix, credential, URL, header ou payload provider.

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 302
# version: 331
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-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]
version = "0.2.12"
version = "0.3.1"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md -->
<!-- version: 90 -->
<!-- version: 94 -->
# Roadmap KSP
@@ -57,8 +57,8 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
- [X] `0.2.10` — OrbitFlare Yellowstone gRPC stable : profil Config V3 Devnet, License Key injectée comme metadata secrète `x-token`, smoke live `Subscribe -> Slot + Ping` validé deux fois, sans modification du moteur N1/N2 ni heartbeat provider.
- [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 nest 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 dune moyenne SOL/USD consumer-owned et dun équivalent USD exact best-effort ; smoke live de composition, workspace complet et bundles Tauri Linux validés.
- [ ] `0.2.13` — Introduire la première surface de `ksp-interface-lib`, façade wire KSP publique et passive utilisable par les implémentations officielles et externes ; `pre.001` doit confronter larchitecture actuelle aux contrats utiles de lancien `khadhroony-bot3` sans recréer son monolithe `ks-lib`, et séparer explicitement données wire, comportement Program et implémentations.
- [ ] `0.2.14`Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet ; les traits de reconnaissance/decode/build et autres comportements ne doivent pas être aspirés prématurément dans `ksp-interface-lib`.
- [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.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
@@ -80,33 +80,52 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
## Architecture de données — progression canonique
La chaîne durable cible est :
La progression durable cible est :
```text
RAW -> CORE -> DECODE -> SPECIALIZED
RAW -> STRUCTURAL -> DECODED -> DOMAIN
```
- **RAW** et **CORE** 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.
- À 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.
- **RAW** et **STRUCTURAL** ne nécessitent aucun décodage Program.
- 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.
- À 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.1` Introduire `ksp-store-api` + `ksp-store-lib` avec PostgreSQL de référence et **modèles/persistence RAW uniquement**.
- [ ] `0.3.2`Étendre `ksp-interface-lib` avec les wires génériques nécessaires aux acquisitions et à la future normalisation CORE.
- [ ] `0.3.3`Introduire `ksp-job-api` et un job de backfill historique concret.
- [ ] `0.3.4`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 à CORE.
- [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`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` nest ajouté dans cette slice.
- [ ] `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`É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.
- [ ] `0.3.5` — Étendre `ksp-interface-lib` uniquement avec les modèles passifs/events réellement partagés par les premiers consumers dacquisition, 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 dexploitation 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.
- [ ] Implémenter `RAW -> CORE` sans decoder Program : blocs, slots, signatures, transactions/messages, comptes, instructions/CPI brutes, logs/meta et relations structurelles.
- [ ] Ajouter replay/backfill RAW -> CORE.
- [ ] Ajouter worker/service CORE.
- [ ] Ajouter l'application de contrôle/inspection CORE utile.
- [ ] **TODO** — maintenir la matrice dadmission 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.
- [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`.
- [ ] **TODO**`TransactionStatusObservation` : réauditer `signatureSubscribe`, `getSignatureStatuses`, Yellowstone TransactionStatus et extensions provider lorsquun consumer réel apparaît ; ne pas fusionner snapshot, transition et update dans un modèle Option-soup.
- [ ] **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 dun wake-up « donnée persistée disponible » reste distinct et appartient à `ksp-store-api` conformément à `KSP-NOTIFY-*`, mais ne sera matérialisé quavec un publisher/consumer réel.
- [ ] **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 dabord ê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 nest identifiée.
- [ ] **TODO** — processing ledger : reprendre lidée kbot2/kbot3 `stage + processor identity/version + input identity/hash + terminal status`, sans faire dun `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 quun 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 louverture de N2/N3 ; conserver lisolation 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
@@ -119,7 +138,7 @@ RAW -> CORE -> DECODE -> SPECIALIZED
- [ ] SPL Token.
- [ ] Associated Token Account.
- [ ] 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
@@ -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.
- [ ] 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

View File

@@ -0,0 +1,14 @@
# file: crates/ksp-interface-lib/Cargo.toml
# version: 1
[package]
name = "ksp-interface-lib"
version.workspace = true
edition.workspace = true
repository.workspace = true
[dependencies]
ksp-core-lib = { path = "../ksp-core-lib" }
[lints]
workspace = true

View File

@@ -0,0 +1,131 @@
<!-- file: crates/ksp-interface-lib/README.md -->
<!-- version: 2 -->
# ksp-interface-lib
`ksp-interface-lib` est la façade wire officielle KSP destinée aux contrats passifs partagés par les implémentations Program Solana officielles ou externes. La crate expose uniquement des structures de représentation/admission ; elle ne possède ni transport, ni exécution, ni persistence, ni comportement métier Program.
## Ownership
La crate réutilise les primitives fondamentales déjà possédées par `ksp-core-lib` :
```text
Pubkey
Error / ErrorCode / Result
Program IDs fondamentaux
```
`Pubkey` est réexporté depuis le crate-root Interface afin qu'un consumer wire n'introduise aucun wrapper d'identité parallèle. Les Program IDs restent possédés et répertoriés par Core.
La dependency direction candidate `0.2.13` reste strictement :
```text
ksp-interface-lib
└── ksp-core-lib
└── solana-pubkey
```
## Surface publique `0.2.13`
La façade crate-root expose exactement :
```text
Pubkey
ProgramAccountMeta
MAX_PROGRAM_INSTRUCTION_ACCOUNTS
ProgramInstruction
MAX_PROGRAM_INSTRUCTION_DATA_LEN
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
```
Aucun module interne n'est public.
### `ProgramAccountMeta`
`ProgramAccountMeta` représente un compte ordonné d'une instruction avec :
```text
pubkey
is_signer
is_writable
```
Les champs restent privés. Les constructeurs publics sont `readonly(pubkey, is_signer)` et `writable(pubkey, is_signer)`, complétés par les accessors `pubkey()`, `is_signer()` et `is_writable()`.
La structure n'applique aucune sémantique spécifique à un programme et n'exige pas que la `Pubkey` appartienne au registry des Program IDs Core.
### `ProgramInstruction`
`ProgramInstruction` représente un contrat passif borné :
```text
program_id
accounts: Vec<ProgramAccountMeta>
data: Vec<u8>
```
`ProgramInstruction::try_new` consomme directement les deux `Vec`, conserve l'ordre et les doublons des accounts et préserve les octets opaques de `data` sans décodage.
Les cas vides sont valides et une `program_id` inconnue du registry KSP reste admissible.
## Bornes d'admission
Interface applique deux limites locales :
| Limite | Valeur |
|------------------------------------|----------|
| `MAX_PROGRAM_INSTRUCTION_ACCOUNTS` | `255` |
| `MAX_PROGRAM_INSTRUCTION_DATA_LEN` | `10_240` |
Ces valeurs sont des **bornes d'admission Interface**. Elles ne constituent pas une garantie qu'une instruction donnée tient dans toutes les contraintes d'une transaction Solana top-level. La limite CPI de comptes uniques n'est notamment pas transformée en règle artificielle sur la liste d'account metas.
Un dépassement utilise le code commun :
```text
interface.program_instruction_limit_exceeded
```
Le contexte d'erreur est limité aux métadonnées sûres `field`, `actual_len` et `maximum_len`. Aucun payload arbitraire ni account meta hostile n'est recopié dans l'erreur.
Le `Debug` de `ProgramInstruction` est volontairement borné : il affiche `program_id`, `account_count` et `data_len`, jamais les accounts complets ni les octets du payload.
## Codecs et runtime
La foundation `0.2.13` n'ajoute aucun codec par réflexe :
```text
serde / serde_json absents
borsh absent
wincode absent
bincode absent
solana-instruction absent
```
Des codecs/layouts/discriminants spécifiques pourront être ajoutés ultérieurement uniquement lorsqu'un vertical Program réel en démontre le besoin et que leur ownership wire appartient bien à Interface.
La crate ne produit aucun événement runtime. Elle ne dépend donc pas de `ksp-logging-lib` et ne possède ni `constants.rs` ni `TRACING_TARGET`. Si un futur comportement Interface exige réellement du logging, le runtime devra passer par la façade Logging KSP plutôt que par une dépendance directe à Tracing.
## Frontières
`ksp-interface-lib` ne possède pas :
```text
RPC / WebSocket / gRPC
provider DTOs Transport
wallet / signature
Config / environnement
persistence / Store
Program decoding / recognition / proofs
execution policy / signers
transaction replay / CPI path / runtime logs
lifecycle réseau
```
La foundation Program API est reportée à `0.2.14`. Les wires génériques d'acquisition/CORE restent reportés à `0.3.2+`.
## Références
- [Usage public](USAGE.md)
- [Plan `0.2.13`](../../docs/plans/020-V0_2_13_INTERFACE_PLAN.md)
- [Validation `0.2.13`](../../docs/validation/016-V0_2_13_INTERFACE.md)
- [Architecture Wire + Program](../../docs/architecture/006-WIRE_AND_PROGRAM.md)

View File

@@ -0,0 +1,122 @@
<!-- file: crates/ksp-interface-lib/USAGE.md -->
<!-- version: 2 -->
# Usage de ksp-interface-lib
Cette page décrit la façade publique matérialisée par `0.2.13`. Les modules internes ne font pas partie du contrat consommable : utiliser uniquement les exports du crate-root.
## Construire des account metas
```rust
let readonly_account = ksp_interface_lib::Pubkey::new_from_array([1_u8; 32]);
let writable_account = ksp_interface_lib::Pubkey::new_from_array([2_u8; 32]);
let readonly = ksp_interface_lib::ProgramAccountMeta::readonly(readonly_account, true);
let writable = ksp_interface_lib::ProgramAccountMeta::writable(writable_account, false);
assert_eq!(readonly.pubkey(), &readonly_account);
assert!(readonly.is_signer());
assert!(!readonly.is_writable());
assert_eq!(writable.pubkey(), &writable_account);
assert!(!writable.is_signer());
assert!(writable.is_writable());
```
`readonly`/`writable` décrivent uniquement les flags wire de l'account meta. Interface ne valide pas l'identité du compte contre un registry Program.
## Construire une instruction passive
```rust
let program_id = ksp_interface_lib::Pubkey::new_from_array([3_u8; 32]);
let account_id = ksp_interface_lib::Pubkey::new_from_array([4_u8; 32]);
let account = ksp_interface_lib::ProgramAccountMeta::writable(account_id, true);
let result = ksp_interface_lib::ProgramInstruction::try_new(
program_id,
std::vec![account, account],
std::vec![7_u8, 8, 9],
);
match result {
std::result::Result::Ok(instruction) => {
assert_eq!(instruction.program_id(), &program_id);
assert_eq!(instruction.accounts(), &[account, account]);
assert_eq!(instruction.data(), &[7_u8, 8, 9]);
}
std::result::Result::Err(error) => {
eprintln!("{error}");
}
}
```
L'ordre et les doublons des accounts sont conservés. Les octets `data` restent opaques : `ProgramInstruction` ne les sérialise, désérialise ni interprète.
Les `Vec` fournis à `try_new` sont consommés par la structure après validation des bornes ; aucun clone ou reformatage interne n'est requis par le contrat actuel.
## Bornes
Les limites publiques sont :
```rust
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
```
`255` account metas et `10_240` bytes de data sont admis. `256` account metas ou `10_241` bytes sont refusés par `try_new` avant création d'un `ProgramInstruction` valide.
Ces limites sont des bornes locales Interface et ne promettent pas qu'une instruction admise respecte à elle seule toutes les contraintes de taille/account-set d'une transaction Solana complète.
## Observer une erreur de limite
Le code public est :
```rust
assert_eq!(
ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.domain(),
"interface",
);
assert_eq!(
ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.code(),
"program_instruction_limit_exceeded",
);
```
Une erreur de dépassement expose uniquement un contexte structurel sûr :
```text
field
actual_len
maximum_len
```
Le contenu du payload et les account metas arbitraires ne sont pas projetés dans le diagnostic.
## Debug borné
Le `Debug` de `ProgramInstruction` contient uniquement :
```text
program_id
account_count
data_len
```
Il ne faut donc pas attendre de ce rendu une sérialisation wire ou un dump du payload.
## Dépendances à ne pas ajouter côté consumer
Un consumer de la façade Interface n'a pas besoin d'ajouter un SDK Program Solana uniquement pour reconstruire `ProgramInstruction`. La crate utilise le `Pubkey` canonique partagé avec Core et conserve son propre contrat passif.
La foundation ne fournit volontairement pas :
```text
serde générique
Borsh / Wincode générique
solana-instruction interop automatique
transport réseau
Program decoder/preparer
signing/execution
```
Ces surfaces doivent être introduites dans leur owner respectif lorsqu'un cas réel le justifie, pas comme dépendances implicites d'un consumer Interface.

View File

@@ -0,0 +1,6 @@
// file: crates/ksp-interface-lib/src/error.rs
// version: 1
/// Error code used when a bounded passive Program instruction contract exceeds one of its Interface-owned admission limits.
pub const ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("interface", "program_instruction_limit_exceeded");

View File

@@ -0,0 +1,30 @@
// file: crates/ksp-interface-lib/src/lib.rs
// version: 3
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Passive wire contracts shared by KSP Program implementations.
//!
//! The foundation reuses the canonical Solana [`Pubkey`] owned by
//! `ksp-core-lib` and exposes only bounded, passive Program-facing structures.
//! Runtime, transport, persistence and Program behavior remain outside this
//! crate.
mod error;
mod program_account_meta;
mod program_instruction;
/// Error code used when an Interface-owned Program instruction admission limit is exceeded.
pub use self::error::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED;
/// Maximum number of account metas admitted by one passive Program instruction contract.
pub use self::program_account_meta::MAX_PROGRAM_INSTRUCTION_ACCOUNTS;
/// Passive account metadata attached to one Program instruction.
pub use self::program_account_meta::ProgramAccountMeta;
/// Maximum opaque data payload admitted by one passive Program instruction contract.
pub use self::program_instruction::MAX_PROGRAM_INSTRUCTION_DATA_LEN;
/// Passive, bounded Program instruction wire contract.
pub use self::program_instruction::ProgramInstruction;
/// Canonical Solana account address primitive owned by `ksp-core-lib`.
pub use ksp_core_lib::Pubkey;

View File

@@ -0,0 +1,52 @@
// file: crates/ksp-interface-lib/src/program_account_meta.rs
// version: 1
/// Maximum number of account metas admitted by one passive Program instruction contract.
pub const MAX_PROGRAM_INSTRUCTION_ACCOUNTS: usize = 255;
/// Passive account metadata attached to one Program instruction.
///
/// Account metas preserve the caller-provided Solana account identity and the
/// signer/writable flags without applying Program-specific semantics.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProgramAccountMeta {
pubkey: crate::Pubkey,
is_signer: bool,
is_writable: bool,
}
impl ProgramAccountMeta {
/// Creates one read-only account meta.
#[must_use]
pub const fn readonly(pubkey: crate::Pubkey, is_signer: bool) -> Self {
return Self { pubkey, is_signer, is_writable: false };
}
/// Creates one writable account meta.
#[must_use]
pub const fn writable(pubkey: crate::Pubkey, is_signer: bool) -> Self {
return Self { pubkey, is_signer, is_writable: true };
}
/// Returns whether this account must sign the containing instruction.
#[must_use]
pub const fn is_signer(&self) -> bool {
return self.is_signer;
}
/// Returns whether this account may be written by the containing instruction.
#[must_use]
pub const fn is_writable(&self) -> bool {
return self.is_writable;
}
/// Returns the canonical account address.
#[must_use]
pub const fn pubkey(&self) -> &crate::Pubkey {
return &self.pubkey;
}
}
#[cfg(test)]
#[path = "../unit_tests/program_account_meta.rs"]
mod tests;

View File

@@ -0,0 +1,81 @@
// file: crates/ksp-interface-lib/src/program_instruction.rs
// version: 1
/// Maximum opaque data payload admitted by one passive Program instruction contract.
pub const MAX_PROGRAM_INSTRUCTION_DATA_LEN: usize = 10 * 1024;
/// Passive, bounded Program instruction wire contract.
///
/// The instruction preserves the caller-provided Program identity, ordered
/// account metas and opaque data bytes without applying Program-specific
/// semantics. Construction enforces only the Interface-owned admission bounds.
pub struct ProgramInstruction {
program_id: crate::Pubkey,
accounts: std::vec::Vec<crate::ProgramAccountMeta>,
data: std::vec::Vec<u8>,
}
impl ProgramInstruction {
/// Creates one passive Program instruction after enforcing Interface admission bounds.
///
/// The provided vectors are consumed directly. Their account order and duplicates
/// are preserved exactly when construction succeeds.
pub fn try_new(program_id: crate::Pubkey, accounts: std::vec::Vec<crate::ProgramAccountMeta>, data: std::vec::Vec<u8>) -> ksp_core_lib::Result<Self> {
if accounts.len() > crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS {
return std::result::Result::Err(
ksp_core_lib::Error::new(
crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED,
"Program instruction account count exceeds the Interface admission limit",
)
.with_context("field", "accounts")
.with_context("actual_len", accounts.len().to_string())
.with_context("maximum_len", crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS.to_string()),
);
}
if data.len() > crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN {
return std::result::Result::Err(
ksp_core_lib::Error::new(
crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED,
"Program instruction data length exceeds the Interface admission limit",
)
.with_context("field", "data")
.with_context("actual_len", data.len().to_string())
.with_context("maximum_len", crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN.to_string()),
);
}
return std::result::Result::Ok(Self { program_id, accounts, data });
}
/// Returns the ordered account metas exactly as admitted at construction.
#[must_use]
pub fn accounts(&self) -> &[crate::ProgramAccountMeta] {
return self.accounts.as_slice();
}
/// Returns the opaque Program instruction data bytes.
#[must_use]
pub fn data(&self) -> &[u8] {
return self.data.as_slice();
}
/// Returns the Program identity.
#[must_use]
pub const fn program_id(&self) -> &crate::Pubkey {
return &self.program_id;
}
}
impl std::fmt::Debug for ProgramInstruction {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("ProgramInstruction")
.field("program_id", &self.program_id)
.field("account_count", &self.accounts.len())
.field("data_len", &self.data.len())
.finish();
}
}
#[cfg(test)]
#[path = "../unit_tests/program_instruction.rs"]
mod tests;

View File

@@ -0,0 +1,131 @@
// file: crates/ksp-interface-lib/tests/dependency_boundary.rs
// version: 5
//! Dependency and passive-surface canaries for the Interface foundation.
#[test]
fn pre_002_manifest_has_exact_core_only_runtime_dependency() {
let manifest = include_str!("../Cargo.toml");
let dependencies_tail = manifest.split("[dependencies]").nth(1);
assert!(dependencies_tail.is_some(), "Interface 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-logging-lib",
"ksp-offchain-transport-lib",
"ksp-onchain-transport-lib",
"ksp-program-api",
"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 Interface dependency detected: {forbidden}");
}
return;
}
#[test]
fn pre_005_surface_remains_passive_without_codecs_or_runtime_logging() {
let crate_root = include_str!("../src/lib.rs");
assert!(crate_root.contains("ProgramAccountMeta"));
assert!(crate_root.contains("MAX_PROGRAM_INSTRUCTION_ACCOUNTS"));
assert!(crate_root.contains("ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED"));
assert!(crate_root.contains("ProgramInstruction"));
assert!(crate_root.contains("MAX_PROGRAM_INSTRUCTION_DATA_LEN"));
assert!(!crate_root.contains("TRACING_TARGET"));
assert!(!crate_root.contains("ksp_logging_lib"));
assert!(!std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/constants.rs").exists());
let instruction_source = include_str!("../src/program_instruction.rs");
for forbidden in ["serde", "borsh", "bincode", "wincode", "solana_instruction", "ksp_logging_lib", "TRACING_TARGET"] {
assert!(!instruction_source.contains(forbidden), "forbidden Interface surface detected: {forbidden}");
}
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;
}
#[test]
fn pre_005_all_production_sources_preserve_the_dependency_firewall() {
let production_sources = [
include_str!("../src/error.rs"),
include_str!("../src/lib.rs"),
include_str!("../src/program_account_meta.rs"),
include_str!("../src/program_instruction.rs"),
];
for source in production_sources {
for forbidden in [
"borsh::",
"bincode::",
"ksp_config_lib::",
"ksp_logging_lib::",
"ksp_offchain_transport_lib::",
"ksp_onchain_transport_lib::",
"ksp_program_api::",
"ksp_program_lib::",
"ksp_store_api::",
"ksp_store_lib::",
"ksp_wallet_lib::",
"reqwest::",
"serde::",
"serde_json::",
"solana_instruction::",
"tauri::",
"tokio::",
"tonic::",
concat!("tracing", "::"),
"wincode::",
] {
assert!(!source.contains(forbidden), "forbidden production dependency path detected: {forbidden}");
}
}
return;
}
#[test]
fn pre_005_instruction_source_has_no_narrowing_cast_or_hidden_codec_entry_point() {
let instruction_source = include_str!("../src/program_instruction.rs");
for forbidden in [" as u8", " as u16", " as u32", " as u64", "serialize", "deserialize", "encode", "decode"] {
assert!(!instruction_source.contains(forbidden), "forbidden instruction implementation pattern detected: {forbidden}");
}
return;
}

View File

@@ -0,0 +1,27 @@
// file: crates/ksp-interface-lib/tests/external_consumer.rs
// version: 1
//! Downstream-style consumer canary for the public Interface facade.
fn consume_instruction(instruction: ksp_interface_lib::ProgramInstruction) -> (ksp_interface_lib::Pubkey, usize, usize) {
return (*instruction.program_id(), instruction.accounts().len(), instruction.data().len());
}
#[test]
fn pre_005_external_consumer_uses_only_the_crate_root_facade() {
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xA1_u8; 32]);
let account_id = ksp_interface_lib::Pubkey::new_from_array([0xA2_u8; 32]);
let readonly = ksp_interface_lib::ProgramAccountMeta::readonly(account_id, true);
let writable = ksp_interface_lib::ProgramAccountMeta::writable(account_id, false);
let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![readonly, writable, readonly], std::vec![7_u8, 8, 9]);
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(consume_instruction(instruction), (program_id, 3, 3));
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.domain(), "interface");
return;
}

View File

@@ -0,0 +1,79 @@
// file: crates/ksp-interface-lib/tests/public_api.rs
// version: 4
//! Integration canaries for the public `ksp-interface-lib` foundation.
fn consume_pubkey(pubkey: ksp_interface_lib::Pubkey) -> [u8; 32] {
return pubkey.to_bytes();
}
#[test]
fn public_pre_002_pubkey_contract_is_available_from_crate_root() {
let pubkey = ksp_interface_lib::Pubkey::default();
assert_eq!(consume_pubkey(pubkey), [0_u8; 32]);
return;
}
#[test]
fn public_pre_003_account_meta_and_limit_contracts_are_available_from_crate_root() {
let pubkey = ksp_interface_lib::Pubkey::new_from_array([3_u8; 32]);
let readonly = ksp_interface_lib::ProgramAccountMeta::readonly(pubkey, true);
let writable = ksp_interface_lib::ProgramAccountMeta::writable(pubkey, false);
assert_eq!(readonly.pubkey(), &pubkey);
assert!(readonly.is_signer());
assert!(!readonly.is_writable());
assert_eq!(writable.pubkey(), &pubkey);
assert!(!writable.is_signer());
assert!(writable.is_writable());
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
return;
}
#[test]
fn public_pre_003_interface_error_code_is_stable_and_core_owned() {
let code: ksp_core_lib::ErrorCode = ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED;
assert_eq!(code.domain(), "interface");
assert_eq!(code.code(), "program_instruction_limit_exceeded");
return;
}
#[test]
fn public_pre_004_program_instruction_contract_is_available_from_crate_root() {
let program_id = ksp_interface_lib::Pubkey::new_from_array([4_u8; 32]);
let account = ksp_interface_lib::ProgramAccountMeta::readonly(ksp_interface_lib::Pubkey::new_from_array([5_u8; 32]), true);
let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![account], std::vec![1_u8, 2, 3]);
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(instruction.program_id(), &program_id);
assert_eq!(instruction.accounts(), &[account]);
assert_eq!(instruction.data(), &[1_u8, 2, 3]);
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
return;
}
#[test]
fn public_pre_005_limit_error_is_observable_without_private_module_access() {
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xC1_u8; 32]);
let rejected = ksp_interface_lib::ProgramInstruction::try_new(
program_id,
std::vec::Vec::new(),
std::vec![0xC2_u8; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN + 1],
);
assert!(rejected.is_err());
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED);
assert_eq!(error.context().len(), 3);
assert_eq!(error.context()[0].key(), "field");
assert_eq!(error.context()[0].value(), "data");
assert_eq!(error.context()[1].key(), "actual_len");
assert_eq!(error.context()[1].value(), "10241");
assert_eq!(error.context()[2].key(), "maximum_len");
assert_eq!(error.context()[2].value(), "10240");
return;
}

View File

@@ -0,0 +1,75 @@
// file: crates/ksp-interface-lib/tests/release_completeness.rs
// version: 1
//! Release-level completeness canaries for the `0.2.13` Interface 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::error::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED;",
"pub use self::program_account_meta::MAX_PROGRAM_INSTRUCTION_ACCOUNTS;",
"pub use self::program_account_meta::ProgramAccountMeta;",
"pub use self::program_instruction::MAX_PROGRAM_INSTRUCTION_DATA_LEN;",
"pub use self::program_instruction::ProgramInstruction;",
"pub use ksp_core_lib::Pubkey;",
];
expected.sort_unstable();
assert_eq!(actual, expected);
assert!(!crate_root.contains("pub mod "));
return;
}
#[test]
fn pre_005_production_module_inventory_contains_no_second_wire_domain() -> 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!["error.rs", "lib.rs", "program_account_meta.rs", "program_instruction.rs"]);
return std::result::Result::Ok(());
}
#[test]
fn pre_005_foundation_has_one_error_code_and_two_bounded_passive_types() {
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.domain(), "interface");
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.code(), "program_instruction_limit_exceeded");
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xB1_u8; 32]);
let account = ksp_interface_lib::ProgramAccountMeta::readonly(ksp_interface_lib::Pubkey::new_from_array([0xB2_u8; 32]), false);
let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![account], std::vec![0xB3_u8]);
assert!(instruction.is_ok());
return;
}

View File

@@ -0,0 +1,31 @@
// file: crates/ksp-interface-lib/unit_tests/program_account_meta.rs
// version: 1
#[test]
fn readonly_and_writable_constructors_preserve_identity_signer_and_writable_flags() {
let readonly_pubkey = crate::Pubkey::new_from_array([7_u8; 32]);
let writable_pubkey = crate::Pubkey::new_from_array([9_u8; 32]);
let readonly = crate::ProgramAccountMeta::readonly(readonly_pubkey, true);
assert_eq!(readonly.pubkey(), &readonly_pubkey);
assert!(readonly.is_signer());
assert!(!readonly.is_writable());
let writable = crate::ProgramAccountMeta::writable(writable_pubkey, false);
assert_eq!(writable.pubkey(), &writable_pubkey);
assert!(!writable.is_signer());
assert!(writable.is_writable());
return;
}
#[test]
fn account_meta_accepts_opaque_pubkeys_without_program_registry_validation() {
let opaque_pubkey = crate::Pubkey::new_from_array([0xA5_u8; 32]);
let meta = crate::ProgramAccountMeta::readonly(opaque_pubkey, false);
assert_eq!(meta.pubkey(), &opaque_pubkey);
return;
}
#[test]
fn account_limit_constant_matches_the_interface_admission_contract() {
assert_eq!(crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
return;
}

View File

@@ -0,0 +1,162 @@
// file: crates/ksp-interface-lib/unit_tests/program_instruction.rs
// version: 2
fn meta(byte: u8, writable: bool) -> crate::ProgramAccountMeta {
let pubkey = crate::Pubkey::new_from_array([byte; 32]);
if writable {
return crate::ProgramAccountMeta::writable(pubkey, false);
}
return crate::ProgramAccountMeta::readonly(pubkey, false);
}
#[test]
fn instruction_accepts_empty_accounts_and_data_with_opaque_program_id() {
let program_id = crate::Pubkey::new_from_array([0xD3_u8; 32]);
let instruction = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec::Vec::new());
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(instruction.program_id(), &program_id);
assert!(instruction.accounts().is_empty());
assert!(instruction.data().is_empty());
return;
}
#[test]
fn instruction_preserves_account_order_duplicates_and_opaque_data() {
let program_id = crate::Pubkey::new_from_array([0x41_u8; 32]);
let first = meta(1, false);
let duplicate = meta(2, true);
let accounts = std::vec![first, duplicate, first];
let data = std::vec![0_u8, 1, 2, 0xFF];
let instruction = crate::ProgramInstruction::try_new(program_id, accounts, data.clone());
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(instruction.accounts(), &[first, duplicate, first]);
assert_eq!(instruction.data(), data.as_slice());
return;
}
#[test]
fn instruction_accepts_account_limit_and_rejects_one_above_it() {
let program_id = crate::Pubkey::new_from_array([0x51_u8; 32]);
let account = meta(7, false);
let accepted = crate::ProgramInstruction::try_new(program_id, std::vec![account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS], std::vec::Vec::new());
assert!(accepted.is_ok());
let rejected = crate::ProgramInstruction::try_new(program_id, std::vec![account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS + 1], std::vec::Vec::new());
assert!(rejected.is_err());
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED);
assert_eq!(error.message(), "Program instruction account count exceeds the Interface admission limit");
assert_eq!(error.context().len(), 3);
assert_eq!(error.context()[0].key(), "field");
assert_eq!(error.context()[0].value(), "accounts");
assert_eq!(error.context()[1].key(), "actual_len");
assert_eq!(error.context()[1].value(), "256");
assert_eq!(error.context()[2].key(), "maximum_len");
assert_eq!(error.context()[2].value(), "255");
return;
}
#[test]
fn instruction_accepts_data_limit_and_rejects_one_byte_above_it() {
let program_id = crate::Pubkey::new_from_array([0x61_u8; 32]);
let accepted = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec![0xA5_u8; crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN]);
assert!(accepted.is_ok());
let rejected = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec![0x5A_u8; crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN + 1]);
assert!(rejected.is_err());
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED);
assert_eq!(error.message(), "Program instruction data length exceeds the Interface admission limit");
assert_eq!(error.context().len(), 3);
assert_eq!(error.context()[0].value(), "data");
assert_eq!(error.context()[1].value(), "10241");
assert_eq!(error.context()[2].value(), "10240");
return;
}
#[test]
fn instruction_debug_is_bounded_and_omits_accounts_and_payload_bytes() {
let program_id = crate::Pubkey::new_from_array([0x71_u8; 32]);
let payload = b"PAYLOAD_SENTINEL_NEVER_RENDER".to_vec();
let instruction = crate::ProgramInstruction::try_new(program_id, std::vec![meta(8, false), meta(9, true)], payload);
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let rendered = std::format!("{instruction:?}");
assert!(rendered.contains("ProgramInstruction"));
assert!(rendered.contains("account_count: 2"));
assert!(rendered.contains("data_len: 29"));
assert!(!rendered.contains("PAYLOAD_SENTINEL_NEVER_RENDER"));
assert!(!rendered.contains("is_signer"));
assert!(!rendered.contains("is_writable"));
return;
}
#[test]
fn instruction_preserves_owned_vector_allocations_without_internal_reallocation() {
let program_id = crate::Pubkey::new_from_array([0x81_u8; 32]);
let accounts = std::vec![meta(10, false), meta(11, true), meta(12, false)];
let accounts_ptr = accounts.as_ptr();
let data = std::vec![0x10_u8, 0x20, 0x30, 0x40];
let data_ptr = data.as_ptr();
let instruction = crate::ProgramInstruction::try_new(program_id, accounts, data);
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(instruction.accounts().as_ptr(), accounts_ptr);
assert_eq!(instruction.data().as_ptr(), data_ptr);
return;
}
#[test]
fn instruction_limit_errors_do_not_echo_hostile_payload_or_account_material() {
let program_id = crate::Pubkey::new_from_array([0x91_u8; 32]);
let hostile_marker = b"HOSTILE_INTERFACE_PAYLOAD_SENTINEL";
let mut hostile_data = hostile_marker.to_vec();
hostile_data.resize(crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN + 1, 0xA5_u8);
let data_error = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), hostile_data);
assert!(data_error.is_err());
let data_error = match data_error {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
let data_display = std::format!("{data_error}");
let data_debug = std::format!("{data_error:?}");
assert!(!data_display.contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL"));
assert!(!data_debug.contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL"));
for context in data_error.context() {
assert!(!context.value().contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL"));
}
let hostile_account = meta(0xE1, true);
let hostile_account_debug = std::format!("{hostile_account:?}");
let account_error =
crate::ProgramInstruction::try_new(program_id, std::vec![hostile_account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS + 1], std::vec::Vec::new());
assert!(account_error.is_err());
let account_error = match account_error {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
let account_debug = std::format!("{account_error:?}");
assert!(!account_debug.contains(hostile_account_debug.as_str()));
assert_eq!(account_error.context().len(), 3);
assert_eq!(account_error.context()[0].value(), "accounts");
assert_eq!(account_error.context()[1].value(), "256");
assert_eq!(account_error.context()[2].value(), "255");
return;
}

View 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

View 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)

View 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.

View 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;

View 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;

View 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>>;
}

View 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;
}

View 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;
}

View 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);
}
}

View 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;
}

View 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;
}

View File

@@ -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;
}

View 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

View 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>>;

View 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>>;
}

View 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>>;
}

View 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>>;
}

View 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");

View 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;

View 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;

View 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;

View 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;
}
}

View 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;

View 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;

View 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;

View File

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

View 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;
}

View 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;
}

View 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;
}

View 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);
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

276
deltas/0.2.13/pre.001.md Normal file
View File

@@ -0,0 +1,276 @@
<!-- file: deltas/0.2.13/pre.001.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.001` — audit/sizing Interface / wire foundation
## 1. Base requise
```text
v0.2.12
```
Archive opérateur utilisée : `khadhroony-solana-project-v0.2.12.zip`, annoncée comme issue du tag Gitea stable.
Vérifications d'identité :
```text
workspace.package.version = 0.2.12 PASS à l'entrée
deltas/0.2.12/rel.001.md présent
prompts/018-V0_2_13_START_PROMPT.md présent
crates/ksp-interface-lib absent
crates/ksp-program-api absent
.git absent de l'archive
```
Le tag stable n'est donc pas revérifiable localement par Git. L'archive opérateur, le delta stable et la preuve opérateur fournie constituent l'autorité de reprise.
## 2. Objectif
Exécuter le gate obligatoire `pre.001` sans ouvrir encore la crate : relire règles/architecture, auditer Core/Transport, auditer l'héritage kbot3, vérifier les candidates externes actuelles, décider le premier lot wire, fixer ownership/bornes/threat model/dependency graph/tests, produire plan/validation et recalibrer la release.
## 3. Décisions prises
```text
crate cible ksp-interface-lib
premier lot ProgramAccountMeta + ProgramInstruction
orientation Program-facing passive
Pubkey ksp-core-lib::Pubkey, aucun wrapper parallèle
Program IDs fondamentaux restent ksp-core-lib
account metas ordre + doublons préservés
borne account metas 255
borne instruction data 10_240 octets
fields privés pour préserver les invariants
serde OUT pour la foundation
borsh/bincode/wincode OUT sans protocole concret
solana-instruction référence normative, pas dépendance runtime
solana-loader-v3-interface reporté au vertical loader-v3 réel
Transport DTO refactor OUT
Program traits/decode/recognition 0.2.14, OUT de Interface
replay/CPI/logs/return-data/Core 0.3.2+, OUT de 0.2.13
nouvelle dépendance externe aucune
network/logging runtime aucun
```
Les plafonds 255/10 KiB sont des bornes d'admission Interface dérivées des limites runtime Solana actuelles. Ils ne garantissent pas qu'une instruction donnée rentre dans une transaction top-level complète ; l'assembly transactionnel reste hors scope.
## 4. Héritage kbot3
Archive auditée : `khadhroony-bot3_v0.5.3-pre.005-fix010.zip`.
Verdict synthétique :
```text
REPRENDRE ordered account meta; instruction program_id/accounts/data
REDESSINER wrappers String Pubkey/ProgramId; payload non borné
REPORTER decoder traits; recognition/proofs; replay/CPI/logs/return data;
canonical transaction; prepared execution/signers/policies
REJETER monolithe ks-lib; JSON/version/hash génériques par réflexe;
operation_code dans le contrat d'instruction passive
```
Aucun code kbot3 n'est porté par ce delta.
## 5. Audit externe ciblé
État observé le 27 août 2026 :
```text
solana-instruction 3.5.0
Instruction = program_id: Pubkey + Vec<AccountMeta> + Vec<u8>
solana-pubkey ^4.3.0
serde/borsh/bincode/wincode optionnels
licence Anza SDK Apache-2.0
solana-program 4.1.0
MAX_CPI_INSTRUCTION_ACCOUNTS = 255
MAX_CPI_INSTRUCTION_DATA_LEN = 10 * 1024
MAX_CPI_ACCOUNT_INFOS = 128 (unique account infos; non appliqué aux metas)
solana-loader-v3-interface 8.1.1
compatible avec solana-instruction ^3.5.0 / solana-pubkey ^4.3.0 observés
hors premier lot
```
Aucune nouvelle dépendance n'est justifiée par `pre.001`.
## 6. Fichiers ajoutés
```text
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
deltas/0.2.13/pre.001.md
```
## 7. Fichiers modifiés
```text
Cargo.toml
docs/000-README.md
docs/plans/000-README.md
docs/validation/000-README.md
```
`workspace.package.version` passe à `0.2.13-pre.1` conformément au workflow des prereleases non-fix.
## 8. Fichiers supprimés
```text
aucun
```
## 9. Fichiers volontairement inchangés
```text
ROADMAP.md
CHANGELOG.md
prompts/018-V0_2_13_START_PROMPT.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/architecture/**
crates/**
config/**
.env.example
```
La roadmap/séquence/architecture possèdent déjà les frontières `0.2.13` / `0.2.14` / `0.3.2`. `pre.001` les confronte à la base réelle dans le nouveau plan sans les réécrire par duplication. `ROADMAP.md` et `CHANGELOG.md` restent hors du couloir de cette tranche.
## 10. Audit interne exécuté
Points confirmés :
```text
Core réexporte Pubkey et possède les Program IDs fondamentaux
Core possède Error/Result communs
Transport possède encore les projections HTTP/WS/Yellowstone concrètes
aucun besoin de déplacer ces DTOs vers Interface en 0.2.13
Interface ne doit pas dépendre de Program/Transport/Store/Wallet/Config
Program API pourra dépendre de Core + Interface en 0.2.14
wires d'acquisition/CORE génériques restent réservés à 0.3.2+
workspace n'a pas solana-instruction/borsh/wincode comme besoin Interface actuel
```
## 11. Threat model et tests décidés
```text
accounts 255 accepté
accounts 256 refusé
data 10_240 accepté
data 10_241 refusé
empty accounts/data accepté
ordre + doublons préservés
unknown Program Pubkey accepté
Debug instruction résumé borné
payload hostile dans Debug/Error absent
serde hostile éliminé par absence de serde
malformed/discriminant N/A tant qu'aucun codec n'est retenu
panic/unwrap/expect/? audits/lints KSP
network smoke non requis
public API / external consumer requis
cargo tree / dependency firewall requis dès scaffold
```
## 12. Validations exécutées dans le sandbox
Avant modification, sur l'archive stable extraite :
```text
python3 scripts/audit_rust_workspace_rules.py PASS
python3 scripts/audit_markdown_tables.py ... deltas/0.2.13 PASS
```
La preuve opérateur fournie sur le checkout stable `0.2.12` établit :
```text
cargo fmt --all PASS
Rust rules audit PASS
Markdown table audit PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test --workspace PASS
```
Après génération du présent overlay :
```text
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 ... deltas/0.2.13 PASS
tables / files 160 / 111
```
## 13. Validations non exécutées dans ce sandbox
```text
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test --workspace
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
Cause : executable `cargo` absent du sandbox de préparation. `ksp-interface-lib` n'existe pas encore par contrat de `pre.001`, donc les graphes propres à la crate commenceront avec `pre.002`.
Aucune commande non exécutée localement n'est déclarée PASS.
## 14. Forecast souple recalibré
### pre.001 — audit + héritage + frontières + sizing
**Statut : livré par le présent delta.**
### pre.002 — scaffold `ksp-interface-lib` + façade + firewall
**Statut : prévu.**
### pre.003 — `ProgramAccountMeta` + bornes communes
**Statut : prévu.**
### pre.004 — `ProgramInstruction` passif borné
**Statut : prévu.**
### pre.005 — adversarial + consumer externe + API/dependency hardening
**Statut : prévu.**
### pre.006 — gate technique final
**Statut : prévu.**
### pre.007 — réconciliation documentaire finale
**Statut : prévu.**
### pre.008 — préparation de publication minimale
**Statut : prévu.**
### rel.001 — publication stable
**Statut : prévu.**
Le second lot wire optionnel du forecast initial est supprimé : aucun second domaine ne justifie une tranche dans cette foundation. Le scope reste compatible avec une release/session.
## 15. Questions ouvertes
Aucune question bloquante pour `pre.002`.
Les noms exacts des codes d'erreur et le détail final des accessors peuvent encore être ajustés dans `pre.003`/`pre.004` sans rouvrir l'ownership, les bornes ou le scope de release.
## 16. Validation opérateur après application
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.2.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test --workspace
```
`pre.002` ne commence qu'après retour du gate appliqué.

205
deltas/0.2.13/pre.002.md Normal file
View File

@@ -0,0 +1,205 @@
<!-- file: deltas/0.2.13/pre.002.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.002` — scaffold `ksp-interface-lib`
## 1. Base requise
Cette tranche s'applique exclusivement sur :
```text
v0.2.12
+ 0.2.13-pre.001
```
Le gate opérateur fourni pour `pre.001` est intégralement vert : `cargo fmt --all`, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace et `cargo test --workspace` passent.
La version workspace passe de :
```text
0.2.13-pre.1
```
à :
```text
0.2.13-pre.2
```
## 2. Objectif
Créer uniquement le scaffold de `ksp-interface-lib` et verrouiller sa frontière avant les types wire fonctionnels.
La tranche matérialise :
```text
nouveau membre workspace ksp-interface-lib
manifest avec ksp-core-lib comme seule dépendance normale
façade crate-root explicite
réexport contrôlé de ksp_core_lib::Pubkey
README/USAGE initiaux
canari public API minimal
canari dependency firewall
```
Elle ne matérialise pas encore :
```text
ProgramAccountMeta
ProgramInstruction
bornes instruction
codes d'erreur Interface
serde / serde_json
borsh / bincode / wincode
solana-instruction
network/runtime
Program behavior
```
## 3. Décision Logging
`0.2.13` reste une foundation de contrats passifs. Aucun comportement runtime n'est actuellement prévu dans `ksp-interface-lib`, donc aucun événement/span n'a besoin d'être émis.
En conséquence, la tranche maintient explicitement absents :
```text
ksp-logging-lib
src/constants.rs
TRACING_TARGET
tracing direct
```
Cette absence est volontaire et couverte par le canari de dépendances. Si un comportement runtime réel apparaît dans une future évolution d'Interface, l'instrumentation devra être ajoutée via `ksp-logging-lib`; `DEP-LOG-010` imposera alors un `pub(crate) const TRACING_TARGET: &str` possédé par `src/constants.rs`.
Les futures validations de bornes de `pre.003`/`pre.004` doivent retourner des erreurs structurées ; elles ne justifient pas à elles seules des logs runtime.
## 4. Surface publique de scaffold
La seule primitive publique nouvellement exposée est :
```rust
pub use ksp_core_lib::Pubkey;
```
Ce réexport confirme l'ownership décidé : Interface n'introduit aucun wrapper `ProgramId`/`Pubkey` parallèle et utilisera la primitive Core dans ses futurs contrats wire.
La façade ne contient aucun `pub mod`. Les types d'instruction restent absents pour éviter d'avancer `pre.003`/`pre.004`.
## 5. Dependency firewall
Le manifest membre possède exactement :
```text
[dependencies]
ksp-core-lib = { path = "../ksp-core-lib" }
```
Le canari `tests/dependency_boundary.rs` vérifie notamment l'absence de :
```text
ksp-logging-lib
ksp-program-*
ksp-*-transport-lib
ksp-config-lib
ksp-store-*
ksp-wallet-lib
serde / serde_json
borsh / bincode / wincode
solana-instruction
reqwest / tokio / tonic / tauri / tracing
```
Il verrouille aussi l'absence de `src/constants.rs` et `TRACING_TARGET` tant que la crate reste passive.
## 6. Tests ajoutés
`tests/public_api.rs` vérifie que le `Pubkey` canonique est consommable uniquement depuis le crate-root Interface.
`tests/dependency_boundary.rs` vérifie :
```text
dépendance normale exacte = ksp-core-lib
absence des dépendances interdites
absence de ProgramAccountMeta/ProgramInstruction en pre.002
absence de surface Logging runtime
```
Aucun unit test métier n'est ajouté : aucune primitive fonctionnelle n'existe encore.
## 7. Fichiers ajoutés
```text
crates/ksp-interface-lib/Cargo.toml
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
crates/ksp-interface-lib/src/lib.rs
crates/ksp-interface-lib/tests/dependency_boundary.rs
crates/ksp-interface-lib/tests/public_api.rs
deltas/0.2.13/pre.002.md
```
## 8. Fichiers modifiés
```text
Cargo.toml
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
```
## 9. Fichiers volontairement inchangés
```text
README.md
ROADMAP.md
CHANGELOG.md
.env.example
docs/architecture/**
docs/rules/**
crates/ksp-core-lib/**
crates/ksp-logging-lib/**
crates/ksp-onchain-transport-lib/**
crates/ksp-offchain-transport-lib/**
crates/ksp-config-lib/**
crates/ksp-wallet-lib/**
crates/ksp-app-*/**
prompts/**
```
Aucune architecture durable n'est rouverte : le graphe `Interface -> Core`, avec Logging uniquement lorsqu'un runtime logging réel existe, est déjà documenté.
## 10. Validations de génération
Les audits Python sont rejoués sur l'arbre matérialisé avant livraison. Le sandbox de génération ne fournit pas `cargo`, `rustc` ou `rustfmt`; aucun PASS Cargo nouveau n'est donc 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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
cargo test --workspace
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
Le `cargo tree -p ksp-interface-lib --edges normal` doit montrer Core comme unique dépendance KSP directe et ne doit pas révéler de codec/runtime ajouté par Interface.
Une correction découverte par ce gate reste un `0.2.13-pre.002-fix.NNN` et n'avance pas `pre.003`.
## 12. Suite
Après gate vert, `pre.003` pourra matérialiser uniquement :
```text
ProgramAccountMeta
constante de borne account metas
modèle d'erreur Interface minimal nécessaire
unit/public canaries correspondants
```
`ProgramInstruction`, sa data bound et son Debug résumé restent réservés à `pre.004`.

272
deltas/0.2.13/pre.003.md Normal file
View File

@@ -0,0 +1,272 @@
<!-- file: deltas/0.2.13/pre.003.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.003` — `ProgramAccountMeta` + borne accounts
## 1. Base requise
Cette tranche s'applique exclusivement sur :
```text
v0.2.12
+ 0.2.13-pre.001
+ 0.2.13-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
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test --workspace PASS
cargo tree -p ksp-interface-lib --edges normal inspecté
cargo tree --duplicates inspecté
```
Le graphe ciblé confirme :
```text
ksp-interface-lib
└── ksp-core-lib
└── solana-pubkey 4.3.0
```
Aucune dépendance codec/runtime n'est introduite par Interface et les doublons workspace observés sont préexistants à cette crate.
La version workspace passe de :
```text
0.2.13-pre.2
```
à :
```text
0.2.13-pre.3
```
## 2. Objectif
Matérialiser uniquement la primitive passive représentant un account meta d'instruction Program, figer la borne publique du nombre d'account metas et introduire le code d'erreur Interface minimal nécessaire à la future admission de `ProgramInstruction`.
La tranche ajoute :
```text
ProgramAccountMeta
MAX_PROGRAM_INSTRUCTION_ACCOUNTS = 255
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
unit tests de la primitive
public API canaries correspondants
```
Elle n'ajoute pas encore :
```text
ProgramInstruction
MAX_PROGRAM_INSTRUCTION_DATA_LEN
enforcement de collection 255/256
data payload
Debug résumé d'instruction
serde / serde_json
borsh / bincode / wincode
solana-instruction
logging/runtime
Program behavior
```
## 3. `ProgramAccountMeta`
La primitive possède trois champs privés :
```text
Pubkey
is_signer
is_writable
```
Les constructeurs publics sont :
```text
ProgramAccountMeta::readonly(pubkey, is_signer)
ProgramAccountMeta::writable(pubkey, is_signer)
```
Les accessors publics sont :
```text
pubkey()
is_signer()
is_writable()
```
Le type reste strictement passif :
- aucune validation de registry Program n'est appliquée au `Pubkey` ;
- aucune policy signer/writable n'est inventée ;
- aucune sérialisation ou conversion vers `solana-instruction::AccountMeta` n'est ajoutée ;
- les champs privés empêchent une dérive future de représentation sans passer par l'API possédée par Interface.
## 4. Borne accounts
La façade expose :
```rust
pub const MAX_PROGRAM_INSTRUCTION_ACCOUNTS: usize = 255;
```
Cette constante fixe le plafond d'admission retenu par le plan `0.2.13`.
`pre.003` ne crée volontairement aucune collection artificielle autour de `ProgramAccountMeta` uniquement pour tester `255/256`. L'enforcement concret sera réalisé dans `ProgramInstruction::try_new` à `pre.004`, lorsque la collection existera réellement.
La borne data `10_240` reste également réservée à `pre.004` avec le payload qu'elle protège.
## 5. Modèle d'erreur minimal
Interface continue d'utiliser `ksp_core_lib::Error`, `ErrorCode` et `Result`; aucun second type d'erreur n'est introduit.
La seule catégorie matérialisée est :
```text
interface.program_instruction_limit_exceeded
```
via :
```text
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
```
Le code `invalid_program_instruction` n'est pas ajouté : aucun invariant de forme distinct des bornes n'existe encore dans la foundation. Le créer uniquement par symétrie serait prématuré.
À `pre.004`, les erreurs de borne devront limiter leur contexte aux longueurs et plafonds sûrs ; aucun payload ni compte arbitraire ne devra être copié dans les diagnostics.
## 6. Logging
La décision de `pre.002` reste inchangée. Interface est toujours une crate de contrats passifs et n'émet aucun événement/span runtime.
Restent donc absents :
```text
ksp-logging-lib
src/constants.rs
TRACING_TARGET
tracing direct
```
## 7. Tests
Les unit tests de `ProgramAccountMeta` couvrent :
```text
readonly -> identité/signature conservées, writable=false
writable -> identité/signature conservées, writable=true
Pubkey opaque/non registry accepté
MAX_PROGRAM_INSTRUCTION_ACCOUNTS == 255
```
`tests/public_api.rs` prouve depuis le crate-root :
```text
construction readonly/writable
accessors publics
borne accounts publique
code d'erreur stable et typé par ksp_core_lib::ErrorCode
```
`tests/dependency_boundary.rs` conserve le firewall `pre.002` et vérifie désormais que :
```text
ProgramAccountMeta est présent
MAX_PROGRAM_INSTRUCTION_ACCOUNTS est présent
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED est présent
ProgramInstruction reste absent
logging/runtime restent absents
```
## 8. Fichiers ajoutés
```text
crates/ksp-interface-lib/src/error.rs
crates/ksp-interface-lib/src/program_account_meta.rs
crates/ksp-interface-lib/unit_tests/program_account_meta.rs
deltas/0.2.13/pre.003.md
```
## 9. Fichiers modifiés
```text
Cargo.toml
crates/ksp-interface-lib/src/lib.rs
crates/ksp-interface-lib/tests/dependency_boundary.rs
crates/ksp-interface-lib/tests/public_api.rs
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
```
## 10. Fichiers volontairement inchangés
```text
README.md
ROADMAP.md
CHANGELOG.md
.env.example
crates/ksp-interface-lib/Cargo.toml
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
docs/architecture/**
docs/rules/**
crates/ksp-core-lib/**
crates/ksp-logging-lib/**
crates/ksp-onchain-transport-lib/**
crates/ksp-offchain-transport-lib/**
crates/ksp-config-lib/**
crates/ksp-wallet-lib/**
crates/ksp-app-*/**
prompts/**
```
README/USAGE restent dans leur lane de réconciliation finale `pre.007`; aucune documentation durable hors plan/validation n'est rouverte pour cette petite tranche fonctionnelle.
## 11. Validations de génération
Les audits Python sont rejoués sur l'arbre matérialisé avant livraison. Le sandbox de génération ne fournit pas `cargo`, `rustc` ou `rustfmt`; aucun nouveau PASS Cargo n'est donc revendiqué localement.
## 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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
cargo test --workspace
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
Une correction découverte par ce gate reste un `0.2.13-pre.003-fix.NNN` et n'avance pas `pre.004`.
## 13. Suite
Après gate vert, `pre.004` pourra matérialiser uniquement :
```text
ProgramInstruction { program_id, accounts, data }
MAX_PROGRAM_INSTRUCTION_DATA_LEN = 10_240
try_new borné
accessors
Debug résumé sans payload
acceptation 0/255 accounts et 0/10_240 data
rejet 256 accounts et 10_241 data
ordre et doublons conservés
contextes d'erreur sûrs
```
Aucun serde/codec ou comportement Program ne doit être ajouté dans cette tranche suivante.

295
deltas/0.2.13/pre.004.md Normal file
View File

@@ -0,0 +1,295 @@
<!-- file: deltas/0.2.13/pre.004.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.004` — `ProgramInstruction` passif borné
## 1. Base requise
Cette tranche s'applique exclusivement sur :
```text
v0.2.12
+ 0.2.13-pre.001
+ 0.2.13-pre.002
+ 0.2.13-pre.003
```
La preuve opérateur fournie pour `pre.003` confirme :
```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-interface-lib PASS
cargo tree -p ksp-interface-lib --edges normal inspecté
cargo tree --duplicates inspecté
```
`cargo test --workspace` n'apparaît pas dans cette preuve opérateur. La présente préparation n'en invente donc pas le résultat ; ce test devra être rejoué avec le gate de `pre.004` avant de déclarer la continuité workspace complète.
Le graphe ciblé reste strictement :
```text
ksp-interface-lib
└── ksp-core-lib
└── solana-pubkey 4.3.0
```
La version workspace passe de :
```text
0.2.13-pre.3
```
à :
```text
0.2.13-pre.4
```
## 2. Objectif
Matérialiser le second type du premier lot wire générique décidé en `pre.001` : une instruction Program passive `{ program_id, accounts, data }`, bornée à l'admission et sans comportement Program.
La tranche ajoute :
```text
ProgramInstruction
MAX_PROGRAM_INSTRUCTION_DATA_LEN = 10_240
ProgramInstruction::try_new(...)
accessors program_id/accounts/data
Debug résumé
unit/public canaries de limites, ordre et doublons
```
Elle n'ajoute pas :
```text
serde / serde_json
borsh / bincode / wincode
solana-instruction
codec ou discriminant Program spécifique
Program API / Program behavior
Transport / Store / Config / Wallet
ksp-logging-lib / constants.rs / TRACING_TARGET
runtime réseau
```
## 3. `ProgramInstruction`
Le type possède trois champs privés :
```text
program_id : Pubkey
accounts : Vec<ProgramAccountMeta>
data : Vec<u8>
```
Le constructeur public est :
```text
ProgramInstruction::try_new(program_id, accounts, data)
-> ksp_core_lib::Result<ProgramInstruction>
```
Les accessors publics sont :
```text
program_id() -> &Pubkey
accounts() -> &[ProgramAccountMeta]
data() -> &[u8]
```
La construction consomme directement les `Vec` fournis. Aucun clone interne, conversion vers `solana-instruction` ou sérialisation n'est ajouté.
## 4. Bornes d'admission
Les deux plafonds sont désormais matérialisés :
```text
MAX_PROGRAM_INSTRUCTION_ACCOUNTS = 255
MAX_PROGRAM_INSTRUCTION_DATA_LEN = 10_240
```
`try_new` vérifie en `usize` :
```text
accounts.len() <= 255
data.len() <= 10_240
```
Canaris exacts :
```text
255 accounts accepté
256 accounts refusé
10_240 octets data accepté
10_241 octets data refusé
```
Ces valeurs restent des bornes d'admission Interface fondées sur les plafonds Solana CPI audités en `pre.001`. Elles ne garantissent pas qu'une instruction rentre dans une transaction top-level complète.
## 5. Sémantique passive
Restent valides :
```text
accounts vides
data vide
Program Pubkey inconnu du registry Core
doublons d'accounts
ordre arbitraire des account metas
payload opaque quelconque sous la borne
```
Interface n'ajoute aucune politique signer/writable, aucun lookup de Program ID, aucune déduplication et aucune interprétation du payload.
## 6. Erreurs
Les deux violations de borne réutilisent :
```text
interface.program_instruction_limit_exceeded
```
via `ksp_core_lib::Error/Result`.
Le contexte d'erreur est limité à :
```text
field
actual_len
maximum_len
```
Aucun octet `data`, aucun account arbitraire et aucun payload externe n'est copié dans l'erreur.
Le code conceptuel `invalid_program_instruction` reste absent : la foundation ne possède toujours aucun invariant de forme distinct des deux limites.
## 7. Debug borné
`ProgramInstruction` n'utilise pas un `derive(Debug)` qui imprimerait les deux `Vec`.
Son `Debug` manuel expose uniquement :
```text
program_id
account_count
data_len
```
Les account metas et le payload sont volontairement absents du rendu.
## 8. Logging
La décision de `pre.002` reste inchangée : cette crate est passive.
Restent absents :
```text
ksp-logging-lib
src/constants.rs
TRACING_TARGET
tracing direct
```
Une erreur de validation de borne est une valeur retournée au caller, pas un événement runtime à logger depuis Interface.
## 9. Tests
Les unit tests de `ProgramInstruction` couvrent :
```text
accounts/data vides acceptés
Program Pubkey opaque accepté
ordre et doublons préservés
payload opaque préservé
255 / 256 accounts
10_240 / 10_241 octets data
code/message/contexte d'erreur sûr
Debug résumé sans payload ni account flags
```
`tests/public_api.rs` ajoute un canari de consommation crate-root de `ProgramInstruction` et de `MAX_PROGRAM_INSTRUCTION_DATA_LEN`.
`tests/dependency_boundary.rs` est avancé à `pre.004` et confirme toujours :
```text
ksp-core-lib seule dépendance normale
aucun serde/borsh/bincode/wincode
aucun solana-instruction
aucun logging/runtime
ProgramInstruction présent via façade explicite
```
## 10. Fichiers ajoutés
```text
crates/ksp-interface-lib/src/program_instruction.rs
crates/ksp-interface-lib/unit_tests/program_instruction.rs
deltas/0.2.13/pre.004.md
```
## 11. Fichiers modifiés
```text
Cargo.toml
crates/ksp-interface-lib/src/lib.rs
crates/ksp-interface-lib/tests/dependency_boundary.rs
crates/ksp-interface-lib/tests/public_api.rs
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
```
## 12. Fichiers volontairement inchangés
```text
README.md
ROADMAP.md
CHANGELOG.md
.env.example
crates/ksp-interface-lib/Cargo.toml
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
crates/ksp-interface-lib/src/error.rs
crates/ksp-interface-lib/src/program_account_meta.rs
crates/ksp-interface-lib/unit_tests/program_account_meta.rs
docs/architecture/**
prompts/**
config/**
```
## 13. Gate attendu
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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
cargo test --workspace
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
Le `cargo test --workspace` est particulièrement requis ici puisqu'il n'était pas présent dans la preuve opérateur fournie pour `pre.003`.
## 14. Suite autorisée
Après gate vert, `pre.005` doit rester une tranche de hardening :
```text
cas adversariaux complémentaires
consumer externe
surface crate-root
firewall source/manifest
graphes Cargo
canari de complétude du premier lot
```
Aucun second domaine wire, codec générique ou comportement Program ne doit être ajouté opportunément.

View File

@@ -0,0 +1,137 @@
<!-- file: deltas/0.2.13/pre.005-fix.001.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.005-fix.001` — faux positif du scanner Logging
## 1. Déclencheur
Le gate opérateur de `pre.005` confirme :
```text
cargo fmt --all PASS
python3 scripts/audit_rust_workspace_rules.py PASS / clean
python3 scripts/audit_markdown_tables.py ... PASS / clean
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test --workspace FAIL
cargo tree -p ksp-interface-lib --edges normal Core-only confirmé
cargo tree --duplicates fourni
```
Le test ciblé Interface est entièrement vert :
```text
unit tests 10/10
manifest/source firewall 4/4
external consumer 1/1
public API 5/5
release completeness 3/3
```
Le workspace échoue uniquement ici :
```text
ksp-logging-lib/tests/ownership.rs
workspace_crates_do_not_bypass_ksp_logging_facade
```
avec :
```text
crates/ksp-interface-lib/tests/dependency_boundary.rs bypasses ksp-logging-lib via tracing
```
## 2. Cause exacte
Le scanner Logging recherche le chemin littéral :
```text
tracing::
```
sur le texte brut de tous les fichiers Rust des crates KSP, tests compris.
Le hardening `pre.005` a ajouté ce même littéral dans `tests/dependency_boundary.rs` comme **pattern interdit à rechercher dans les sources de production**. Il ne s'agit donc ni d'un import, ni d'un appel runtime, ni d'une dépendance Interface vers Tracing : le scanner transversal détecte simplement la donnée de son canari voisin.
Le graphe Cargo opérateur confirme toujours :
```text
ksp-interface-lib
└── ksp-core-lib
└── solana-pubkey 4.3.0
```
## 3. Correction
Le firewall Interface conserve strictement la même vérification, mais le token est écrit sans matérialiser `tracing::` contigu dans le source du test :
```rust
concat!("tracing", "::")
```
La valeur produite à la compilation reste exactement `tracing::`. Le test Interface continue donc d'échouer si un module de production utilise directement ce chemin.
Aucun `allow`, aucune exclusion de fichier et aucun affaiblissement du scanner Logging ne sont introduits.
## 4. Version
Le fix modifie un test Rust ; la version workspace devient :
```text
0.2.13-pre.5.fix.1
```
Commit attendu après validation :
```text
v0.2.13-pre.005-fix.001
```
Aucun tag prerelease.
## 5. Frontières
Inchangés :
```text
crates/ksp-interface-lib/src/**
crates/ksp-interface-lib/Cargo.toml
surface publique Interface
ProgramAccountMeta
ProgramInstruction
bornes 255 / 10_240
Error/Result Core
ksp-logging-lib absent d'Interface
constants.rs absent
TRACING_TARGET absent
serde/borsh/bincode/wincode absents
solana-instruction absent
```
## 6. Fichiers modifiés
```text
Cargo.toml
crates/ksp-interface-lib/tests/dependency_boundary.rs
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
deltas/0.2.13/pre.005-fix.001.md
```
## 7. 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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
cargo test -p ksp-logging-lib --test ownership
cargo test --workspace
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
`pre.006` reste interdit tant que ce gate n'est pas intégralement vert.

213
deltas/0.2.13/pre.005.md Normal file
View File

@@ -0,0 +1,213 @@
<!-- file: deltas/0.2.13/pre.005.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.005` — hardening adversarial + consumer externe + complétude
## 1. Base requise
Cette tranche s'applique exclusivement sur :
```text
v0.2.12
+ 0.2.13-pre.001
+ 0.2.13-pre.002
+ 0.2.13-pre.003
+ 0.2.13-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
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test --workspace PASS
cargo tree -p ksp-interface-lib --edges normal inspecté
cargo tree --duplicates inspecté
```
Le graphe ciblé reste strictement :
```text
ksp-interface-lib
└── ksp-core-lib
└── solana-pubkey 4.3.0
```
La version workspace passe de :
```text
0.2.13-pre.4
```
à :
```text
0.2.13-pre.5
```
## 2. Objectif
Fermer le hardening du premier lot wire générique sans ajouter de capacité fonctionnelle ni de second domaine wire.
La tranche ajoute uniquement des preuves :
```text
consumer externe crate-root
inventaire exact de complétude release
firewall source/manifest renforcé
absence de narrowing/codec caché
transfert sans réallocation des Vec admis
absence de contenu hostile dans les erreurs de limite
public API boundary canary
```
Aucun fichier `src/` de production n'est modifié.
## 3. Consumer externe
`tests/external_consumer.rs` constitue le canari downstream. Comme tout integration test Rust, il est compilé comme un crate distinct de la bibliothèque et utilise exclusivement :
```text
ksp_interface_lib::Pubkey
ksp_interface_lib::ProgramAccountMeta
ksp_interface_lib::ProgramInstruction
ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS
ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN
ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
```
Aucun chemin vers un module privé n'est requis.
## 4. Complétude de release
`tests/release_completeness.rs` verrouille exactement les six exports crate-root retenus pour `0.2.13` et refuse une façade `pub mod`.
Il verrouille également l'inventaire des fichiers Rust de production :
```text
error.rs
lib.rs
program_account_meta.rs
program_instruction.rs
```
Ce canari rend explicite qu'un second lot wire, un codec générique, un runtime ou un nouveau domaine ne peut pas être ajouté silencieusement dans cette release.
## 5. Hardening adversarial
Les unit tests de `ProgramInstruction` sont renforcés sur deux propriétés.
Premièrement, les pointeurs des `Vec<ProgramAccountMeta>` et `Vec<u8>` sont capturés avant `try_new` puis comparés aux slices exposés après construction. Le canari prouve que l'implémentation courante transfère les allocations admises sans clone/réallocation interne.
Deuxièmement, les deux branches d'erreur de limite sont confrontées à du matériel hostile : payload avec marqueur explicite et account meta arbitraire. Les rendus/contextes d'erreur doivent rester bornés aux champs sûrs déjà définis :
```text
field
actual_len
maximum_len
```
Aucun payload ni account meta n'est recopié dans l'erreur.
## 6. Dependency/API firewall
`tests/dependency_boundary.rs` conserve l'exigence manifeste :
```text
ksp-core-lib = seule dépendance normale
```
et étend le scan à tous les fichiers Rust de production pour interdire les chemins directs vers :
```text
Program/Store/Transport/Config/Wallet
serde/serde_json
borsh/bincode/wincode
solana-instruction
reqwest/tokio/tonic/tauri
tracing/ksp-logging-lib
```
Un canari dédié refuse également les casts étroits et les points d'entrée génériques `serialize/deserialize/encode/decode` dans `program_instruction.rs`.
## 7. Public API
`tests/public_api.rs` ajoute un test de rejet `10_241` bytes depuis un consumer externe au module privé et vérifie que le caller reçoit le code Core commun ainsi que les trois contextes sûrs attendus.
Aucune nouvelle API publique n'est créée par `pre.005`.
## 8. Logging et codecs
La décision reste inchangée : Interface est passive.
Toujours absents :
```text
ksp-logging-lib
constants.rs
TRACING_TARGET
tracing direct
serde
borsh
bincode
wincode
solana-instruction
```
## 9. Fichiers ajoutés
```text
crates/ksp-interface-lib/tests/external_consumer.rs
crates/ksp-interface-lib/tests/release_completeness.rs
deltas/0.2.13/pre.005.md
```
## 10. Fichiers modifiés
```text
Cargo.toml
crates/ksp-interface-lib/tests/dependency_boundary.rs
crates/ksp-interface-lib/tests/public_api.rs
crates/ksp-interface-lib/unit_tests/program_instruction.rs
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
```
## 11. Fichiers volontairement inchangés
```text
README.md
ROADMAP.md
CHANGELOG.md
.env.example
crates/ksp-interface-lib/Cargo.toml
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
crates/ksp-interface-lib/src/**
crates/ksp-interface-lib/unit_tests/program_account_meta.rs
docs/architecture/**
prompts/**
config/**
```
## 12. Gate attendu
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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
cargo test --workspace
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
Si ce gate est vert, `pre.006` doit rester un gate technique final sans nouveau développement fonctionnel.

141
deltas/0.2.13/pre.006.md Normal file
View File

@@ -0,0 +1,141 @@
<!-- file: deltas/0.2.13/pre.006.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.006` — gate technique final Interface
## 1. Base requise
Cette tranche s'applique exclusivement après `0.2.13-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
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test -p ksp-logging-lib --test ownership PASS
cargo test --workspace PASS
cargo tree -p ksp-interface-lib --edges normal Core-only confirmé
cargo tree --duplicates inspecté
```
Le faux positif `tracing::` 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.13` sans rouvrir la surface fonctionnelle.
Aucun nouveau type wire, aucun codec, aucun test métier, aucune dépendance et aucun comportement runtime ne sont ajoutés. Les canaris existants suffisent déjà à vérifier :
```text
ProgramAccountMeta
ProgramInstruction
bornes 255 / 10_240
ordre et doublons
Pubkey opaque
Debug borné
erreurs sans payload hostile
consumer externe
façade crate-root
inventaire exact de production
firewall source / manifest
Core-only dependency graph
```
## 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.13-pre.5.fix.1
0.2.13-pre.6
```
Commit attendu après validation :
```text
v0.2.13-pre.006
```
Aucun tag prerelease.
## 5. Fichiers ajoutés
```text
deltas/0.2.13/pre.006.md
```
## 6. Fichiers modifiés
```text
Cargo.toml
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
```
## 7. Fichiers supprimés
Aucun.
## 8. Surface explicitement inchangée
```text
crates/ksp-interface-lib/src/**
crates/ksp-interface-lib/tests/**
crates/ksp-interface-lib/unit_tests/**
crates/ksp-interface-lib/Cargo.toml
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/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 (162 tables, 119 files)
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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
cargo test -p ksp-logging-lib --test ownership
cargo test --workspace
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
## 11. Décisions prises
- le scope fonctionnel `0.2.13` est figé ;
- aucun codec générique n'est introduit ;
- Interface reste passive et sans logging runtime ;
- `ksp-core-lib` reste l'unique dépendance normale directe ;
- aucun smoke réseau n'est pertinent pour cette crate wire pure ;
- `pre.007` ne commence qu'après gate `pre.006` intégralement vert.
## 12. Questions ouvertes
Aucune question technique bloquante pour `0.2.13`. La seule étape suivante après gate vert est la réconciliation documentaire finale.

192
deltas/0.2.13/pre.007.md Normal file
View File

@@ -0,0 +1,192 @@
<!-- file: deltas/0.2.13/pre.007.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.007` — réconciliation documentaire finale Interface
## 1. Base
```text
0.2.13-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 — 162 tables / 119 fichiers
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test -p ksp-logging-lib --test ownership PASS
cargo test --workspace PASS
cargo tree -p ksp-interface-lib --edges normal Core-only confirmé
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.13`. Aucun fichier Rust, test, manifest de crate, dépendance, codec ou comportement n'est modifié.
La tranche transforme les README/USAGE initiaux d'Interface 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.13-pre.6
->
0.2.13-pre.7
```
Le bump est mécanique pour une prerelease non-fix. Aucun autre manifest/version applicative n'est synchronisé artificiellement.
## 4. Surface finale candidate documentée
```text
Pubkey réexport Core
ProgramAccountMeta passif, champs privés
ProgramInstruction passif, champs privés
MAX_PROGRAM_INSTRUCTION_ACCOUNTS 255
MAX_PROGRAM_INSTRUCTION_DATA_LEN 10_240
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED interface.program_instruction_limit_exceeded
```
Les propriétés réconciliées sont :
```text
accounts ordonnés préservés
doublons accounts préservés
accounts/data vides acceptés
Program Pubkey hors registry accepté
Vec accounts/data consommés sans reformatage interne
Debug ProgramInstruction program_id/account_count/data_len uniquement
erreur de borne field/actual_len/maximum_len uniquement
```
Les limites `255` et `10_240` restent explicitement des bornes d'admission Interface et non une garantie complète de fit transaction Solana top-level.
## 5. Dependency firewall final
Le graphe normal confirmé par l'opérateur reste :
```text
ksp-interface-lib
└── ksp-core-lib
└── solana-pubkey
```
La foundation ne dépend pas de :
```text
serde / serde_json
borsh / wincode / bincode
solana-instruction
Transport / Config / Store / Wallet / Program
reqwest / tonic / tokio / Tauri
ksp-logging-lib / tracing runtime
```
L'absence de runtime logging est volontaire : la surface actuelle est purement passive.
## 6. Références durables réconciliées
```text
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
docs/000-README.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/000-README.md
docs/validation/016-V0_2_13_INTERFACE.md
```
Le README Interface fixe l'ownership, la surface publique, les bornes, le modèle d'erreur, le Debug borné, le graphe et les frontières de responsabilité.
`USAGE.md` documente l'usage crate-root de `ProgramAccountMeta` et `ProgramInstruction`, les accessors, les limites et l'observation des erreurs sans accès aux modules privés.
La séquence fonctionnelle enregistre le contenu effectivement livré par `0.2.13`, tout en maintenant :
```text
0.2.14 Program API foundation
0.3.2+ wires génériques acquisition/CORE
```
## 7. Fichiers ajoutés/modifiés
Ajouté :
```text
deltas/0.2.13/pre.007.md
```
Modifiés :
```text
Cargo.toml
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
docs/000-README.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/000-README.md
docs/validation/016-V0_2_13_INTERFACE.md
```
Aucun fichier supprimé.
## 8. Surfaces volontairement inchangées
Cette tranche ne touche pas :
```text
crates/ksp-interface-lib/src/**
crates/ksp-interface-lib/tests/**
crates/ksp-interface-lib/unit_tests/**
crates/ksp-interface-lib/Cargo.toml
docs/architecture/**
CHANGELOG.md
ROADMAP.md
prompts/**
```
Les architectures existantes sont déjà compatibles avec la foundation réellement livrée et ne nécessitent aucune réécriture de clôture.
## 9. 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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
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`.
## 10. Suite
Si le gate est vert :
```text
0.2.13-pre.008 — préparation minimale de publication
```
`pre.008` reste limitée à :
```text
Cargo.toml
CHANGELOG.md
ROADMAP.md
prompts/019-V0_2_14_START_PROMPT.md
deltas/0.2.13/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.

233
deltas/0.2.13/pre.008.md Normal file
View File

@@ -0,0 +1,233 @@
<!-- file: deltas/0.2.13/pre.008.md -->
<!-- version: 1 -->
# Delta `0.2.13-pre.008` — préparation minimale de publication
## 1. Base
```text
0.2.13-pre.007
```
Le gate opérateur de `pre.007`, 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 — 164 tables / 120 fichiers
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test --workspace PASS
```
La réconciliation documentaire finale est donc fermée avant cette tranche.
## 2. Objectif
`pre.008` est exclusivement la lane minimale de préparation de publication de `0.2.13` imposée par `VER-LIFECYCLE-003` et `PROMPT_STRUCTURE.md`.
Elle ne rouvre aucun code, test, README/USAGE, plan, validation, architecture, règle ou configuration.
Elle :
```text
bump la prerelease Cargo vers pre.8
prépare l'entrée CHANGELOG stable 0.2.13
synchronise le statut global ROADMAP
ajoute le prompt autonome 0.2.14
trace cette préparation dans le delta
```
## 3. Version technique
```text
0.2.13-pre.7
->
0.2.13-pre.8
```
Le bump est mécanique pour une prerelease non-fix.
## 4. Changelog / Roadmap
`CHANGELOG.md` prépare l'entrée stable :
```text
0.2.13 — Interface / wire foundation — 2026-08-28
```
Le résumé conserve les faits de clôture réellement prouvés :
```text
ProgramAccountMeta + ProgramInstruction passifs
255 account metas / 10_240 bytes
Error/Debug sans payload hostile
graphe Interface -> Core uniquement
aucun serde/codec générique/solana-instruction/network/logging
canaris public API / external consumer / dependency firewall / completeness
pre.005-fix.001 limité au faux positif scanner Logging
gates techniques et documentaires finaux verts
```
`ROADMAP.md` marque `0.2.13` comme terminé et maintient `0.2.14` comme prochaine release active.
## 5. Prompt `0.2.14`
Ajout :
```text
prompts/019-V0_2_14_START_PROMPT.md
```
Le prompt ouvre :
```text
0.2.14 — Program API foundation
```
exclusivement depuis :
```text
v0.2.13
```
Le démarrage exige explicitement **deux archives** :
```text
archive opérateur KSP v0.2.13
khadhroony-bot3_v0.5.3-pre.005-fix010.zip
```
Ordre d'autorité fixé :
```text
v0.2.13 + règles/architecture KSP autorité actuelle
kbot3 historique audit/héritage uniquement
```
L'archive kbot3 est obligatoire pour le gate `pre.001` afin d'auditer réellement les anciens contrats :
```text
ks-lib/src/decoder/api/contracts.rs
ks-lib/src/decoder/api/decoder.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
```
Le prompt impose la matrice :
```text
REPRENDRE
REDESSINER
REPORTER
REJETER
```
et protège notamment les frontières :
```text
Core = Pubkey / Program IDs / Error
Interface = wire passif
Program API = comportements/contracts extensibles
Program Lib = implémentations officielles futures
CORE futur = replay/context transaction/CPI/logs/return data/balance deltas
Execution futur = policy/wallet/network/simulation/send/confirmation
```
La prévision souple initiale réserve distinctement audit/sizing, scaffold, contracts, hardening, gate technique, réconciliation documentaire et préparation de publication.
## 6. Fichiers ajoutés/modifiés
Ajoutés :
```text
prompts/019-V0_2_14_START_PROMPT.md
deltas/0.2.13/pre.008.md
```
Modifiés :
```text
Cargo.toml
CHANGELOG.md
ROADMAP.md
```
Aucun fichier supprimé.
## 7. Surfaces volontairement inchangées
Cette tranche ne touche pas :
```text
README.md
RULES.md
.env.example
config/**
crates/**
docs/**
prompts/001..018
```
En particulier :
```text
crates/ksp-interface-lib/src/**
crates/ksp-interface-lib/tests/**
crates/ksp-interface-lib/unit_tests/**
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/016-V0_2_13_INTERFACE.md
docs/architecture/**
```
## 8. Validations de préparation
À exécuter dans l'environnement de préparation :
```text
audit Rust workspace
audit Markdown
contrôle exact du payload overlay
contrôle des versions/file headers
```
Aucun résultat Cargo nouveau n'est revendiqué tant qu'il n'a pas été exécuté par l'opérateur sur `pre.008`.
## 9. 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.13
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-interface-lib
cargo test --workspace
```
Aucun smoke réseau/live ni nouveau graphe Cargo n'est requis puisque cette lane ne modifie ni code ni dépendances.
## 10. Suite
Si le gate est vert :
```text
0.2.13-rel.001
```
La release finale effectue uniquement la mécanique de publication stable :
```text
workspace.package.version = 0.2.13
delta rel.001
commit de publication
tag stable v0.2.13 après validation opérateur
```
`rel.001` ne doit contenir aucun rattrapage fonctionnel ou documentaire.

285
deltas/0.2.13/rel.001.md Normal file
View File

@@ -0,0 +1,285 @@
<!-- file: deltas/0.2.13/rel.001.md -->
<!-- version: 1 -->
# Delta `0.2.13-rel.001` — publication stable Interface / wire foundation
## 1. Base requise
Base directe attendue :
```text
0.2.13-pre.008
workspace.package.version = 0.2.13-pre.8
```
Commit attendu pour cette livraison :
```text
v0.2.13-rel.001
```
Tag stable attendu après validation :
```text
v0.2.13
```
## 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.13-pre.8` à `0.2.13` ;
- 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.13-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
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test -p ksp-logging-lib --test ownership PASS
cargo test --workspace PASS
cargo tree -p ksp-interface-lib --edges normal PASS / inspecté
cargo tree --duplicates PASS / inspecté
```
Le graphe ciblé est resté strictement :
```text
ksp-interface-lib
└── ksp-core-lib
└── solana-pubkey
```
Le gate opérateur de la réconciliation documentaire `0.2.13-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 — 164 tables / 120 fichiers
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test --workspace PASS
```
Enfin, le gate opérateur de la préparation minimale de publication `0.2.13-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 — 164 tables / 122 fichiers
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib 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.
## 4. Version stable publiée
La version Cargo devient :
```text
workspace.package.version = 0.2.13
```
La release stable publiée est :
```text
0.2.13 — Interface / wire foundation
```
La surface stable comprend :
```text
Pubkey canonique réexporté depuis ksp-core-lib
ProgramAccountMeta passif à champs privés
ProgramInstruction passif à champs privés
MAX_PROGRAM_INSTRUCTION_ACCOUNTS = 255
MAX_PROGRAM_INSTRUCTION_DATA_LEN = 10_240
ordre et doublons des account metas conservés
Program Pubkeys opaques acceptés
Error/Result Core pour les violations de bornes
contextes d'erreur limités à field / actual_len / maximum_len
Debug borné sans account material ni payload data
six exports crate-root exacts
quatre modules Rust de production exacts
consumer externe canari
firewall de dépendances canari
release completeness canari
```
La dependency direction stable reste :
```text
ksp-interface-lib
-> ksp-core-lib
-> solana-pubkey
```
Sont volontairement absents de cette foundation :
```text
serde / serde_json
borsh / wincode / bincode
solana-instruction
ksp-logging-lib / tracing runtime Interface
reqwest / tonic / tokio / réseau
Program API / Program Lib
Store / Config / Wallet / Tauri
```
## 5. Fichiers ajoutés
```text
deltas/0.2.13/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.13
Cargo.toml header version 312
payload rel.001 2 fichiers exactement
CHANGELOG.md non rouvert
ROADMAP.md non rouvert
prompt 019 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-interface-lib
cargo test --workspace
cargo tree -p ksp-interface-lib --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.13
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.13-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/019-V0_2_14_START_PROMPT.md` restent inchangés dans `rel.001` ;
4. `ksp-interface-lib` reste une façade wire passive et Core-only ;
5. `ksp-program-api` reste réservé à `0.2.14` et ne doit pas être rétro-injecté dans Interface ;
6. l'archive historique `khadhroony-bot3_v0.5.3-pre.005-fix010.zip` reste obligatoire pour l'audit `0.2.14-pre.001`, mais n'est pas une autorité architecturale ;
7. la prochaine release s'ouvre exclusivement depuis le tag stable `v0.2.13`.
## 12. Questions ouvertes
Aucune question ouverte ne bloque la publication de `0.2.13`.
Les choix exacts concernant les contrats extensibles de `ksp-program-api`, les outcomes/proofs et l'héritage éventuel de concepts kbot3 appartiennent à l'audit/sizing de `0.2.14-pre.001` et ne constituent pas une dette bloquante de `0.2.13`.
## 13. Commit et tag stable
Après succès du gate stable :
```text
commit : v0.2.13-rel.001
tag : v0.2.13
```
Aucun tag intermédiaire `rel.001` n'est requis.
Commandes Git typiques :
```bash
git add Cargo.toml deltas/0.2.13/rel.001.md
git commit -m "v0.2.13-rel.001"
git tag v0.2.13
git push
git push origin v0.2.13
```
Après le tag, produire l'archive opérateur stable KSP `v0.2.13` qui servira de première autorité à la session suivante.
## 14. Suite
Après publication du tag stable `v0.2.13`, ouvrir :
```text
0.2.14-pre.001 — Program API foundation
```
avec :
```text
prompts/019-V0_2_14_START_PROMPT.md
```
Le démarrage de `0.2.14-pre.001` exige les deux archives prévues par le prompt :
```text
archive opérateur KSP correspondant exactement à v0.2.13
khadhroony-bot3_v0.5.3-pre.005-fix010.zip
```
L'audit kbot3 doit être réellement rejoué avant toute implémentation lourde de `ksp-program-api`.

201
deltas/0.2.14/pre.001.md Normal file
View 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
View 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
View 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
View 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`.

View 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
View 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
View 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
View 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
View 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
View 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.

View 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.

View 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
View 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
View 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.

View 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
View 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
View 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
View File

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

View File

@@ -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
View 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.

View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -1,5 +1,5 @@
<!-- file: docs/IDEAS.md -->
<!-- version: 25 -->
<!-- version: 26 -->
# 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
**Status :** À explorer avec la première release Store
**Status :** À explorer à partir de `0.3.2`
La structure durable D1D4 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
@@ -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
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
@@ -318,9 +318,9 @@ L'ancienne liste figée `ksp-job-replay-core` / `ksp-job-replay-generic-material
### 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

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/003-COMPONENT_CONTRACTS.md -->
<!-- version: 9 -->
<!-- version: 11 -->
# Contrats initiaux des composants KSP
@@ -16,7 +16,7 @@ Couples explicitement retenus :
```text
ksp-program-api / ksp-program-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 :
@@ -27,6 +27,8 @@ ksp-job-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.
## 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.
`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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
<!-- version: 23 -->
<!-- version: 26 -->
# Inventaire initial des composants KSP
@@ -17,42 +17,43 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
## Inventaire synthétique
| 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 |
| 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 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 |
| 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 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 |
| 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 |
| 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 |
| Wire | `ksp-interface-lib` | lib | Retenu | `0.2.13` | façade wire officielle + API publique wire |
| Program API | `ksp-program-api` | API | Retenu | `0.2.14` | contrats extensibles Program |
| 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` |
| Store API | `ksp-store-api` | API | Retenu | `0.3.1` | contrats persistence backend-agnostic, RAW d'abord |
| Store PostgreSQL | `ksp-store-lib` | lib | Retenu | `0.3.1` | backend PostgreSQL officiel, RAW d'abord |
| Job lifecycle | `ksp-job-api` | API | Retenu | `0.3.3` | lifecycle des jobs terminables |
| Backfill | `ksp-job-backfill` | job/lib à préciser | Retenu | `0.3.3` | acquisition historique vers RAW |
| Backfill Desk | nom à fixer | app | Retenu | `0.3.4` | contrôle/inspection du backfill RAW |
| Worker lifecycle | `ksp-worker-api` | API | Retenu | fin couche RAW | lifecycle des services continus |
| RAW worker | `ksp-worker-raw-retriever` ou nom révisé | worker | Retenu | fin couche RAW | acquisition live vers RAW |
| CORE processor | nom à fixer | processor/lib | Retenu | couche CORE | normalisation Solana générique RAW -> CORE |
| CORE worker | nom à fixer | worker | Retenu | fin couche CORE | backlog RAW -> CORE continu |
| Materializer API | `ksp-materializer-api` | API | Retenu | premier groupe DECODE | contrats extensibles matérialisation |
| Materializer impl. | `ksp-materializer-lib` | lib | Retenu | premier groupe DECODE | implementations officielles communes |
| Execution policy | `ksp-execution-policy-api` | API | Retenu | premier vrai besoin execution | décision/safety multi-contexte |
| Execution orchestration | `ksp-execution-lib` | lib | Retenu | premier vrai cycle execution | Program + policy + Wallet + transport |
| Scenarios | `ksp-scenario-<domain>-lib` | lib | Retenu | vertical slices | validation métier/devnet par groupe |
| Scenario API | `ksp-scenario-api` | API | Non retenu | — | norme souple avant trait commun |
| 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 |
| 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 |
| 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 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 |
| 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 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 |
| 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 |
| 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 |
| Wire | `ksp-interface-lib` | lib | Stable | `0.2.13` | façade wire officielle + API publique wire |
| 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 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` | RAW transaction/account, observations, queries, outcomes, rétention et capabilities backend |
| Store runtime | `ksp-store-lib` | lib | Retenu | `0.3.2``0.3.4` | fondation puis conformance RAW par slices, dispatch features/config |
| Store PostgreSQL | `ksp-store-postgres-lib` | lib | Retenu | `0.3.2``0.3.4` | fondation, RawTransaction puis RawAccountState/complétude |
| Job lifecycle | `ksp-job-api` | API | Retenu | `0.3.6` | lifecycle des jobs terminables |
| Backfill | `ksp-job-backfill` | job/lib à préciser | Retenu | `0.3.6` | acquisition historique vers RAW via `ksp-store-lib` |
| Backfill Desk | nom à fixer | app | Retenu | `0.3.7` | contrôle/inspection du backfill RAW |
| Worker lifecycle | `ksp-worker-api` | API | Retenu | fin couche RAW | lifecycle des services continus |
| RAW worker | `ksp-worker-raw-retriever` ou nom révisé | worker | Retenu | fin couche RAW | acquisition live vers RAW |
| CORE processor | nom à fixer | processor/lib | Retenu | couche CORE | normalisation Solana générique RAW -> CORE |
| CORE worker | nom à fixer | worker | Retenu | fin couche CORE | backlog RAW -> CORE continu |
| Materializer API | `ksp-materializer-api` | API | Retenu | premier groupe DECODE | contrats extensibles matérialisation |
| Materializer impl. | `ksp-materializer-lib` | lib | Retenu | premier groupe DECODE | implementations officielles communes |
| Execution policy | `ksp-execution-policy-api` | API | Retenu | premier vrai besoin execution | décision/safety multi-contexte |
| Execution orchestration | `ksp-execution-lib` | lib | Retenu | premier vrai cycle execution | Program + policy + Wallet + transport |
| Scenarios | `ksp-scenario-<domain>-lib` | lib | Retenu | vertical slices | validation métier/devnet par groupe |
| Scenario API | `ksp-scenario-api` | API | Non retenu | — | norme souple avant trait commun |
| 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

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
<!-- version: 15 -->
<!-- version: 17 -->
# Graphe de dépendances KSP
@@ -336,6 +336,12 @@ ksp-store-api
-> ksp-core-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-core-lib
-> ksp-logging-lib
@@ -347,9 +353,11 @@ Interdictions :
```text
ksp-store-api -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
@@ -364,7 +372,7 @@ Premier job pressenti :
ksp-job-backfill
-> ksp-job-api
-> ksp-onchain-transport-lib
-> ksp-store-api
-> ksp-store-lib
-> ksp-config-lib
-> ksp-logging-lib
```
@@ -426,7 +434,7 @@ backfill app
```text
ksp-app-market-desk
-> ksp-store-api
-> ksp-store-lib
-> live transport only where explicitly useful
-> KSP domain/query contracts
```

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md -->
<!-- version: 4 -->
<!-- version: 7 -->
# Acquisition, workers, jobs et pipelines spécialisés
@@ -262,7 +262,7 @@ status
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.
@@ -290,6 +290,8 @@ output + durable outcome
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.
## Processing outcomes
@@ -340,7 +342,7 @@ Les événements utiles comprennent notamment :
ksp-job-backfill
-> ksp-job-api
-> 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-logging-lib
```
@@ -351,7 +353,7 @@ ksp-job-backfill
ksp-worker-raw-retriever
-> ksp-worker-api
-> 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-logging-lib
```

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# Applications, services, scenarios et control plane
@@ -107,10 +107,11 @@ ksp-app-solprices-desk
-> ksp-offchain-transport-lib
ksp-app-store-desk
-> ksp-store-api
-> 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 N1N4 expriment des responsabilités et une direction de dépendances ; elles n'imposent pas de traverser toutes les couches intermédiaires.
## Tauri

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/000-README.md -->
<!-- version: 63 -->
<!-- version: 68 -->
# Plans KSP
@@ -27,7 +27,10 @@ Un plan décrit le périmètre, les décisions déjà acquises, les questions ou
- [`016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md`](016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md) — plan historique clôturé de la release stable `0.2.9 — Yellowstone gRPC standard/provider-neutral`, publiée par `rel.001`; il fixe le moteur N1, le standard N2, Config V3, le replay prudent et la validation PublicNode Mainnet/Testnet.
- [`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`.
- [`019-V0_2_12_SOL_PRICES_DESK_PLAN.md`](019-V0_2_12_SOL_PRICES_DESK_PLAN.md) — plan actif de `0.2.12 — SOL Prices Desk + intégration prix Wallet Desk`; il fixe le gabarit 1434/1435, le composite Config, les DTO/commands provider-neutral, le threat model et la trajectoire dintégration Wallet.
- [`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, lintégration Wallet minimale et les gates live/build finaux.
- [`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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
<!-- version: 93 -->
<!-- version: 95 -->
# Séquence des releases fonctionnelles KSP
@@ -520,15 +520,37 @@ Metadata HTTP/IPFS/Arweave viendra au premier besoin Metadata réel. SOL/EUR et
### `0.2.13` — Interface foundation
`ksp-interface-lib` devient la façade wire officielle et expose une API publique wire utilisable par les implementations officielles et externes.
`ksp-interface-lib` matérialise la première façade wire officielle KSP, volontairement passive et Program-facing. La surface candidate stable réexporte le `Pubkey` canonique de Core et expose `ProgramAccountMeta` ainsi que `ProgramInstruction` avec champs privés, accessors explicites et admission bornée à `255` account metas et `10_240` bytes de data. L'ordre et les doublons sont conservés, les Program Pubkeys opaques restent acceptés et les diagnostics ne recopient ni payload ni account material arbitraire.
Aucune `ksp-interface-api` séparée n'est retenue pour l'instant.
La dependency direction reste strictement `Interface -> Core`; aucun serde/codec générique, `solana-instruction`, runtime réseau ou logging n'est introduit. La façade crate-root est verrouillée par canaris public API, consumer externe, release completeness et dependency firewall. [`../../crates/ksp-interface-lib/README.md`](../../crates/ksp-interface-lib/README.md) et [`../../crates/ksp-interface-lib/USAGE.md`](../../crates/ksp-interface-lib/USAGE.md) deviennent les références durables de cette foundation. Le gate technique final `pre.006` est vert avant réconciliation documentaire.
Aucune `ksp-interface-api` séparée n'est retenue pour l'instant. Les codecs/layouts spécifiques restent conditionnés à un vertical réel, tandis que les wires génériques d'acquisition/CORE restent reportés à `0.3.2+`.
### `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

View File

@@ -0,0 +1,672 @@
<!-- file: docs/plans/020-V0_2_13_INTERFACE_PLAN.md -->
<!-- version: 8 -->
# Plan `0.2.13` — Interface / wire foundation
## 1. Base, autorité et nature de `pre.001`
La release est ouverte exclusivement depuis l'archive opérateur `khadhroony-solana-project-v0.2.12.zip`, annoncée comme issue du tag stable `v0.2.12`. L'archive fournie ne contient pas de metadata `.git`; `git describe` n'est donc pas vérifiable dans le sandbox. Les marqueurs internes sont cohérents :
```text
workspace.package.version = 0.2.12
prompts/018-V0_2_13_START_PROMPT.md présent
deltas/0.2.12/rel.001.md présent
crates/ksp-interface-lib absent
crates/ksp-program-api absent
```
Le log opérateur joint à la reprise établit sur la base stable :
```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 --workspace PASS
```
Dans le sandbox de préparation, `cargo` n'est pas installé. Les audits Python de la base ont été rejoués et sont propres ; aucune commande Cargo non exécutée localement n'est déclarée PASS.
`0.2.13-pre.001` reste un gate de lecture/audit/sizing. Il ne crée pas encore `ksp-interface-lib` et ne porte aucun code de l'ancien dépôt.
## 2. Décision de scope
Le minimum retenu pour `0.2.13` est une foundation **Program-facing passive** :
```text
ProgramAccountMeta
ProgramInstruction
bornes locales explicites sur account metas et data
façade crate-root explicite
Error/Result communs via ksp-core-lib
aucun serde/codec générique ajouté sans protocole réel
aucun comportement Program
```
Cette surface correspond à la forme fondamentale d'une invocation Solana : identité du programme, comptes ordonnés avec flags signer/writable, puis octets opaques. Elle prépare directement la future construction d'instructions de `ksp-program-api` sans absorber le modèle d'acquisition/replay qui appartient à `0.3.2+`.
Le scope est volontairement réduit par rapport au forecast initial : `0.2.13` ne crée ni modèle canonique de transaction, ni replay input, ni logs/return-data génériques, ni CPI path, ni discriminant spécifique d'un programme tant qu'aucun vertical slice officiel ne le justifie.
## 3. Règles et architecture confrontées à la base réelle
Sources internes relues avant décision :
```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
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
ROADMAP.md
```
Les contraintes structurantes sont confirmées :
```text
Core possède Pubkey, Error/Result et les Program IDs fondamentaux
Interface possède/reexporte de manière contrôlée les contrats wire officiels
Program API pourra dépendre de Core + Interface, jamais l'inverse
Transport reste propriétaire des DTOs RPC/WS/gRPC et ne dépend pas d'Interface
RAW/CORE n'exige aucun decoder Program
0.3.2 étendra Interface pour les wires génériques d'acquisition/CORE
pas de pub mod ; exports explicites depuis crate root
pas de dépendance externe sans usage réel
```
## 4. Inventaire interne stable pertinent
### 4.1 Core
| Surface stable | Owner actuel | Visibilité/consumer | Décision `0.2.13` |
|----------------------------------|----------------|-----------------------------------------------|---------------------------------------------------------------------|
| `Pubkey` | `ksp-core-lib` | public crate-root; consommé dans le workspace | réutiliser directement; aucun wrapper Interface |
| `Error` / `ErrorCode` / `Result` | `ksp-core-lib` | public crate-root; contrat commun | réutiliser pour les erreurs de bornes Interface |
| 18 Program IDs + registry/filter | `ksp-core-lib` | public crate-root | laisser Core; Interface ne duplique pas les identités fondamentales |
Les anciens wrappers textuels `MdProgramId` / `MdPubkey` de kbot3 n'apportent donc aucune valeur à la frontière KSP actuelle : un `Pubkey` typé existe déjà et doit rester l'identité fondamentale.
### 4.2 Transport
| Type/famille observée | Owner actuel | Sémantique réelle | Décision |
|----------------------------------------------------------|-------------------------|--------------------------------------------------------|-------------------|
| `SolanaEncodedTransaction` | On-chain Transport HTTP | projection JSON-RPC encodée | laisser Transport |
| `SolanaConfirmedTransaction` / `SolanaBlockTransaction` | On-chain Transport HTTP | réponse RPC avec états wire/metadata provider | laisser Transport |
| `SolanaWireField<T>` | On-chain Transport HTTP | distinction omitted/null/value propre aux réponses RPC | laisser Transport |
| `YellowstoneCompiledInstruction` | On-chain Transport gRPC | projection protobuf Yellowstone | laisser Transport |
| `YellowstoneInnerInstructions` / `YellowstoneReturnData` | On-chain Transport gRPC | metadata d'update Geyser | laisser Transport |
| `YellowstoneTransactionError` / `...Meta` | On-chain Transport gRPC | sémantique provider/protobuf | laisser Transport |
Ces DTOs peuvent ressembler à de futurs objets génériques, mais ils représentent aujourd'hui des formes de transport concrètes. `0.2.13` ne les déplace pas et ne force aucune dépendance `Transport -> Interface`.
### 4.3 Dépendances workspace
La base possède déjà notamment :
```text
solana-pubkey = ^4.3, default-features = false
serde = ^1.0
serde_json = ^1.0
```
Aucun `solana-instruction`, `borsh`, `wincode` ou crate d'interface Program n'est actuellement nécessaire au nouveau lot. Le graphe cible initial reste donc `Interface -> Core` uniquement.
## 5. Audit d'héritage `khadhroony-bot3`
Archive auditée : `khadhroony-bot3_v0.5.3-pre.005-fix010.zip`.
Familles inspectées :
```text
ks-lib/src/model/solana.rs
ks-lib/src/model/canonical_transaction.rs
ks-lib/src/model/replay.rs
ks-lib/src/decoder/api/contracts.rs
ks-lib/src/executor/api/execution.rs
familles decoder/* wire pertinentes comme points de comparaison uniquement
```
La matrice suivante porte sur les **concepts**, pas sur un portage de code :
| Concept kbot3 | Verdict | Cible KSP | Raison |
|--------------------------------------------------------------|------------|----------------------------------|--------------------------------------------------------------------------------|
| account `{ pubkey, is_signer, is_writable }` ordonné | REPRENDRE | Interface `0.2.13` | forme passive fondamentale d'une instruction; retirer le naming execution |
| instruction `{ program_id, accounts, data }` | REPRENDRE | Interface `0.2.13` | forme officielle minimale; retirer `operation_code` et toute policy |
| `MdPubkey` / `MdProgramId` en `String` | REDESSINER | Core | remplacer par `ksp_core_lib::Pubkey` déjà possédé par Core |
| payload `Vec<u8>` non borné | REDESSINER | Interface `0.2.13` | ajouter admission bornée et Debug résumé |
| `DcApiInstructionDecoder` + recognition/outcomes/proofs | REPORTER | Program API `0.2.14` | comportement de reconnaissance/decode, interdit dans Interface |
| `MdInstructionPath` / parent / stack height | REPORTER | Interface/CORE `0.3.2+` | contexte d'acquisition/replay, pas nécessaire au builder Program minimal |
| logs / return data / tx failure / balance changes génériques | REPORTER | Interface/CORE `0.3.2+` | faits d'exécution/acquisition; ne pas préfigurer CORE |
| `MdCanonicalTransaction` + canonical JSON/hash | REPORTER | CORE series après `0.3.2` | modèle source-neutral large à redéfinir avec Store/CORE réels |
| `MdCoreInstructionReplayInput` | REPORTER | CORE/replay après RAW | contrat transversal trop large, dépend de données encore non possédées |
| Prepared plan, signers, fee payer, policies | REPORTER | Program API/Execution ultérieurs | responsabilité d'orchestration/exécution, pas wire passif |
| monolithe `ks-lib` + générations d'API coexistantes | REJETER | aucune | ownership flou et couplage vertical/horizontal à ne pas reproduire |
| version/hash/JSON génériques par réflexe | REJETER | aucune dans `0.2.13` | aucun contrat sérialisé ne justifie une enveloppe/version générique maintenant |
Anti-patterns à ne pas reproduire :
```text
wrappers Pubkey/ProgramId textuels alors qu'une primitive typée existe
serde dérivé sur tous les contrats sans frontière de sérialisation réelle
JSON Value comme échappatoire pour des formes structurelles futures
contrat replay géant utilisé comme point de rencontre de toutes les couches
operation_code/policy/signers mêlés à l'instruction passive
traits decoder dans la même crate que les wires
```
## 6. Audit externe actuel — 27 août 2026
L'audit externe est limité aux candidates réellement proches du lot retenu.
| Candidate | Version actuelle observée | Contrat / graphe | Décision |
|------------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|
| `solana-instruction` | 3.5.0 | `Instruction { program_id: Pubkey, accounts: Vec<AccountMeta>, data: Vec<u8> }`; `solana-pubkey ^4.3.0`; codecs optionnels | référence normative; pas de dépendance runtime `0.2.13` |
| `solana-program` syscalls | 4.1.0 | `MAX_CPI_INSTRUCTION_ACCOUNTS = 255`; data `10 KiB`; account infos uniques `128` | source normative des plafonds d'admission initiaux; pas de dépendance |
| `solana-loader-v3-interface` | 8.1.1 | interface officielle; dépend `solana-instruction ^3.5.0` + `solana-pubkey ^4.3.0`; wincode optionnel | compatible en apparence mais hors premier lot; réauditer au vertical loader-v3 |
Sources primaires/current utilisées :
```text
https://docs.rs/solana-instruction/3.5.0/solana_instruction/struct.Instruction.html
https://docs.rs/solana-program/4.1.0/solana_program/syscalls/index.html
https://docs.rs/solana-loader-v3-interface/8.1.1/solana_loader_v3_interface/
https://github.com/anza-xyz/solana-sdk
```
`solana-instruction` 3.5.0 est Apache-2.0 via le workspace Anza. Ses features `serde`, `borsh`, `bincode` et `wincode` sont optionnelles. La génération `solana-pubkey ^4.3.0` est cohérente avec la base KSP.
La dépendance directe n'est néanmoins pas retenue maintenant : ses champs `Vec` publics ne permettent pas à KSP de garantir les bornes d'admission sur sa propre API, et aucun interop runtime ne requiert encore de convertir vers ce type. `DEP-KSP-005` impose de ne pas ajouter une dépendance seulement pour refléter une forme triviale que KSP doit de toute façon borner.
## 7. Matrice de frontières
| Concept/type | Owner cible | 0.2.13 ? | 0.2.14 ? | 0.3.2+ ? | Autorité / raison |
|-------------------------------------------------------------|----------------|----------|------------------|------------------------|-----------------------------------------------|
| `Pubkey` | Core | reuse | reuse | reuse | primitive fondamentale KSP déjà stable |
| Program IDs fondamentaux | Core | reuse | reuse | reuse | registry Core déjà stable |
| account meta d'instruction | Interface | OUI | consume | reuse | forme Solana officielle passive |
| instruction Program passive | Interface | OUI | consume | reuse | minimum nécessaire au futur preparer |
| codec/discriminant Program spécifique | Interface | NON | si vertical réel | selon besoin | aucun protocole concret dans foundation |
| instruction path / CPI ancestry | Interface/CORE | NON | NON requis | OUI | fait structurel d'acquisition/normalisation |
| logs / return data / tx failure / balance deltas génériques | Interface/CORE | NON | NON requis | OUI | surface RAW/CORE future |
| DTOs RPC/WS/Yellowstone existants | Transport | laisser | laisser | adapter plus tard | projection transport concrète |
| decoder identity/recognition/decode | Program API | NON | OUI audit | consume CORE plus tard | comportement extensible Program |
| implémentations officielles de programmes | Program Lib | NON | NON | plus tard | vertical slices distincts |
| RAW persistence | Store | NON | NON | `0.3.1` | architecture RAW |
| transaction/replay canonique générique | CORE | NON | NON | après `0.3.2` | doit être défini avec acquisition/Store réels |
## 8. Surface API proposée
Les noms restent ajustables jusqu'à implémentation, mais le contrat conceptuel est fermé :
```rust
pub use ksp_core_lib::Pubkey;
pub const MAX_PROGRAM_INSTRUCTION_ACCOUNTS: usize = 255;
pub const MAX_PROGRAM_INSTRUCTION_DATA_LEN: usize = 10 * 1024;
pub struct ProgramAccountMeta { /* private */ }
pub struct ProgramInstruction { /* private */ }
```
Façade visée :
```text
ProgramAccountMeta::writable(pubkey, is_signer)
ProgramAccountMeta::readonly(pubkey, is_signer)
ProgramAccountMeta::{pubkey,is_signer,is_writable}
ProgramInstruction::try_new(program_id, accounts, data) -> ksp_core_lib::Result<Self>
ProgramInstruction::{program_id,accounts,data}
```
Décisions de contrat :
- `Pubkey` est consommé via Core ; aucun `ProgramId` wrapper supplémentaire ;
- les comptes gardent exactement leur ordre et les doublons restent permis ;
- comptes vides et data vide restent valides ;
- aucune validation sémantique de Program ID, signer ou writable au-delà de la forme passive ;
- les champs restent privés afin d'empêcher la construction d'un état hors bornes ;
- la construction valide les longueurs sans cloner les vecteurs fournis ;
- `Debug` de `ProgramInstruction` doit résumer `program_id`, `account_count` et `data_len`, sans imprimer tout le payload ;
- pas de `serde::{Serialize, Deserialize}` dans la foundation : aucune persistence/IPC/wire sérialisée générique ne l'exige, et une dérive `Deserialize` naïve affaiblirait le gate de taille avant allocation ;
- aucun `borsh`, `bincode` ou `wincode` générique : les codecs arrivent avec un vrai protocole Program dans Interface ;
- pas de `format_version` artificiel tant qu'aucun format sérialisé KSP n'est défini.
### 8.1 Bornes
Les bornes initiales suivent les plafonds runtime Solana actuels :
```text
account metas d'une instruction <= 255
data d'une instruction <= 10_240 octets
```
Elles sont des **bornes d'admission Interface**, pas la promesse qu'une instruction seule rentrera dans toute transaction top-level. Les contraintes de message/transaction, comptes uniques, fee payer, blockhash et assembly appartiennent aux couches d'exécution ultérieures.
### 8.2 Erreurs
Le crate n'introduit pas un second type d'erreur. Les violations de borne utilisent `ksp_core_lib::Error/Result` avec codes Interface stables et contexte sûr limité aux longueurs/plafonds. Aucun octet du payload, compte arbitraire ou valeur externe n'est copié dans les diagnostics.
Les codes exacts seront matérialisés avec la première primitive ; deux catégories suffisent conceptuellement :
```text
invalid_program_instruction
program_instruction_limit_exceeded
```
Si la première implémentation démontre qu'une seule catégorie bornée suffit, ne pas créer la seconde par symétrie.
## 9. Threat / robustness model
| Risque | Décision | Canari prévu |
|-------------------------------------|------------------------------------------------------|------------------------------------------------------|
| data surdimensionnée | refus avant stockage/copie supplémentaire | `10_240` accepté; `10_241` refusé |
| collection accounts surdimensionnée | refus déterministe | `255` accepté; `256` refusé |
| conversion/index overflow | aucun cast étroit en foundation; comparer en `usize` | borne 255 + absence de casts unchecked |
| ordre/doublons accounts | préserver exactement; ne pas dédupliquer | ordre et doublon round-trip structurel via accessors |
| wire malformé | N/A : aucun decoder/codec dans le lot | aucun faux test; réouvrir au premier codec réel |
| enum/discriminant inconnu | N/A : aucun discriminant dans le lot | réouvrir au vertical Program |
| structures imbriquées pathologiques | N/A au-delà des deux Vec plates | bornes accounts/data suffisent pour ce lot |
| Debug volumineux | Debug résumé | payload sentinelle absent du rendu Debug |
| allocation/copie excessive | `try_new` consomme les Vec; pas de clone interne | canari structure/API + revue source |
| serde hostile | serde absent | dependency/public API canary |
| panic/unwrap/expect/? | interdits par règles/lints | audit Rust + Clippy workspace |
| Program ID inconnu | accepté : Pubkey opaque typé | canari avec Pubkey non registry |
## 10. Dependency graph cible et firewall
Cible de `0.2.13` :
```text
ksp-interface-lib
-> ksp-core-lib
-> solana-pubkey ^4.3
```
Interdictions :
```text
ksp-interface-lib -X-> ksp-program-api
ksp-interface-lib -X-> ksp-program-lib
ksp-interface-lib -X-> ksp-onchain-transport-lib
ksp-interface-lib -X-> ksp-offchain-transport-lib
ksp-interface-lib -X-> ksp-config-lib
ksp-interface-lib -X-> ksp-store-*
ksp-interface-lib -X-> ksp-wallet-lib
ksp-interface-lib -X-> Tauri
ksp-interface-lib -X-> reqwest/tonic/tokio/network
```
Aucun logging runtime n'est attendu dans une crate de contrats passifs. `ksp-logging-lib` reste absent sauf comportement futur démontré.
Validation Cargo prévue dès le scaffold :
```bash
cargo tree -p ksp-interface-lib --edges normal
cargo tree --duplicates
```
Comme aucune nouvelle dépendance externe n'est retenue au gate, aucun doublon codec/Solana ne doit être créé par `pre.002`.
## 11. Stratégie de tests
### Tests unitaires privés
Sous `unit_tests/` rattachés aux modules privés :
```text
constructeurs account meta writable/readonly
bornes comptes 255/256
bornes data 10_240/10_241
empty accounts/data acceptés
ordre et doublons préservés
Pubkey inconnu accepté
Debug borné et payload absent
erreurs sans bytes ni valeur hostile
```
### Tests d'intégration `tests/`
```text
public_api.rs consommation uniquement via crate-root
external_consumer.rs petit consumer/crate canary si le pattern workspace le permet
dependency_boundary.rs manifest/source firewall et absence de serde/codecs/network
release_completeness.rs inventaire exact de la surface 0.2.13 retenue
```
Aucun smoke réseau n'est requis. Aucun round-trip binaire/JSON n'est inventé sans codec réel.
## 12. Hors scope explicite
```text
ksp-program-api et tout trait decoder/preparer
ksp-program-lib
codec Program spécifique sans vertical réel
system/token/loader/metaplex instruction families
PDA seeds sans protocole concret
transaction/message/compiled instruction génériques d'acquisition
replay input / instruction path / CPI tree
logs / return data / transaction errors / balances génériques
Store/persistence
Transport refactor ou dépendance vers Interface
Wallet/Tauri/Config
serde/JSON canonical/hash
execution policy, signer selection, blockhash, simulation, send
```
## 13. Prévision souple recalibrée
Le scope réduit permet de supprimer le second lot wire optionnel du forecast initial et de garder des tranches compatibles avec le budget KSP de 1520 minutes de travail effectif.
### pre.001 — audit + héritage + frontières + sizing
**Statut : réalisé ; gate opérateur `pre.001` intégralement PASS.**
Relire les règles/architectures, auditer Core/Transport, classer kbot3, auditer les candidates externes, retenir la surface instruction passive, fixer bornes/firewall/threat model/tests et créer plan/validation.
### pre.002 — scaffold `ksp-interface-lib` + façade + firewall
**Statut : réalisé ; gate opérateur intégralement PASS.**
Créer la crate, l'ajouter au workspace, poser `lib.rs` avec exports explicites, lints, README/USAGE initiaux et canaris manifest/firewall. Dépendance normale unique : `ksp-core-lib`.
### pre.003 — `ProgramAccountMeta` + bornes communes
**Statut : réalisé ; fmt/audits/check/Clippy/tests Interface PASS, `cargo test --workspace` non fourni dans la preuve opérateur.**
Matérialiser la primitive de compte ordonné, la borne accounts, le modèle d'erreur Interface minimal et les unit/public canaries associés. `ProgramInstruction` et la borne data restent réservés à `pre.004`.
### pre.004 — `ProgramInstruction` passif borné
**Statut : réalisé ; gate opérateur intégralement PASS.**
Ajouter l'instruction `{ program_id, accounts, data }`, constructeur validé, accessors, Debug résumé et canaris d'ordre/doublons/limites. Aucun serde/codec ou comportement Program.
### pre.005 — adversarial + consumer externe + API/dependency hardening
**Statut : réalisé ; `pre.005-fix.001` validé, gate opérateur intégralement PASS.**
Fermer les cas limites, surface crate-root, consumer externe, firewall source/manifest et graphes Cargo. Ne pas ajouter un second domaine wire opportuniste.
### pre.006 — gate technique final
**Statut : réalisé ; gate opérateur intégralement PASS.**
Workspace complet, canaris de complétude/public API/dependencies, ownership Logging, `cargo tree` et audit final du scope exact sont validés. Aucun README/USAGE final ni préparation de publication n'a été mélangé à cette tranche.
### pre.007 — réconciliation documentaire finale
**Statut : réalisé ; gate opérateur documentaire à confirmer.**
README/USAGE Interface, plan, validation, séquence fonctionnelle et index documentaires réellement affectés sont réconciliés. `CHANGELOG.md`, `ROADMAP.md` et le prompt suivant restent réservés à `pre.008`.
### pre.008 — préparation de publication minimale
**Statut : prévu**
Préparer uniquement `prompts/019-V0_2_14_START_PROMPT.md`, `CHANGELOG.md`, `ROADMAP.md`, le bump prerelease mécanique et le delta. Aucun code/test/README/USAGE/plan/validation/architecture ne doit être rouvert.
### rel.001 — publication stable
**Statut : prévu**
Passer `workspace.package.version` à `0.2.13`, rejouer le gate stable requis, publier le commit `v0.2.13-rel.001` puis le tag stable `v0.2.13`.
## 14. Sizing et critère session
| Tranche | Charge relative | Motif du découpage |
|---------|------------------|----------------------------------|
| pre.002 | petite | scaffold + firewall uniquement |
| pre.003 | petite | une primitive + bornes/erreurs |
| pre.004 | petite à moyenne | instruction + hardening local |
| pre.005 | moyenne | consumer + adversarial + graphes |
| pre.006 | gate | aucun développement fonctionnel |
| pre.007 | documentation | réconciliation séparée |
| pre.008 | publication prep | payload minimal séparé |
Le scope paraît clôturable dans une release/session sans sacrifier les lanes de sortie. Si `pre.003` ou `pre.004` révèle qu'une dépendance/codec spécifique est indispensable, le gate doit être rouvert avant d'étendre le scope ; il ne faut pas réactiver automatiquement `pre.005` comme second lot wire.
## 15. Gate `0.2.13-pre.001`
À la sortie préparée :
```text
base stable / prompt / rel confirmé
règles et architecture obligatoires relues
Core / Transport inventoriés
archive kbot3 auditée
matrice REPRENDRE/REDESSINER/REPORTER/REJETER produite
0.2.13 / 0.2.14 / 0.3.2 séparés
premier lot ProgramAccountMeta + ProgramInstruction
nouvelle dépendance externe aucune
threat model / tests / dependency graph définis
plan + validation créés
implementation Program absente
```
## 16. Gate attendu pour `pre.003`
`pre.002` matérialise le scaffold strict décidé par le gate précédent :
```text
nouvelle crate membre ksp-interface-lib
Cargo dépend uniquement de ksp-core-lib
lints workspace hérités
crate root explicite sans pub mod
Pubkey réexporté depuis Core
README/USAGE initiaux
canari public API minimal
canari dependency firewall
aucun ProgramAccountMeta/ProgramInstruction fonctionnel
aucun ksp-logging-lib / constants.rs / TRACING_TARGET
```
`pre.003` peut commencer après application du delta et gate opérateur vert. Sa responsabilité reste limitée à `ProgramAccountMeta`, aux bornes communes et au modèle d'erreur Interface minimal ; `ProgramInstruction` reste réservé à `pre.004`.
## 17. État préparé `pre.003`
`pre.003` matérialise uniquement la primitive de compte et les contrats nécessaires à la future admission de `ProgramInstruction` :
```text
ProgramAccountMeta présent
fields privés
constructeurs readonly / writable
accessors pubkey / is_signer / is_writable
Pubkey Core réutilisé
MAX_PROGRAM_INSTRUCTION_ACCOUNTS 255
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED interface.program_instruction_limit_exceeded
ProgramInstruction absent par contrat
MAX_PROGRAM_INSTRUCTION_DATA_LEN absent jusqu'à pre.004
serde / borsh / bincode / wincode absents
ksp-logging-lib / constants.rs / TRACING_TARGET absents
```
La constante `255` ferme le contrat public de borne accounts mais son enforcement `255/256` appartient au constructeur de `ProgramInstruction` de `pre.004`; `ProgramAccountMeta` représente un seul compte et n'invente donc pas une collection artificielle uniquement pour tester cette limite.
Une seule catégorie d'erreur Interface est introduite. `invalid_program_instruction` reste absent : aucun invariant de forme distinct des limites n'est encore matérialisé et le créer par symétrie serait prématuré. Les futures erreurs de borne doivent utiliser `ksp_core_lib::Error/Result` et ne projeter que des longueurs/plafonds sûrs.
La preuve opérateur fournie pour `pre.003` confirme fmt, audits, `cargo check`, Clippy, tests ciblés Interface et graphes Cargo. `cargo test --workspace` n'apparaît pas dans cette preuve ; il doit donc être rejoué au gate de `pre.004` avant de déclarer la continuité workspace complète.
## 18. État préparé `pre.004`
`pre.004` matérialise le second et dernier type du premier lot wire générique retenu :
```text
ProgramInstruction présent
fields privés
program_id Pubkey Core
accounts Vec<ProgramAccountMeta> consommé
data Vec<u8> opaque consommé
MAX_PROGRAM_INSTRUCTION_ACCOUNTS 255
MAX_PROGRAM_INSTRUCTION_DATA_LEN 10_240
try_new bornes accounts puis data
accounts 255 / 256 accepté / refusé
data 10_240 / 10_241 accepté / refusé
accounts/data vides acceptés
ordre/doublons préservés
Program Pubkey non registry accepté
Debug program_id/account_count/data_len uniquement
serde / borsh / bincode / wincode absents
solana-instruction absent
ksp-logging-lib / constants.rs / TRACING_TARGET absents
```
`ProgramInstruction::try_new` consomme les deux `Vec` fournis et compare leurs longueurs en `usize` avant stockage dans l'objet. La foundation n'ajoute aucun clone, cast étroit ou allocation de conversion. Une allocation éventuellement déjà réalisée par l'appelant pour construire le `Vec` reste naturellement hors du contrôle de l'API.
Les deux limites utilisent le code commun `interface.program_instruction_limit_exceeded`. Le contexte d'erreur est volontairement réduit à `field`, `actual_len` et `maximum_len`; aucune donnée du payload ni aucun account arbitraire n'est recopié dans le diagnostic.
Le `Debug` manuel n'imprime ni la collection accounts ni les octets `data`. Il expose uniquement l'identité publique du programme et les deux longueurs structurelles utiles au diagnostic.
Le gate opérateur `pre.004` confirme désormais fmt, audits Rust/Markdown, `cargo check --workspace`, Clippy, tests ciblés Interface, `cargo test --workspace` et les deux graphes Cargo. Le passage à `pre.005` se concentre donc exclusivement sur les canaris adversariaux/consumer externe/API/dependency hardening, sans ajouter un second domaine wire.
## 19. État préparé `pre.005`
`pre.005` n'ajoute aucun code de production et ferme uniquement les preuves de consommation, complétude et résistance adversariale autour de la surface déjà matérialisée :
```text
production src modifié non
nouveau domaine wire non
external consumer integration crate présent
release completeness inventory présent
façade crate-root exacte 6 exports contrôlés
modules de production 4 fichiers exacts
Vec accounts/data transfert sans réallocation interne testé
payload hostile dans erreur absent attendu
account material hostile dans erreur absent attendu
narrowing casts absents par source canary
codec encode/decode générique absent par source canary
manifest dependency ksp-core-lib uniquement
source dependency firewall renforcé sur tous les modules production
```
Le canari `tests/external_consumer.rs` est volontairement un test d'intégration Rust séparé : Cargo le compile comme un crate consommateur externe de la bibliothèque. Il n'utilise que la façade `ksp_interface_lib::*` accessible depuis le crate-root et n'exige donc ni fixture workspace artificielle ni invocation Cargo imbriquée.
`tests/release_completeness.rs` verrouille l'inventaire exact de la foundation `0.2.13` :
```text
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
MAX_PROGRAM_INSTRUCTION_ACCOUNTS
ProgramAccountMeta
MAX_PROGRAM_INSTRUCTION_DATA_LEN
ProgramInstruction
Pubkey
```
Il verrouille également l'absence de `pub mod` et l'inventaire exact des quatre fichiers Rust de production. Ajouter une seconde famille wire dans `0.2.13` ferait ainsi échouer explicitement le canari de complétude au lieu d'élargir silencieusement la release.
Le hardening unitaire vérifie enfin que les allocations des `Vec` admis sont transférées telles quelles dans `ProgramInstruction` et que les erreurs de dépassement ne recopient ni marqueur hostile de payload ni représentation arbitraire d'account meta. Ces tests renforcent le contrat de construction sans changer l'API publique.
Après validation opérateur de `pre.005`, `pre.006` doit rester un gate technique pur sans nouveau développement fonctionnel.
## 20. Correctif préparé `pre.005-fix.001`
Le gate opérateur `pre.005` confirme les preuves suivantes :
```text
fmt / audits Rust / Markdown PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
external consumer PASS
release completeness PASS
hardening adversarial PASS
cargo test --workspace FAIL cross-crate canary Logging
cargo tree Interface Core-only confirmé
```
L'échec workspace ne révèle aucun bypass runtime d'Interface. Le scanner `ksp-logging-lib/tests/ownership.rs` recherche littéralement le chemin source `tracing::` dans tous les fichiers Rust des crates KSP, tests compris. Le firewall `ksp-interface-lib/tests/dependency_boundary.rs` contenait cette chaîne comme **valeur de test interdite**, ce qui déclenchait le scanner Logging sur le canari lui-même.
Le fix conserve exactement la même interdiction mais construit le token de recherche sans faire apparaître le chemin littéral dans le source du test :
```rust
concat!("tracing", "::")
```
Le test Interface continue donc de refuser un chemin direct vers Tracing dans les sources de production, tandis que le scanner Logging ne confond plus la donnée du canari avec un usage runtime réel.
Aucun fichier `src/` de production, aucune dépendance, aucune API publique, aucune borne et aucune décision de logging ne sont modifiés. La version technique devient `0.2.13-pre.5.fix.1` parce que le fix touche un test Rust.
Le gate opérateur du fix est désormais intégralement vert : fmt, audits Rust/Markdown, `cargo check --workspace`, Clippy, tests Interface, canari Logging ciblé, `cargo test --workspace` et graphes Cargo sont tous validés/inspectés. Le faux positif est donc fermé sans élargissement du scope.
## 21. Gate technique final validé `pre.006`
`pre.006` n'a ajouté aucun code, aucun test et aucune dépendance. Le gate opérateur du **28 août 2026** ferme la lane technique avec les preuves suivantes :
```text
cargo fmt --all PASS
audits Rust / export completeness / workspace PASS
audit Markdown PASS — 162 tables / 119 fichiers
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-interface-lib PASS
cargo test -p ksp-logging-lib --test ownership PASS
cargo test --workspace PASS
cargo tree -p ksp-interface-lib --edges normal Core-only confirmé
cargo tree --duplicates inspecté
```
La surface technique finale avant documentation reste donc :
```text
workspace.package.version 0.2.13-pre.6
production Interface inchangée
tests Interface inchangés
surface crate-root 6 exports exacts
modules production 4 fichiers exacts
dépendance normale Interface ksp-core-lib uniquement
codec / serde / solana-instruction absents
ksp-logging-lib / tracing runtime Interface absents
network/live smoke N/A pour foundation wire pure
```
Le gate `pre.006` autorise la réconciliation documentaire finale sans réouverture technique.
## 22. Réconciliation documentaire finale `pre.007`
`pre.007` ne modifie aucun fichier Rust, test, manifest de crate, dépendance ou comportement. La version workspace avance mécaniquement à `0.2.13-pre.7` conformément au workflow de prerelease non-fix.
Les références durables sont réconciliées autour du contrat réellement livré :
```text
crates/ksp-interface-lib/README.md
crates/ksp-interface-lib/USAGE.md
docs/000-README.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
docs/validation/000-README.md
docs/validation/016-V0_2_13_INTERFACE.md
```
Le README Interface fixe l'ownership, la surface crate-root exacte, les deux bornes d'admission, le modèle d'erreur sûr, le Debug borné, le graphe `Interface -> Core`, l'absence de codecs/logging/runtime et les frontières avec Program API/Transport/Store. `USAGE.md` documente les constructeurs `readonly`/`writable`, `ProgramInstruction::try_new`, les accessors, les bornes et l'observation des erreurs sans dépendre des modules privés.
La séquence fonctionnelle précise désormais le contenu réellement livré par `0.2.13` et maintient les reports : Program API en `0.2.14`, wires génériques acquisition/CORE en `0.3.2+`. Les index plan/validation passent du statut actif au statut candidat réconcilié sans annoncer prématurément la release stable.
La tranche ne touche explicitement pas :
```text
CHANGELOG.md
ROADMAP.md
prompts/019-V0_2_14_START_PROMPT.md
architecture
code/tests/manifests de crate
```
Ces surfaces appartiennent à `pre.008` pour la préparation minimale de publication, sauf découverte d'un défaut réel imposant une nouvelle tranche corrective.

View 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 nest 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.

Some files were not shown because too many files have changed in this diff Show More