# Validation `0.3.1` — Store API RAW foundation ## 1. Objet Cette matrice est ouverte par `0.3.1-pre.001`, corrigée par `pre.001-fix.001` puis recalibrée par `pre.001-fix.002`. Elle valide **`ksp-store-api` uniquement**. La façade/runtime commune `ksp-store-lib` et l'implémentation PostgreSQL séparée `ksp-store-postgres-lib` sont réunies dans `0.3.2`. Le scope concret N1 certain est : ```text RawTransaction + observation provenance/références/outcomes/queries communs contrats/capabilities backend externes cycle de rétention logique + tombstone ``` `pre.004` prouve la convergence de `RawAccountState`/observation entre HTTP/WS/gRPC sous admission stricte bytes complets + slot. `TransactionStatusObservation` reste différé : snapshot HTTP, transition WS et update Yellowstone ne sont pas encore un fait unique. Les notifications logs/slot/vote restent event-only candidates, avec ownership Interface préféré lorsqu'elles ne sont pas persistées. ## 2. Gate `pre.001` | Critère | Statut | Preuve / décision | |----------------------------------|---------|-----------------------------------------------------------------------------------| | base stable `0.2.14` | PASS | Cargo `0.2.14`, `rel.001` et prompt 020 présents | | baseline opérateur | PASS | audits/check/Clippy fournis verts ; validation tests déclarée OK | | archive kbot3 + kbot2 historique | PASS | kbot3 extraite ; `olddocs/archivekbot2` audité pour lifecycle/replay | | règles Store/API relues | PASS | règles KSP/Dependencies/Workflow prescrites relues | | split version | PASS | `0.3.1 = Store API`, `0.3.2 = Store lib + PostgreSQL lib` | | dependency graph `0.3.1` | PASS | cible Core-only | | modèle backend commun | PASS | API object/struct commune, rows backend privées | | admission multi-source | PASS | même modèle seulement si HTTP/WS/gRPC satisfont intégralement la même sémantique | | `RawTransaction` | PASS | premier modèle persistant certain | | logs dans transaction | PASS | restent dans `RawTransaction`, extraction seulement en N2 STRUCTURAL | | logsSubscribe | REPORTÉ | event-only candidat ; pas de `RawLog` Store persistant par défaut | | account state | PRÉVU | modèle/observation à figer après matrice de compatibilité | | transaction status | PRÉVU | observation/event à figer si sémantique commune prouvée | | slot/vote | TODO | event-only candidats ; ownership Interface à auditer | | RawBlock | IDEA | non persisté par défaut ; `getBlock` sert de conteneur d'acquisition | | Yellowstone Entry | REJETÉ | aucun replay/decomposition/event métier justifiant un modèle Store | | frontière Interface/Store | PASS | persistent/replay -> Store API ; event-only partagé -> Interface préférentiel | | N2 nomenclature | PASS | `CORE` remplacé par nom de travail `STRUCTURAL` | | pipeline non linéaire | PASS | toutes les familles N1 ne sont pas forcées N1->N2->N3->N4 | | processing proof | PASS | futur ledger stage+processor/version+input hash ; `processed: bool` insuffisant | | retention lifecycle | PASS | `Full/Compacted/Archived/Purged` redessiné backend-agnostic | | tombstone anti-rebackfill | PASS | identité/hash/slot minimal conservé après purge ; backfill forcé distinct | | notification ownership runtime | PASS | Store ne possède aucun event bus/scheduler/DB notify | | schema/migrations PostgreSQL | REPORTÉ | responsabilité `ksp-store-postgres-lib` `0.3.2` | | D2/N3/N4 persistence | ABSENT | hors `0.3.1` | | threat model | PASS | source mismatch, purge prématurée, stale processing, backend/event leaks couverts | | sizing | PASS | dix prereleases courtes + lanes fermeture séparées | ## 3. Décisions structurelles à prouver par le code | Contrat | Décision `pre.001-fix.002` | Gate futur | |--------------------------------|-------------------------------------------------------------------|------------------------------| | crate `ksp-store-api` | seule crate Store créée en `0.3.1` | `pre.002` | | dépendance normale | `ksp-core-lib` uniquement par défaut | `pre.002` | | `RawPayload` | KSP-owned, source-independent, versionné, borné, Debug sans bytes | `pre.003` | | `RawTransaction` | objet persistant commun uniquement depuis une source complète | `pre.003` | | `RawTransactionObservation` | acquisition/provenance séparée du RAW | `pre.003` | | transaction `logMessages` | partie du RAW transactionnel | `pre.003` | | logs structuraux | extraction N2 STRUCTURAL future, jamais `RawLog` N1 distinct | canari négatif `pre.003/007` | | `RawAccountState`/observation | modèle prévu si HTTP/WS/gRPC convergent sans perte | `pre.004` | | `TransactionStatusObservation` | modèle prévu si surfaces status convergent | `pre.004` | | logs/slot/vote event-only | ne créent aucune capability Store par défaut | `pre.004/007` | | Interface vs Store | event-only partagé -> Interface ; persistent/replay -> Store API | `pre.004/007` | | model vs capability | un modèle API n'oblige pas tous les backends à le persister | `pre.005` | | capabilities | read/write fines et object-safe | `pre.005` | | transaction handle | aucun handle SQL/backend public | `pre.005` | | atomic acquisition | méthode métier RAW + observation all-or-nothing | `pre.005/006` | | write outcome | `Inserted / AlreadyPresent`; divergence = `Err Conflict` | `pre.006` | | page/cursor | limit caller > 0 ; sans max KSP ; cursor opaque <= 4 KiB | `pre.006` | | `RawRetentionState` | logique `Full/Compacted/Archived/Purged`, sans détail physique | `pre.006` | | tombstone | identité/hash/slot minimal durable après purge | `pre.006` | | backfill normal après purge | skip distinct | `pre.006` | | force rehydrate | chemin explicite distinct, jamais fallback automatique | `pre.006` | | processing evidence | futur ledger version-aware, jamais un bool unique | boundary `pre.006/007` | | façade runtime `Store` | reportée à `ksp-store-lib`, hors `0.3.1` | `0.3.2` | ## 4. Dependency firewall final attendu Le graphe final `0.3.1` doit rester : ```text ksp-store-api └── ksp-core-lib └── solana-pubkey ``` Interdits par défaut dans Store API : ```text ksp-store-lib ksp-onchain-transport-lib ksp-offchain-transport-lib ksp-interface-lib sans usage de type réel ksp-program-api ksp-program-lib ksp-materializer-api ksp-config-lib ksp-logging-lib ksp-wallet-lib tokio-postgres sqlx serde / serde_json chrono tokio async-trait Tauri ``` Toute divergence exige un usage public réel et une révision du plan. ## 5. Matrice N1 initiale | Famille / fait | Classification actuelle | Persistence Store | N2 attendu | Action `0.3.1` | |--------------------------------|--------------------------|-------------------|----------------|--------------------------------------------------------------------| | transaction complète | N1 RAW | oui | STRUCTURAL oui | implémenter modèle + observation | | logs contenus dans transaction | partie de RawTransaction | via transaction | STRUCTURAL oui | préserver lossless, ne pas dupliquer en `RawLog` | | account state complet | N1 RAW | oui à terme | pas démontré | modèle + observation matérialisés en `pre.004` | | transaction status | familles distinctes | non figée | non | différer : snapshot HTTP != transition WS != update Yellowstone | | `logsSubscribe` notification | event-only candidat | non par défaut | non | ownership Interface/worker à figer | | slot/root/slotsUpdates | event-only candidat | non | non | TODO use-case + compatibilité | | vote | event-only candidat | non | non | TODO seulement si forme commune utile | | block complet | acquisition container | non par défaut | — | IDEA uniquement ; extraire transactions plutôt que stocker le bloc | | Yellowstone Entry | transport-only | non | — | explicitement non retenu | ## 6. Frontière N1 -> N2 STRUCTURAL `0.3.1` doit préserver sans implémenter : ```text RawTransaction -> StructuralTransaction/message -> account keys -> top-level instructions -> CPI/inner instructions -> logs/meta/balances/return data -> traitement individuel ultérieur ``` N2 est nommé **STRUCTURAL** parce qu'il décrit une décomposition générique Solana, pas un domaine métier « Core ». Canaris : ```text ksp-store-api -X-> ksp-program-api RawTransaction logMessages -X-> entité RawLog persistante séparée une erreur/absence future de decoder -X-> blocage des autres instructions ``` Toutes les familles N1 ne sont pas obligées de posséder un N2. `RawAccountState` peut par exemple aller directement vers une future étape de decode si aucune décomposition structurelle utile n'est identifiée. ## 7. Extensibilité backend Le canari externe doit démontrer : ```text crate/test backend externe -> dépend de ksp-store-api -> définit son propre backend mémoire -> implémente les contrats/capabilities publics -X-> ksp-store-lib -X-> PostgreSQL ``` `0.3.2` ajoutera ensuite la façade de consommation `ksp-store-lib`. Le backend officiel `ksp-store-postgres-lib` devra satisfaire la même suite de conformance API. ## 8. Threat/API gates futurs | Gate | Attendu | Statut initial | |---------------------------------------------|-------------------------------------------------|----------------| | oversized RAW payload | rejet avant allocation pathologique | `pre.003` | | payload Debug | aucun bytes brut | `pre.003` | | partial source -> RawTransaction | interdit ; contrat complet exigé | `pre.003/004` | | HTTP/WS/gRPC semantic mismatch | détecté par admission matrix | `pre.004` PASS | | account bytes partiels/parsés | refusés avant `RawAccountState` | `pre.004` PASS | | account sans slot durable | refusé comme état persistant | `pre.004` PASS | | account data Debug | aucun bytes brut | `pre.004` PASS | | status surfaces artificiellement fusionnées | aucun modèle commun prématuré | `pre.004` PASS | | duplicate same content | `AlreadyPresent` | `pre.006` | | duplicate divergent content | Conflict stable | `pre.006` | | partial transaction+observation | interdit par atomic acquisition | `pre.005` PASS | | event-only -> Store capability | absent par défaut | `pre.004/007` | | Interface/Store duplicate model | absent | `pre.007` | | page limit 0 | rejet ; aucun max KSP artificiel | `pre.006` | | SQL/backend cursor leak | absent | `pre.006/007` | | external backend | implémente API sans Store lib | `pre.005` PASS | | processing `bool` comme vérité | absent ; future preuve version-aware documentée | `pre.006/007` | | purge sans policy/evidence | impossible par contrat | `pre.006/007` | | tombstone supprimé avec payload | interdit | `pre.006` | | rebackfill normal après purge | skip | `pre.006` | | force rehydrate implicite | interdit | `pre.006` | | N2/N3/N4 creep | aucune surface | `pre.007` | ### 8.1 Matérialisation `pre.003` La tranche implémente et couvre localement par canaris source/audit : ```text RawNetworkId RawTransactionSignature [u8; 64] RawTransactionReference RawFormatId RawContentHash [u8; 32] RawObservationKey [u8; 32] RawTimestamp RawAcquisitionOrigin RawProvenanceCode RawAcquisitionProvenance RawPayload RawTransaction RawTransactionObservation ``` Gates matérialisés : ```text Core-only dependency firewall conservé payload KSP source-independent, non vide, version > 0 payload maximum Store = 16 MiB source payload metadata maximum = 64 MiB Debug RawPayload ne rend jamais les bytes signature/observation key/hash ne dépendent d'aucune PK backend provenance sans URL/source payload observed_at <= received_at logs transactionnels restent dans le payload RawTransaction aucun RawLog/N2 STRUCTURAL/backend/runtime ajouté ``` La complétude sémantique d'une source HTTP/WS/gRPC vers le format canonique n'est pas simulée dans Store API : elle reste le gate d'admission/conversion de `pre.004`. `pre.003` exige seulement qu'un `RawTransaction` reçoive un `RawPayload` déjà canonique complet selon son format KSP déclaré. ### 8.2 Matérialisation `pre.004` La tranche ajoute : ```text MAX_RAW_ACCOUNT_DATA_BYTES RawAccountStateReference RawAccountState RawAccountObservation ``` Invariants vérifiés par modèle/canaris : ```text référence = network + pubkey + slot + canonical state hash account data complet <= 16 MiB empty account data autorisé Debug RawAccountState ne rend jamais les bytes write_version/transaction_signature/is_startup restent observation-only optionnels HTTP/WS/gRPC source details n'entrent pas dans RawAccountState aucun TransactionStatusObservation artificiel aucun RawLogNotification/RawSlotEvent/RawVoteEvent/RawBlock/YellowstoneEntry public ``` Matrice d'admission validée architecturalement : ```text getAccountInfo/getMultipleAccounts -> oui avec bytes complets getProgramAccounts -> contexte obligatoire accountSubscribe -> oui avec bytes complets programSubscribe -> contexte obligatoire Yellowstone Account -> aucun accounts_data_slice jsonParsed/dataSlice/bare program -> non ``` La conversion source -> modèle reste hors `ksp-store-api`; la crate ne dépend toujours que de `ksp-core-lib`. ### 8.3 Matérialisation `pre.005` La tranche ajoute uniquement des contracts de capability backend-agnostic : ```text StoreApiFuture<'a, T> RawTransactionRead RawTransactionWrite RawTransactionObservationRead RawTransactionObservationWrite RawAccountStateRead RawAccountStateWrite RawAccountObservationRead RawAccountObservationWrite ``` Gates matérialisés : ```text traits Send + Sync et dyn-compatible aucune dépendance async-trait/tokio/futures-util ajoutée backend externe implémentable avec std + ksp-store-api seulement aucun trait StoreBackend monolithique aucune façade runtime Store aucun type Config/backend/SQL public persist_raw_transaction_acquisition = transaction + observation atomiques persist_raw_account_acquisition = account state + observation atomiques record_*_observation = acquisition supplémentaire sans retransmettre le RAW get_* = référence/observation key backend-independent write success/failure seulement en pre.005 outcomes/idempotence/conflict détaillés réservés à pre.006 ``` Le canari `tests/external_backend.rs` définit un backend mémoire externe qui implémente les huit traits sans dépendre de `ksp-store-lib`, PostgreSQL ou d'un runtime async. Il prouve également que chaque capability est utilisable derrière `dyn Trait`. Aucune implémentation de persistence n'est fournie par `ksp-store-api`; les futures concrètes du canari ne servent qu'à vérifier le contrat d'extension. ## 9. Gates de fermeture prévus ### Gate technique final `pre.008` Commandes de référence : ```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éconciliation documentaire `pre.009` Doit fermer : ```text crates/ksp-store-api/README.md crates/ksp-store-api/USAGE.md plan 022 validation 018 indexes/références durables concernées ``` Sans `CHANGELOG.md`, `ROADMAP.md` ni prompt suivant. ### Préparation de publication `pre.010` Doit rester limitée à : ```text Cargo.toml CHANGELOG.md ROADMAP.md prompts/021-V0_3_2_START_PROMPT.md delta pre.010 ``` Le `ROADMAP.md` est réconcilié par `pre.001-fix.001` puis `pre.001-fix.002`; `pre.010` ne doit plus avoir à réparer la taxonomie N1/N2 ni le split backend. ## 10. PostgreSQL reporté à `0.3.2` Aucun gate PostgreSQL live n'est demandé à `0.3.1`. Le prochain prompt devra transformer les invariants API en façade commune + backend de référence et prévoir : ```text ksp-store-lib -> façade Store commune -> réexports utiles de ksp-store-api -> feature postgres par défaut -> dispatch backend selon Config -> erreur backend connu mais non compilé ksp-store-postgres-lib -> tokio-postgres -> pool/TLS audités -> migrations KSP-owned -> schema conformance -> write/read round-trip des modèles API -> idempotence/race -> atomic rollback -> pagination Config -> std.store -> URI/DSN backend lorsque naturel -> secrets ${KSP_SECRET_*} / .env owned par ksp-config-lib -> aucun vrai secret versionné sécurité -> URI/DSN/credentials redacted -> aucune lecture directe env/.env par Store ou backend -> PostgreSQL réel opt-in puis gate final obligatoire ``` ### 8.4 Matérialisation `pre.006` La tranche ajoute les contrats logiques suivants sans backend/runtime : ```text RawPageCursor / RawPageLimit / RawPageRequest / RawPage RawSlotRange / RawSortDirection RawTransactionQuery / RawAccountStateQuery RawEntityWriteOutcome / RawObservationWriteOutcome / RawAcquisitionWriteOutcome RawRetentionState RawTransactionAcquisitionMode RawTransactionTombstone RawTransactionRetentionTransition / RawRetentionWriteOutcome RawTransactionRetentionRead / RawTransactionRetentionWrite ``` Gates matérialisés : ```text page limit 0 rejeté u64::MAX admis comme demande représentable : aucun maximum métier KSP cursor opaque borné à 4 KiB et Debug sans contenu range de slots inversée rejetée queries limitées aux critères de données, sans backlog/executor policy Inserted / AlreadyPresent explicites contenu divergent = ERROR_CODE_RAW_CONFLICT RAW + observation gardent le contrat atomique Full -> Compacted -> Archived -> Purged, avec Full -> Archived autorisé aucun Full -> Purged direct aucun Purged -> Full via transition générique force rehydrate explicite via acquisition transactionnelle tombstone minimal sans payload retention policy choisie hors Store par worker/job/maintenance ``` ## 11. État initial des tranches | Tranche | Objet | État | |-----------|------------------------------------------|-----------------------| | `pre.001` | audit/design/taxonomie/split | PRÊT après gate local | | `pre.002` | scaffold + taxonomie Store API | À FAIRE | | `pre.003` | primitives + RawTransaction | À FAIRE | | `pre.004` | admission matrix + account/status models | PRÊT après gate local | | `pre.005` | backend contracts/capabilities | PRÊT après gate local | | `pre.006` | queries/outcomes/retention/tombstone | PRÊT après gate local | | `pre.007` | boundary/adversarial/completeness | À FAIRE | | `pre.008` | gate technique final | À FAIRE | | `pre.009` | réconciliation documentaire | À FAIRE | | `pre.010` | préparation publication | À FAIRE | | `rel.001` | stable | À FAIRE |