# Validation `0.3.3` — Store/PostgreSQL RawTransaction vertical slice ## 1. Objet Cette validation accompagne : ```text 0.3.3 — Store/PostgreSQL RawTransaction vertical slice ``` Elle démarre en `0.3.3-pre.001` comme matrice de preuve. Les lignes non encore implémentées restent explicitement `À FAIRE`; elles ne sont pas présentées comme acquises. ## 2. Baseline stable | Preuve | Attendu `pre.001` | Statut | |-----------------------------------------|--------------------------------------------------------------------|--------| | Base de travail | archive/tag `v0.3.2` | PASS | | workspace package version avant overlay | `0.3.2` | PASS | | Gate opérateur `0.3.2` | audits/check/clippy/tests Store+Config/no-default-features propres | PASS | | PostgreSQL live métier `0.3.2` | absent ; seule fondation opt-in existe | PASS | | Prompt release | `prompts/022-V0_3_3_START_PROMPT.md` | PASS | | Archive historique kbot3 | audit ciblé uniquement | PASS | ## 2.1 Inventaire exact `0.3.2` ```text ksp-store-api modules=capability,error,model root pub use=60 ksp-store-lib modules=constants,error,health,settings,store root pub use=84, default feature=postgres ksp-store-postgres-lib modules=constants,error,health,migration,runtime root pub use=7 V000__bootstrap.sql SHA-256=d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450 std.store format_version 1 std.store profiles devnet/devnet, mainnet/mainnet-beta, testnet/testnet foundation live PASS PostgreSQL 17 en pre.008 et pre.010 ``` Le test live est `#[ignore]` dans les gates ordinaires mais sa preuve réelle antérieure est documentée dans `019-V0_3_2_STORE_POSTGRES_FOUNDATION.md`. ## 3. Scope exact des capacités | Capacité | Backend PostgreSQL | Façade Store | Preuve finale | |----------------------------------|--------------------|--------------|---------------------------| | `RawTransactionRead` | À FAIRE | À FAIRE | unit + integration + live | | `RawTransactionWrite` | À FAIRE | À FAIRE | unit + concurrency live | | `RawTransactionObservationRead` | À FAIRE | À FAIRE | unit + live | | `RawTransactionObservationWrite` | À FAIRE | À FAIRE | unit + concurrency live | | `RawTransactionRetentionRead` | À FAIRE | À FAIRE | unit + live | | `RawTransactionRetentionWrite` | À FAIRE | À FAIRE | unit + race live | Aucune capacité `RawAccountState`, structurale N2, job ou worker n'est admise dans cette matrice. ### 3.1 Matrice `RawTransactionRead` | Axe | Preuve/attendu | |----------------------|-------------------------------------------------------------| | input | reference pour get; query pour list | | invariants pré-I/O | network exact; cursor lié à query | | transaction SQL | aucune; statement cohérent | | rows touchées | transaction + archive pour get; index transaction pour list | | idempotence | N/A | | conflit | N/A; corruption != conflict métier | | absence/not-found | get None; list vide | | error mapping | query/wrong-network/data/query-failed sûrs | | concurrence | statement snapshot; aucun snapshot inter-pages | | test déterministe | full/archive/purged, u64 max, ranges, cursor hostile | | test PostgreSQL live | get exact + pages multi-slot/ties dans deux directions | ### 3.2 Matrice `RawTransactionWrite` | Axe | Preuve/attendu | |----------------------|-------------------------------------------------------------------| | input | transaction + observation + acquisition mode | | invariants pré-I/O | deux networks exacts; observation reference = canonical reference | | transaction SQL | une transaction atomique | | rows touchées | canonical + observation + archive si état existant Archived | | idempotence | contenu exact identique -> AlreadyPresent | | conflit | divergence même signature -> ERROR_CODE_RAW_CONFLICT | | absence/not-found | insert; Purged Normal compatible -> SkippedPurged/NotRecorded | | error mapping | API model/conflict; physical write/data sûrs | | concurrence | unique insert + FOR UPDATE du gagnant | | test déterministe | equality/conflict/tombstone/modes/redaction | | test PostgreSQL live | identical/divergent concurrent + rollback + force | ### 3.3 Matrice `RawTransactionObservationRead` | Axe | Preuve/attendu | |----------------------|-----------------------------------------------| | input | observation key | | invariants pré-I/O | key API valide; database network déjà bound | | transaction SQL | aucune | | rows touchées | observation | | idempotence | N/A | | conflit | N/A | | absence/not-found | None | | error mapping | stored-row invalid/query-failed sûrs | | concurrence | observation immuable, statement snapshot | | test déterministe | provenance complète/optionnelle + hostile row | | test PostgreSQL live | observation round-trip exact | ### 3.4 Matrice `RawTransactionObservationWrite` | Axe | Preuve/attendu | |----------------------|-----------------------------------------------------------| | input | observation | | invariants pré-I/O | observation transaction network exact | | transaction SQL | oui | | rows touchées | canonical verrouillé + observation | | idempotence | same key + fields identical -> AlreadyPresent | | conflit | same key + divergence -> ERROR_CODE_RAW_CONFLICT | | absence/not-found | canonical absent -> safe not-found; Purged -> NotRecorded | | error mapping | provenance/conflict API + not-found/write sûrs | | concurrence | canonical FOR UPDATE + unique observation key | | test déterministe | equality/missing/purged/error canaries | | test PostgreSQL live | identical/divergent observation races | ### 3.5 Matrice `RawTransactionRetentionRead` | Axe | Preuve/attendu | |----------------------|----------------------------------------------| | input | transaction reference | | invariants pré-I/O | network exact | | transaction SQL | aucune | | rows touchées | canonical metadata | | idempotence | N/A | | conflit | N/A | | absence/not-found | state unknown -> None; tombstone only Purged | | error mapping | wrong-network/stored-metadata invalid sûrs | | concurrence | état committé par statement | | test déterministe | Full/Archived/Purged + tombstone minimal | | test PostgreSQL live | reads après chaque transition | ### 3.6 Matrice `RawTransactionRetentionWrite` | Axe | Preuve/attendu | |----------------------|---------------------------------------------------------| | input | retention transition | | invariants pré-I/O | network exact; Compacted non supporté rejeté avant pool | | transaction SQL | oui | | rows touchées | canonical + archive | | idempotence | current == target -> AlreadyAtTarget | | conflit | expected perdu -> ExpectedStateMismatch | | absence/not-found | unknown reference -> safe not-found | | error mapping | invalid transition API; unsupported/physical write sûrs | | concurrence | FOR UPDATE canonique | | test déterministe | transition matrix + Compacted unsupported | | test PostgreSQL live | transition/purge/write/force races | ## 4. Design physique V001 | Élément | Décision `pre.001` | Statut | |-------------------|----------------------------------------------------|-------------| | Migration | V001 embedded, après généralisation du moteur V000 | PASS design | | Database identity | singleton `ksp_store_identity` | PASS design | | Réseau | un réseau exact par base V001 | PASS design | | Transaction key | signature `BYTEA(64)` logique | PASS design | | Observation key | `BYTEA(32)` | PASS design | | Slot | `NUMERIC(20,0)` ; aucun narrowing `u64` | PASS design | | Format version | `BIGINT`, check domaine `u32` non nul | PASS design | | Timestamp | `BIGINT`, check borne `RawTimestamp` | PASS design | | Payload | `BYTEA`, 1..=16 MiB | PASS design | | Archive | relation dédiée hors index hot-path | PASS design | | Purge | tombstone minimal dans ligne canonique | PASS design | | Compacted | non simulé ; rejet stable tant qu'aucun codec réel | PASS design | | Navigation | index partiel `(slot, signature)` hors purged | PASS design | ## 5. Inventaire SQL minimal prévu ### 5.1 Tables ```text ksp_store_identity ksp_raw_transactions ksp_raw_transaction_observations ksp_raw_transaction_archive_payloads ``` Statut `pre.001` : **design seulement**. Aucun de ces objets V001 n'est encore créé. ### 5.2 Indexes ```text PK ksp_raw_transactions(signature) PK ksp_raw_transaction_observations(observation_key) PK ksp_raw_transaction_archive_payloads(signature) ix_ksp_raw_transactions_slot_signature(slot, signature) WHERE retention_state <> 'purged' ``` Tout index additionnel doit être justifié par une requête effectivement ajoutée à la release. ## 6. Mapping numérique sans narrowing | Valeur API | Représentation SQL | Cas limites obligatoires | |-----------------------|----------------------------------------------------|-------------------------------------------| | `slot: u64` | `NUMERIC(20,0)` | 0, `i64::MAX`, `i64::MAX + 1`, `u64::MAX` | | `format_version: u32` | `BIGINT` | 1, `i32::MAX + 1`, `u32::MAX` | | `RawTimestamp` | `BIGINT` | 0, max documenté, max+1 DB hostile | | source payload size | `BIGINT` | 0, 64 MiB, >64 MiB DB hostile | | page limit | bind signé de `limit+1` seulement si représentable | `i64::MAX - 1`, valeur supérieure rejetée | Critère : aucune branche ne doit utiliser `as i64`, `as i32`, clamp ou saturation pour faire tenir une valeur valide API. ## 7. Binding réseau ### 7.1 Bootstrap | Cas | Résultat attendu | |-------------------------------------------|------------------------------------------------------------------------| | V001 appliquée pour la première fois | identity créée avec le réseau runtime dans la transaction de bootstrap | | réouverture même réseau | succès idempotent | | réouverture autre réseau | erreur terminale sûre | | V001 présente mais identity absente | mismatch/corruption, jamais rebind silencieuse | | identity malformée | erreur terminale sûre | | migration pending + auto_migrate disabled | backend non prêt, aucune persistence métier | ### 7.2 Pré-I/O capability Pour tout input portant un `RawNetworkId`, un mismatch avec `Store.network` ou `PostgresBackend.network` doit être détecté avant acquisition d'un client du pool. ## 8. Idempotence canonique | État existant | Input | Mode | Outcome attendu | |--------------------------------|-------------------------------|----------------|-------------------------------------------| | absent | canonique+observation valides | Normal | `Inserted / Inserted` | | Full identique | observation identique | Normal | `AlreadyPresent / AlreadyPresent` | | Full identique | observation nouvelle | Normal | `AlreadyPresent / Inserted` | | Full divergent | n'importe quelle observation | Normal | `ERROR_CODE_RAW_CONFLICT`, aucun write | | Archived identique | observation nouvelle | Normal | `AlreadyPresent / Inserted` | | Purged compatible | observation | Normal | `SkippedPurged / NotRecorded` | | Purged divergent sur tombstone | observation | Normal | `ERROR_CODE_RAW_CONFLICT` | | Purged compatible | observation | ForceRehydrate | `Rehydrated / Inserted ou AlreadyPresent` | | Purged divergent | observation | ForceRehydrate | `ERROR_CODE_RAW_CONFLICT` | L'égalité canonique compare les octets réels lorsqu'ils sont encore retenus. Le content hash seul n'est jamais une preuve suffisante dans `Full` ou `Archived`. ## 9. Atomicité canonique + observation ### 9.1 Cas obligatoires | Scénario | Preuve attendue | |--------------------------------------------------------------|----------------------------------| | nouvel insert, observation valide | les deux commit | | nouvel insert, observation divergente sur key déjà existante | rollback canonique + observation | | canonical conflict | aucune nouvelle observation | | cancellation avant commit | aucun succès partiel visible | | erreur SQL observation | rollback canonique | ### 9.2 FK Le FK observation -> transaction est : - non nullable ; - sans `ON DELETE SET NULL` ; - impossible à contourner dans le repository métier ; - cohérent avec le fait que purge conserve la ligne canonique/tombstone. ## 10. Concurrence | Race | Attendu | |-----------------------------------------|-------------------------------------------------------------| | deux inserts identiques même signature | un `Inserted`, l'autre `AlreadyPresent`; données identiques | | deux inserts divergents même signature | un gagnant, l'autre `ERROR_CODE_RAW_CONFLICT` | | deux observations identiques même key | un `Inserted`, l'autre `AlreadyPresent` | | deux observations divergentes même key | un gagnant, l'autre conflit | | deux transitions vers même target | un transitionne, l'autre `AlreadyAtTarget` | | transitions avec expected incompatibles | gagnant déterministe + `ExpectedStateMismatch` | | purge vs acquisition | sérialisation sur ligne canonique | | purge vs ForceRehydrate | aucun état moitié purgé/moitié full | | deux ForceRehydrate | idempotence ou conflit selon contenu | La preuve live doit utiliser de vraies tâches concurrentes, pas une simulation séquentielle renommée « concurrency ». ## 11. Observation write séparée | Cas | Attendu | |-----------------------------------------|----------------------------------------| | canonique Full présent, key absente | `Inserted` | | canonique Archived présent, key absente | `Inserted` | | key identique | `AlreadyPresent` | | key divergente | `ERROR_CODE_RAW_CONFLICT` | | canonique absent | erreur sûre, aucune création implicite | | canonique Purged | `NotRecorded` | | mauvais réseau | rejet pré-I/O | L'absence du canonical requis est classée `store.raw_reference_not_found`. Ce code runtime ne modifie pas les invariants de construction de `ksp-store-api`. ## 12. Lecture ### 12.1 RawTransaction | État physique | `get_raw_transaction` | |-----------------------------------------|------------------------------------------------| | Full cohérent | `Some(RawTransaction)` depuis payload hot | | Archived cohérent | `Some(RawTransaction)` depuis archive relation | | Purged | `None` | | row inconnue | `None` | | état/bytes/taille/numérique incohérents | erreur sûre de donnée physique | ### 12.2 Observation | Cas | Résultat | |----------------------------------|--------------------------------------------------------| | key connue | observation reconstruite avec network du backend bound | | key inconnue | `None` | | bytes/provenance invalides en DB | erreur sûre | ## 13. Pagination ### 13.1 Ordre ```text Ascending = (slot ASC, signature ASC) Descending = (slot DESC, signature DESC) ``` ### 13.2 Dataset obligatoire Le test doit contenir au minimum : - plusieurs slots ; - au moins trois signatures distinctes sur le même slot ; - suffisamment de rows pour trois pages ; - une row Purged exclue de la liste ; - bornes start/end présentes et absentes. ### 13.3 Cursor V1 | Test | Attendu | |----------------------------|---------------------------------| | round-trip | exact | | taille != 109 | rejet | | magic modifié | rejet | | version inconnue | rejet | | digest modifié | rejet | | direction différente | rejet | | range différente | rejet | | network différent | rejet | | last slot hors range | rejet | | random bytes <=4096 | rejet sans panic/echo | | cursor >4096 au niveau API | déjà rejeté par `RawPageCursor` | ### 13.4 Pas de cap métier Aucun test ne doit imposer 100, 500 ou 1000 comme limite Store. La seule erreur de taille côté backend doit correspondre à la représentation physique nécessaire au `LIMIT + 1`. ## 14. Rétention ### 14.1 États supportés physiquement en `0.3.3` ```text Full Archived Purged ``` `Compacted` reste un état logique API connu, explicitement optionnel depuis le plan `0.3.1`, mais non représenté mensongèrement par PostgreSQL `0.3.3`. ### 14.2 Matrice | Current | Expected | Target | Attendu PostgreSQL 0.3.3 | |-------------------|-----------|-----------|------------------------------------------------------------| | Full | Full | Archived | `Transitioned` | | Archived | Full | Archived | `AlreadyAtTarget` | | Archived | Archived | Purged | `Transitioned` | | Purged | Archived | Purged | `AlreadyAtTarget` | | Full | Archived | Purged | input transition invalide au niveau API | | Full | Full | Compacted | `store.postgres_retention_compaction_unsupported`, pré-I/O | | Compacted attendu | Compacted | Archived | `store.postgres_retention_compaction_unsupported`, pré-I/O | | Full | Archived | Archived | `ExpectedStateMismatch` | ### 14.3 Tombstone Après purge, la reconstruction autorisée contient exactement : ```text reference slot format_id format_version content_hash ``` Le block time et les octets de payload ne restent pas dans la table hot ni dans la table archive. ## 15. ForceRehydrate | Cas | Attendu | |--------------------------------------|------------------------------------------------| | Purged + tombstone compatible | restaure Full et payload, outcome `Rehydrated` | | Purged + slot divergent | conflict | | Purged + format id/version divergent | conflict | | Purged + content hash divergent | conflict | | Full/Archived identique + Force | idempotence normale, pas faux `Rehydrated` | | race avec purge | sérialisation ; état final cohérent | ## 16. Erreurs et redaction ### 16.1 Canary hostile Les tests devront injecter des valeurs sentinelles dans : ```text URI server error text SQL text simulé signature observation key content hash payload provider/protocol/method cursor bytes row DB invalide ``` Aucune sentinelle ne doit apparaître dans `Display`, `Debug` ou message sûr retourné. ### 16.2 Classification | Classe | Interface attendue | |----------------------------------------|---------------------------------------------------------------| | input/model invalide | codes `ksp-store-api` acquis | | canonical/observation divergent | `ERROR_CODE_RAW_CONFLICT` | | retention representation non supportée | `store.postgres_retention_compaction_unsupported` | | réseau incohérent | code statique, pas de réseau hostile rendu | | DB row invalide | code statique de corruption/data invalid | | query/write PostgreSQL | code statique backend/runtime | | pool/cancel/migration | catégories runtime existantes ou extension statique justifiée | SQLSTATE et texte PostgreSQL ne font pas partie du contrat public. ## 17. Migration engine ### 17.1 Multi-version Preuves nécessaires avant V001 : - V000 reste checksum-identique ; - registre embedded ordonné `[V000, V001]` ; - historique vide -> bootstrap correct ; - V000 seule + auto migrate -> V001 appliquée ; - V000 seule + auto migrate disabled -> pending/non-ready ; - V001 checksum divergent -> mismatch ; - version > V001 -> schema newer ; - ligne de migration manquante/divergente -> mismatch ; - deux open concurrents -> advisory lock, historique unique. ### 17.2 Binding réseau atomique L'identité réseau doit être créée/validée avant commit du bootstrap V001. Un crash ne doit pas pouvoir laisser V001 « appliquée » avec une base prête mais sans identité exploitable. ## 18. Boundaries Les scans/tests doivent garantir : ```text ksp-store-lib -X-> tokio-postgres/deadpool-postgres/rustls ksp-store-lib -X-> SQL literal ksp-store-lib -X-> std::env / dotenv / libpq files ksp-store-postgres-lib -X-> ksp-store-lib ksp-store-postgres-lib -X-> direct Config ownership ksp-store-api -X-> backend physical dependency ``` La façade ne publie aucun `Client`, `Transaction`, pool, TLS type, SQL row ou type backend. ## 19. Feature matrix | Build | Attendu | |---------------------------------------|----------------------------------------------------------------------------| | `ksp-store-lib` default | feature postgres active, six capacités dispatchables une fois implémentées | | `ksp-store-lib --no-default-features` | compile ; aucune dépendance backend tirée | | config backend postgres sans feature | erreur `backend_not_compiled` avant I/O | | malformed postgres URI avec feature | erreur safe pré-I/O comme `0.3.2` | ## 20. Audit historique — preuves de non-régression Les tests/source scans devront empêcher le retour des patterns historiques rejetés : ```text has_raw_transaction_signature BIGSERIAL id comme identity publique slot BIGINT dans V001 canonical_json JSONB processing_state ON DELETE SET NULL raw SQL error text dans Error cap 500/1000 dans Store pagination ``` ## 21. Planning de validation par tranche ### `pre.001` - documents plan/validation ; - version `0.3.3-pre.1` ; - aucun SQL métier ; - gate workspace actuel. ### `pre.002` - migration registry multi-version ; - tests V000 conservés ; - préparation binding. ### `pre.003` - V001 + tables/constraints/indexes ; - binding réseau ; - checksum/inventory tests. ### `pre.004` - row codecs et lectures ; - malformed DB matrix. ### `pre.005` - atomic writes/idempotence/conflict ; - observation writes. ### `pre.006` - list/cursor/order/ranges ; - hostile cursor matrix. ### `pre.007` - archive/purge/tombstone/ForceRehydrate ; - Compacted unsupported stable. ### `pre.008` - façade/feature dispatch ; - no physical leak ; - pre-I/O mismatch. ### `pre.009` - PostgreSQL live opt-in complet. ### `pre.010` - hardening/completeness. ### `pre.011` - gate technique final. ### `pre.012` - réconciliation documentaire. ### `pre.013` - publication prep. ### `rel.001` - publication stable. ## 22. Gate courant `pre.001` ```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.3 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-store-api cargo test -p ksp-store-lib cargo test -p ksp-store-postgres-lib cargo test -p ksp-config-lib cargo check -p ksp-store-lib --no-default-features ``` ## 23. Critère de clôture de release `0.3.3` ne pourra être déclarée stable que si : - les six capacités sont réellement implémentées sur `PostgresBackend` et dispatchées par `Store` ; - aucune valeur valide API n'est narrowée ; - l'idempotence distingue strictement identité identique et contenu divergent ; - canonical+observation est atomique ; - pagination est déterministe et cursorisée ; - réseau physique incorrect est refusé ; - rétention supportée est honnête et atomique ; - `Compacted` n'est pas prétendu sans représentation réelle ; - tombstone et ForceRehydrate respectent les contrats acquis ; - les erreurs ne fuient aucun secret/SQL/server text ; - le live PostgreSQL opt-in couvre concurrence et rollback ; - tous les gates techniques/doc sont propres ; - aucun scope `RawAccountState`/worker/app/N2 n'a été ouvert.