# Validation `0.3.4` — Store/PostgreSQL `RawAccountState` + complétude RAW ## 1. Portée Cette matrice est ouverte par `0.3.4-pre.001`. `0.3.4-pre.002` matérialise uniquement la fondation V002 : registry, deux tables et PK/FK de base. Son gate opérateur révèle deux canaris unitaires obsolètes sans défaut de migration ; `0.3.4-pre.002-fix.001` les corrige sans modifier les resources SQL. Les contraintes de domaine, l'index, la compatibilité externe complète, les repositories et les quatre capabilities account restent volontairement pending. Base : ```text v0.3.3 ``` Cible release : ```text PostgresBackend = 10/10 capabilities RAW Store = 10/10 capabilities RAW ``` ## 2. Gate base `pre.001` | Preuve | Attendu | État pre.001 | |------------------------------------|------------------|-------------------------------------------------| | workspace.package.version base | 0.3.3 | PASS audit | | V000 checksum | d29068b8…46a1450 | PASS recalcul indépendant | | V001 checksum | 31488cda…d72cc51 | PASS recalcul indépendant / 40 resources | | capabilities transaction physiques | 6 | PASS inventaire source | | capabilities account physiques | 0 | PASS inventaire source ; volontairement pending | | canaries anti-account 0.3.3 | présentes | PASS inventaire tests | | ksp-store-api change nécessaire | non | PASS audit | | SQL V002 créé en pre.001 | non | PASS scope | ## 3. Matrice capability finale | Capability | Base v0.3.3 | Cible 0.3.4 | Preuve finale | |--------------------------------|-------------|-------------|-------------------------------| | RawTransactionRead | PASS | PASS | régression suite 0.3.3 | | RawTransactionWrite | PASS | PASS | régression suite 0.3.3 | | RawTransactionObservationRead | PASS | PASS | régression suite 0.3.3 | | RawTransactionObservationWrite | PASS | PASS | régression suite 0.3.3 | | RawTransactionRetentionRead | PASS | PASS | régression suite 0.3.3 | | RawTransactionRetentionWrite | PASS | PASS | régression suite 0.3.3 | | RawAccountStateRead | PENDING | PASS | tests backend + façade + live | | RawAccountStateWrite | PENDING | PASS | tests backend + façade + live | | RawAccountObservationRead | PENDING | PASS | tests backend + façade + live | | RawAccountObservationWrite | PENDING | PASS | tests backend + façade + live | ### 3.1 Contrat opérationnel détaillé des quatre capabilities account | Capability | Input | Output | Guard réseau | SQL/transaction | Idempotence | Conflit | Absence/référence | Pagination | Error mapping | Preuve live | |----------------------------|---------------------|---------------------------------|------------------------------------------------------------------------------|--------------------------------------------------------|----------------------------------|----------------------------------------|-----------------------------------|---------------------|---------------------------------------------------------------------------|------------------------------------------------| | RawAccountStateRead | reference / query | Option / Page | reference/query network == backend | SELECT ; list keyset | n/a | n/a | get absent -> None | oui, cursor account | WrongNetwork, QueryInvalid, PageLimitUnsupported, DataInvalid, ReadFailed | get exact + list matrix | | RawAccountStateWrite | state + observation | RawAcquisitionWriteOutcome | state + observation networks + exact same reference | transaction state+observation | exact existing -> AlreadyPresent | same full ref divergent -> RawConflict | n/a | non | WrongNetwork, Conflict, DataInvalid, WriteFailed | atomicité + identical/divergent + cancellation | | RawAccountObservationRead | observation_key | Option | aucun input réseau ; backend fournit son network à la référence reconstruite | SELECT by PK | n/a | n/a | absent -> None | non | DataInvalid, ReadFailed | round-trip optional metadata | | RawAccountObservationWrite | observation | RawObservationWriteOutcome | observation.account.network == backend | transaction ; state FOR KEY SHARE + observation insert | exact existing -> AlreadyPresent | same key divergent -> RawConflict | state absent -> ReferenceNotFound | non | WrongNetwork, ReferenceNotFound, Conflict, DataInvalid, WriteFailed | missing ref + collision + concurrence | ## 4. Matrice `RawAccountState` | Champ | Représentation décidée | Validation requise | |------------|------------------------------------------|-----------------------------------------------------------------| | network | binding DB existant `ksp_store_identity` | pas de colonne par ligne ; guard exact avant I/O | | pubkey | BYTEA NOT NULL | `octet_length = 32` ; composant PK | | slot | NUMERIC(20,0) NOT NULL | 0..18446744073709551615 ; aucun narrowing | | state_hash | BYTEA NOT NULL | `octet_length = 32` ; composant PK ; jamais recalculé par Store | | lamports | NUMERIC(20,0) NOT NULL | domaine u64 exact | | owner | BYTEA NOT NULL | `octet_length = 32` | | executable | BOOLEAN NOT NULL | mapping direct | | rent_epoch | NUMERIC(20,0) NOT NULL | domaine u64 exact | | data | BYTEA NOT NULL | 0..=16 MiB ; vide autorisé ; aucun base64 | Points structurants : ```text identity = network API + (pubkey, slot, state_hash) network SQL = singleton ksp_store_identity, pas colonne row PK state = (pubkey, slot, state_hash) state_hash n'est pas une preuve suffisante d'idempotence : comparaison contenu exacte obligatoire empty data = valide ``` ## 5. Matrice observation | Champ | Représentation décidée | Validation requise | |---------------------------|------------------------|--------------------------------------------------| | observation_key | BYTEA NOT NULL | PK family-local ; collision = comparaison exacte | | account.pubkey | BYTEA NOT NULL | FK composite vers state | | account.slot | NUMERIC(20,0) NOT NULL | domaine u64 exact ; FK composite | | account.state_hash | BYTEA NOT NULL | FK composite | | provider | TEXT NOT NULL | 1..128 octets + alphabet logique stable V001 | | protocol | TEXT NOT NULL | idem V001 | | acquisition_method | TEXT NOT NULL | idem V001 | | origin | TEXT NOT NULL | backfill/import/live/repair/replay | | received_at | BIGINT NOT NULL | 0..253402300799999 | | capture_session_id | TEXT NULL | mêmes checks V001 | | commitment | TEXT NULL | mêmes checks V001 | | endpoint_id | TEXT NULL | mêmes checks V001 | | filter_id | TEXT NULL | mêmes checks V001 | | observed_at | BIGINT NULL | borne + `observed_at <= received_at` | | source_payload_hash | BYTEA NULL | NULL ou 32 bytes | | source_payload_size_bytes | BIGINT NULL | 0..=64 MiB ; borne API tient dans BIGINT | | is_startup | BOOLEAN NULL | Yellowstone metadata observation-only | | transaction_signature | BYTEA NULL | NULL ou 64 bytes ; aucune FK transaction | | write_version | NUMERIC(20,0) NULL | NULL ou domaine u64 exact | Invariants additionnels : ```text FK observation -> state composite record_raw_account_observation n'insère jamais le state transaction_signature observation-only, sans FK transaction write_version/is_startup observation-only observation_key family-local unique ``` ## 6. Query/pagination | Élément | Décision | Canari requis | |-----------------|------------------------------------------------|--------------------------------| | ordre ASC | slot, pubkey, state_hash ASC | multi-page sans duplicate/skip | | ordre DESC | slot, pubkey, state_hash DESC | symétrique ASC | | filtre pubkey | égalité optionnelle | cursor lié au filtre | | slot range | inclusive | cursor lié aux deux bornes | | cursor magic | KSPA | rejette KSPT transaction | | cursor version | 1 | rejette version hostile | | cursor longueur | 109 bytes | rejette taille hostile | | cursor domain | KSP/raw-account-state-cursor/v1 | digest family-bound | | cursor digest | network+pubkey filter+range+direction+last key | anti-replay query | | pagination | keyset seulement | aucun OFFSET | | limit | limite physique PostgreSQL uniquement | pas de batch policy | ## 7. Migration/schema V002 | Objet | Décision / état physique | État | |----------------------------------|--------------------------------------------------------------------------|---------------------| | V000 | bytes/checksum inchangés | PASS | | V001 | 40 resources + checksum inchangés | PASS | | V002 logical name | `raw_account_state` | PASS pre.002 | | table state | `ksp_raw_account_states` | PASS pre.002 | | table observation | `ksp_raw_account_observations` | PASS pre.002 | | PK state | `(pubkey,slot,state_hash)` | PASS pre.002 | | PK observation | `(observation_key)` | PASS pre.002 | | FK observation -> state | `(account_pubkey,account_slot,account_state_hash)` -> state composite PK | PASS pre.002 | | contraintes domaine/fixed-width | non encore ajoutées | PENDING pre.003 | | index métier | `(slot,pubkey,state_hash)` non encore ajouté | PENDING pre.003 | | index owner/provider/time/status | aucun prévu | PASS negative scope | | archive/tombstone account | aucune | PASS negative scope | | V002 checksum intermédiaire | `30ac8749…26311f55` sur les 5 resources pre.002 | PROVISIONAL | | V002 checksum final | après contraintes/index de pre.003 | PENDING pre.003 | ## 8. Idempotence et concurrence | Scénario | Résultat attendu | État | |------------------------------------------------------------|-------------------------------------------------------|-------------------| | 2 acquisitions identiques, même ref + même observation key | 1 Inserted/Inserted ; 1 AlreadyPresent/AlreadyPresent | PENDING impl/live | | 2 acquisitions même ref, contenu divergent | un gagnant durable ; autre `RAW_CONFLICT` | PENDING impl/live | | même pubkey+slot, state_hash différents | les deux states sont représentables | PENDING impl/live | | observation key identique, observations divergentes | une observation gagne ; autre `RAW_CONFLICT` | PENDING impl/live | | record observation identique concurrent | 1 Inserted ; 1 AlreadyPresent | PENDING impl/live | | record observation divergente concurrent | 1 Inserted ; 1 `RAW_CONFLICT` | PENDING impl/live | | annulation après insertion state avant observation | aucune moitié durable | PENDING impl/live | Le test de contenu divergent doit comparer les fields complets et ne jamais traiter l'égalité de `state_hash` comme preuve suffisante. ## 9. Threat model | Menace | Mitigation figée | Preuve | |---------------------------------------------------|-----------------------------------------------------------------------|----------------------------| | URI pointe vers DB d’un autre réseau | binding `ksp_store_identity` + guard réseau avant opération | unit + live | | pubkey/hash/signature DB de mauvaise largeur | CHECK SQL + mapping `DataInvalid` statique | unit hostile rows | | slot/lamports/rent_epoch/write_version > i64::MAX | NUMERIC(20,0) + conversion décimale fallible | unit limites u64 | | data account > 16 MiB | admission API + CHECK `octet_length <= 16777216` | unit + live boundary | | data account vide | aucune borne basse SQL ; vide explicitement valide | unit | | observation sans state | FK composite + record pré-vérifie state | unit + live | | échec observation après state insert | une transaction PostgreSQL commune | live cancellation/rollback | | même ref, contenu divergent | ON CONFLICT DO NOTHING + SELECT FOR UPDATE + comparaison exacte | unit + live concurrent | | même pubkey/slot, hashes distincts | PK `(pubkey,slot,state_hash)` | unit + live | | collision observation key | comparaison exacte de tous les champs ; conflict sinon | unit + live | | cursor transaction injecté dans account | magic/domain account distincts | unit cursor | | cursor account rejoué sur autre query | digest lie network/pubkey/range/direction/last key | unit cursor matrix | | ordre même slot ambigu | ordre total `(slot,pubkey,state_hash)` | unit + live pagination | | metadata Yellowstone promue au state | is_startup/transaction_signature/write_version uniquement observation | schema + mapping canary | | transaction_signature impose transaction row | aucune FK vers `ksp_raw_transactions` | schema canary | | SQL/server text fuit | projection PostgresBackendError statique | hardening | | V000/V001 modifiées | checksums figés et canaries exactes | migration tests | | index non justifié | un seul index métier V002 pour query unfiltered | schema completeness | ## 10. Audit historique kbot3 | Élément | Classement | Disposition | |----------------------------------------------------------|------------|-----------------------------------------------------------------------| | unicité observation_key et FK comme invariants physiques | REPRENDRE | garder le principe, avec clés KSP actuelles | | index account+slot pour navigation déterministe | REPRENDRE | réutiliser le principe seulement pour les queries réellement exposées | | NUMERIC(20,0) pour lamports/rent_epoch | REPRENDRE | étendre à slot/write_version pour le domaine u64 complet | | BIGSERIAL/id technique comme identité | REDESSINER | identité durable = `(pubkey, slot, state_hash)` | | pubkey/owner/hash en TEXT/base58 | REDESSINER | BYTEA fixed-width | | account data en base64 + `space` indépendant | REDESSINER | BYTEA complet ; longueur dérivée ; aucun double truth | | observation duplique l’état canonique | REDESSINER | observation référence un state complet séparé | | slot BIGINT/i64 | REDESSINER | NUMERIC(20,0) sans narrowing | | normalisation N1 -> CORE + processing ledger/version | REPORTER | future CORE/jobs/workers ; hors Store RAW 0.3.4 | | batch maximum 1000 / policy de sélection | REPORTER | policy worker/job ; pas pagination Store | | indexes owner/provider/method/time/status | REPORTER | aucune query publique actuelle ne les justifie | | status/error_message persistés dans observation RAW | REJETER | observation réussie uniquement ; erreurs sûres hors ligne métier | | sqlx et Store monolithique | REJETER | backend tokio-postgres séparé derrière ksp-store-lib | | maintenance CASCADE/TRUNCATE comme surface normale | REJETER | aucune destructive capability account | Le pipeline historique `k_sol_obs_account_observations -> k_sol_core_account_states` reste une source de comparaison, pas une architecture cible. La normalisation versionnée et ses caps de batch sont hors scope `0.3.4`. ## 11. Canaries `0.3.3` à faire évoluer À l'ouverture effective de la surface account : ```text ksp-store-lib/tests/dependency_boundary.rs remplacer interdiction RawAccount par exigence des 4 impl + absence types SQL ksp-store-lib/tests/hardening_completeness.rs remplacer inventaire 6 + RawAccount fermé par inventaire exact 10 ksp-store-postgres-lib/tests/dependency_boundary.rs ajouter module raw_account privé et exiger les 4 impl dans runtime bridge conserver V000 et health business-free ksp-store-postgres-lib/tests/hardening_completeness.rs remplacer interdiction RawAccount globale par inventaire exact 10 remplacer l'absence V002/account par checks de surface V002 minimale ``` Les tests historiques nommés `pre_XXX` peuvent conserver leurs noms si le contrat qu'ils vérifient reste valide ; seul leur contenu doit être élargi lorsqu'il fige intentionnellement le scope `0.3.3`. ## 12. Taxonomie d'erreur à prouver | Cas | Code/projection attendue | État | |---------------------------------------------------|------------------------------------------------------|--------------| | mauvais réseau opération | WrongNetwork -> `store.wrong_network` | PENDING impl | | référence absente pour observation supplémentaire | ReferenceNotFound -> `store.raw_reference_not_found` | PENDING impl | | contenu divergent / collision observation | Conflict -> `store_api.raw_conflict` | PENDING impl | | cursor/query invalide | QueryInvalid -> `store_api.raw_query_invalid` | PENDING impl | | page > limite physique PostgreSQL | PageLimitUnsupported | PENDING impl | | row DB incompatible/malformée | DataInvalid | PENDING impl | | échec SELECT | ReadFailed | PENDING impl | | échec INSERT/transaction | WriteFailed | PENDING impl | | V002 absente/incompatible/divergente | MigrationMismatch / MigrationFailed / SchemaNewer | PENDING impl | Aucune nouvelle variante backend n'est justifiée au gate `pre.001`. ## 13. Preuve live finale Le test live account devra être opt-in/ignored, URI stdin, sans environnement ni fuite de secret. Il doit couvrir V002 et rejouer un canari transaction dans la même base afin de prouver la coexistence cross-family. | Scénario live | Obligation | |-----------------------------------------|-------------| | schema update V001 -> V002 | obligatoire | | reopen idempotent | obligatoire | | u64 > i64::MAX / u64::MAX | obligatoire | | data empty / 16 MiB | obligatoire | | identical/divergent acquisition | obligatoire | | same pubkey+slot / distinct hash | obligatoire | | additional observation + missing ref | obligatoire | | collision observation | obligatoire | | keyset filtered/unfiltered ASC/DESC | obligatoire | | cursor hostile/cross-query/cross-family | obligatoire | | concurrency | obligatoire | | cancellation rollback | obligatoire | | RawTransaction regression canary | obligatoire | | cleanup isolation | obligatoire | ## 14. Sizing | Tranche | Objet | État | |---------|-------------------------------------------------------------------|---------| | pre.001 | audit, kbot3, threat model, V002 design, sizing, plan/validation | DONE | | pre.002 | V002 registry + deux tables + PK/FK de base, sans repository | FIX.001 | | pre.003 | contraintes complètes, index, schema compatibility, checksum V002 | PLANNED | | pre.004 | mapping privé state/observation + get reads + hostile rows | PLANNED | | pre.005 | acquisition atomique state+observation + idempotence/conflict | PLANNED | | pre.006 | observation supplémentaire + races/cancellation unitaires | PLANNED | | pre.007 | list RawAccountStateQuery + keyset cursor V1 account | PLANNED | | pre.008 | 4 impl backend + 4 dispatch Store + conformance 10/10 | PLANNED | | pre.009 | preuve PostgreSQL live account + coexistence RawTransaction | PLANNED | | pre.010 | hardening/completeness cross-family + canaries ownership | PLANNED | | pre.011 | gate technique final + replay live ciblé + graphes | PLANNED | | pre.012 | réconciliation docs finale sans rouvrir le code | PLANNED | | pre.013 | publication prep minimale + prompt 0.3.5 | PLANNED | | rel.001 | stabilisation/tag après gate opérateur | PLANNED | ## 15. Negative scope final | Surface | Doit rester absent en 0.3.4 | |--------------------------------------------|-----------------------------| | ksp-store-api redesign | oui | | account retention/archive/purge | oui | | CORE/STRUCTURAL persistence | oui | | workers/jobs/backfills | oui | | Store batch/priorité policy | oui | | owner/provider/time/status queries/indexes | oui | | Transport -> Store coupling | oui | | PostgreSQL types via ksp-store-lib | oui | | nouveau backend | oui | ## 16. Gate opérateur prévu pour clôture technique ```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.4 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 cargo test --workspace cargo tree -p ksp-store-lib --edges normal cargo tree -p ksp-store-lib -e features cargo tree -p ksp-store-postgres-lib --edges normal cargo tree --duplicates ``` Les preuves live restent séparées et explicitement `--ignored --nocapture`. ## 17. Verdict `pre.002` Fondation V002 minimale : **PASS statique**. Le registry logique V002 expose exactement cinq resources et aucune surface repository/runtime account. Gate opérateur du 2026-08-30 : audits Rust/Markdown, `cargo check --workspace`, Clippy all-targets, `ksp-store-api`, `ksp-store-lib`, `ksp-config-lib` et `ksp-store-lib --no-default-features` passent. `cargo test -p ksp-store-postgres-lib` exécute 43 tests unitaires et échoue uniquement sur deux canaris obsolètes : - `pre_003_fix_001_newer_history_is_rejected_without_down_migration` attendait `SchemaNewer` avec une fausse version future `2`, désormais occupée par V002 ; - `pre_002_v002_resources_are_exactly_two_tables_plus_base_pk_fk_without_indexes_or_domain_checks` comparait la FK à une chaîne sensible au saut de ligne avant `REFERENCES`. Ces deux défauts appartiennent strictement au couloir `pre.002` et sont corrigés par `pre.002-fix.001`. Ils ne remettent en cause ni les cinq resources V002 ni leur checksum. Contraintes complètes/index/schema compatibility/checksum final : **PENDING `pre.003`**. Repository et capabilities account : **PENDING `pre.004+`**. ## 18. Correctif `pre.002-fix.001` Le correctif applique deux recalibrages de tests seulement : 1. le canari d'historique construit désormais le préfixe valide V000/V001/V002 puis ajoute une version `3` inconnue, ce qui teste réellement le cas « schéma plus récent » ; 2. le canari de fondation V002 normalise les espaces ASCII du SQL embedded avant ses assertions sémantiques, sans relâcher l'inventaire exact des cinq resources ni les interdictions `CREATE INDEX`, `CHECK`, cascade, `BIGSERIAL`, `BIGINT` ou `TEXT` pour les données account. Invariants du correctif : - aucune ressource sous `migrations/v002_raw_account_state/` modifiée ; - checksum V002 intermédiaire inchangé : `30ac87496f1bb3805d816660891d7eab2127c599a636eb40c17ade5926311f55` ; - V000/V001 inchangées ; - aucun module repository account, dispatch Store ou implémentation `RawAccount*` ouvert ; - `workspace.package.version = 0.3.4-pre.2.fix.1` conformément à `VER-ID-007/010`, car des sources Rust de tests sont modifiées. Gate opérateur du correctif : **À REJOUER**.