56 KiB
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 la fondation V002 minimale et pre.002-fix.001 corrige deux canaris sans modifier le SQL ; son gate opérateur complet est PASS. 0.3.4-pre.003 complète les contraintes de domaine, l'index de navigation, la compatibilité externe et le checksum V002 final ; pre.003-fix.001 corrige uniquement l'ordre alphabétique du bloc const et son gate opérateur complet est PASS. pre.004 à pre.007 matérialisent successivement mapping/read, writes, observation supplémentaire et pagination account ; pre.007-fix.001 clôt son gate opérateur en PASS. pre.008 ouvre les quatre capabilities account dans PostgresBackend et Store, portant l'inventaire RAW statique à 10/10 ; pre.008-fix.001 corrige son unique canari mal réconcilié et son gate opérateur complet est PASS. pre.009 matérialise la preuve PostgreSQL live account, la concurrence/cancellation réelle et la coexistence RawTransaction sans modifier les migrations ; son gate standard et son live account sur PostgreSQL 17 sont PASS. pre.010 ferme les canaris cross-family sans modifier le runtime et son gate opérateur complet est PASS. pre.011 ouvre le gate technique final, le replay live ciblé et les graphes Cargo sans nouveau développement.
Base :
v0.3.3
Cible release :
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 :
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 :
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 | 26 CHECK + PK/FK, bornes API exactes | PASS pre.003 |
| index métier | (slot,pubkey,state_hash) non unique / non filtré |
PASS 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 | ff21605e…bdb550e sur 32 resources |
PASS 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 :
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 | PASS |
| pre.003 | contraintes complètes, index, schema compatibility, checksum V002 | PASS |
| pre.004 | mapping privé state/observation + get reads + hostile rows | PASS |
| pre.005 | acquisition atomique state+observation + idempotence/conflict | PASS |
| pre.006 | observation supplémentaire + races/cancellation unitaires | PASS |
| pre.007 | list RawAccountStateQuery + keyset cursor V1 account | PASS |
| pre.008 | 4 impl backend + 4 dispatch Store + conformance 10/10 | PASS |
| pre.009 | preuve PostgreSQL live account + coexistence RawTransaction | CURRENT |
| 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
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_migrationattendaitSchemaNeweravec une fausse version future2, désormais occupée par V002 ;pre_002_v002_resources_are_exactly_two_tables_plus_base_pk_fk_without_indexes_or_domain_checkscomparait la FK à une chaîne sensible au saut de ligne avantREFERENCES.
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 : PASS pre.003. Mapping/lectures account : PENDING pre.004 à ce verdict historique ; writes/pagination/capabilities restent pre.005+.
18. Correctif pre.002-fix.001
Le correctif applique deux recalibrages de tests seulement :
- le canari d'historique construit désormais le préfixe valide V000/V001/V002 puis ajoute une version
3inconnue, ce qui teste réellement le cas « schéma plus récent » ; - 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,BIGINTouTEXTpour 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.1conformément àVER-ID-007/010, car des sources Rust de tests sont modifiées.
Gate opérateur du correctif du 2026-08-30 : PASS complet — audits Rust/Markdown, workspace check, Clippy all-targets, Store API, Store façade, backend PostgreSQL, Config et --no-default-features sont verts.
19. Verdict pre.003
V002 finale : PASS fonctionnel opérateur ; fix de conformité Rust requis.
Inventaire final :
V002 resources = 32
tables = 2
constraints = 29 (2 PK + 1 FK + 26 CHECK)
indexes = 1
final checksum = ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e
provisional pre.002= 30ac87496f1bb3805d816660891d7eab2127c599a636eb40c17ade5926311f55
Contraintes matérialisées : exactitude fixed-width des identités/hash/signature, domaines u64 en NUMERIC(20,0), data account <= 16 MiB, provenance logique 1..128 octets/alphabet sûr, origin fermé aux cinq variantes API, timestamps 0..MAX_RAW_UNIX_MILLIS, observed_at <= received_at, hash source 32 bytes et source size <= 64 MiB. is_startup reste un booléen nullable sans contrainte artificielle.
L'unique index métier V002 est non unique et non filtré :
(slot, pubkey, state_hash)
Aucun index owner/provider/method/time/status n'est ajouté.
Compatibilité de prerelease : le checksum provisoire V002 de pre.002 n'est accepté que lorsque schema_update=update_if_needed. Dans ce cas, les resources manquantes sont matérialisées et vérifiées, la compatibilité externe V002 est contrôlée, puis l'entrée d'historique est mise à jour atomiquement vers le checksum final. Avec schema_update=disabled, le checksum provisoire est rejeté ; tout checksum non explicitement connu reste MigrationMismatch.
Le probe d'adoption couvre V001 + V002. V000/V001 et leurs checksums restent inchangés. Aucune surface repository/runtime account n'est ouverte : src/raw_account.rs absent, aucune implémentation RawAccount* et aucun dispatch Store.
Gate opérateur de pre.003 du 2026-08-30 : cargo check --workspace, Clippy all-targets, Store API, Store façade, backend PostgreSQL (45 tests), Config et --no-default-features sont PASS. L'audit Markdown est PASS. L'unique défaut est RUST-FMT-104 dans src/migration.rs : le bloc const n'est pas intégralement trié alphabétiquement.
20. Correctif pre.003-fix.001
Le correctif est strictement hygiénique : HISTORY_LOAD_SQL est déplacée avant HISTORY_UPDATE_CHECKSUM_SQL. Les valeurs, types et usages des constantes restent identiques.
Invariants du fix :
- aucune ressource V002 modifiée ;
- checksum V002 final inchangé :
ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e; - checksum provisoire
pre.002et bridge de migration inchangés ; - aucune modification de schéma, repository, dispatch ou capability ;
workspace.package.version = 0.3.4-pre.3.fix.1conformément àVER-ID-007/010.
Gate opérateur de pre.003-fix.001 du 2026-08-30 : PASS complet — audits Rust/Markdown, cargo check --workspace, Clippy all-targets, Store API, Store façade, backend PostgreSQL (45 tests), Config et --no-default-features sont verts.
21. Verdict pre.004
Mapping PostgreSQL privé account : PASS final via pre.004-fix.001. Le gate initial pre.004 avait échoué uniquement sur Clippy ; le gate complet du correctif est désormais vert.
Surface matérialisée :
src/raw_account.rs privé
PostgresBackend::get_raw_account_state public bridge backend
PostgresBackend::get_raw_account_observation public bridge backend
RawAccount* trait impl 0/4 — volontairement fermé
write SQL account absent
pagination/cursor account absent
migrations V000/V001/V002 inchangées
Le mapping state couvre slot, lamports et rent_epoch sur tout le domaine u64 via les projections ::text, les largeurs exactes 32 bytes de pubkey/owner/state hash, le booléen executable et les bytes account complets. RawAccountState::try_new fournit la dernière admission backend-agnostic et transforme un payload > 16 MiB en DataInvalid; les comptes à data vide restent valides.
Le mapping observation reconstruit l'identité account, toute la provenance commune et les metadata optionnelles is_startup, transaction_signature et write_version. write_version conserve tout le domaine u64 via NUMERIC(20,0)::text; une signature présente doit faire exactement 64 bytes. L'absence de metadata optionnelle reste None, sans valeur inventée.
Hostile-row guards déterministes couvrent notamment : largeur pubkey/hash/signature invalide, décimal négatif ou > u64::MAX, provenance/origin invalide, ordre temporel impossible et data oversized. Les erreurs restent DataInvalid/ReadFailed/WrongNetwork avec phases statiques et sans écho de valeur hostile.
Le garde réseau de get_raw_account_state est exécuté avant acquisition d'une connexion. get_raw_account_observation reçoit seulement un RawObservationKey; la référence reconstruite utilise donc le réseau mono-backend déjà validé à l'ouverture, comme prévu par le contrat.
Les canaris de scope prouvent encore l'absence de :
INSERT / UPDATE / DELETE account
ON CONFLICT / FOR UPDATE account
list_raw_account_states / cursor KSPA
impl RawAccountStateRead/Write for PostgresBackend
impl RawAccountObservationRead/Write for PostgresBackend
Store dispatch account
Le gate opérateur de pre.004 du 2026-08-30 établit : cargo check --workspace PASS, suites ksp-store-api/ksp-store-lib/ksp-store-postgres-lib/ksp-config-lib PASS, ksp-store-lib --no-default-features PASS et audits Rust/Markdown PASS ; cargo clippy --workspace --all-targets FAIL uniquement sur les 27 usages de ? du nouveau mapping, doublés par 27 retours implicites de closures, avec un warning séparé de variable crate_root inutilisée dans dependency_boundary.rs.
21.1. Correctif pre.004-fix.001
Le correctif remplace exclusivement les map_err(...)? de raw_account_observation_db_row et raw_account_state_db_row par des match typés avec retours explicites DataInvalid, conformément à RUST-ERR-002/003. Il retire également la déclaration crate_root inutilisée du canari pre_003_v002_schema_is_complete_without_account_trait_or_write_dispatch.
Invariants du fix :
- aucun SQL ou bind account modifié ;
- aucun invariant de mapping ou phase d'erreur modifié ;
- aucune migration V000/V001/V002 modifiée ;
- checksum V002 final inchangé :
ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e; - aucun write, cursor, trait account ou dispatch Store ouvert ;
workspace.package.version = 0.3.4-pre.4.fix.1.
Validations exécutées dans l'environnement d'assemblage du fix : audit Rust général/export/workspace PASS, audit Markdown PASS, absence de ? dans src/raw_account.rs confirmée. Gate opérateur du 2026-08-30 : PASS complet — audits Rust/Markdown, workspace check, Clippy all-targets, Store API, Store façade, backend PostgreSQL (52 tests), Config et --no-default-features sont verts.
22. Verdict pre.005
Acquisition atomique account : PASS complet opérateur.
Surface ajoutée :
PostgresBackend::persist_raw_account_acquisition
INSERT state ON CONFLICT DO NOTHING
SELECT state FOR UPDATE sur collision
comparaison exacte du state canonique complet
INSERT observation ON CONFLICT DO NOTHING
SELECT observation FOR UPDATE sur collision
comparaison exacte référence + provenance + metadata optionnelles
COMMIT unique state + observation
Les préconditions state.reference.network == backend.network, observation.account.network == backend.network et observation.account == state.reference sont évaluées avant pool.get(). Une divergence de référence ou de contenu est classée Conflict; un échec physique d'écriture reste WriteFailed, sans texte PostgreSQL conservé.
L'idempotence state ne fait aucune confiance exclusive à state_hash : un même (pubkey, slot, state_hash) n'est AlreadyPresent que si lamports, owner, executable, rent_epoch et data sont identiques. L'idempotence observation compare le modèle RawAccountObservation complet, y compris is_startup, transaction_signature, write_version et toute la provenance.
La transaction commune garantit qu'une collision divergente sur l'observation après insertion d'un nouveau state ne peut pas laisser ce state durable isolément. Aucun has_*, ON CONFLICT DO UPDATE, UPDATE ou DELETE account n'est introduit.
Canaries pre.005 : guard réseau/référence, comparaison state complète, comparaison observation complète, encodage origin fermé, SQL DO NOTHING + FOR UPDATE, bridge backend-agnostic et absence des quatre implémentations RawAccount*.
Le gate opérateur du 2026-08-30 est PASS complet : audits Rust/Markdown, cargo check --workspace, Clippy all-targets, Store API, Store façade, backend PostgreSQL (56 tests unitaires), Config et ksp-store-lib --no-default-features sont verts.
Reste après pre.005 : observation supplémentaire/concurrence ciblée (pre.006), pagination/cursor (pre.007) et implémentations/dispatch 10/10 (pre.008). Les migrations V000/V001/V002 restent inchangées et le checksum V002 final demeure ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e.
23. Verdict pre.006
Observation supplémentaire account : PASS complet opérateur.
Surface ajoutée :
PostgresBackend::record_raw_account_observation
network guard avant pool.get()
SELECT state reference FOR KEY SHARE
state absent -> ReferenceNotFound
INSERT observation ON CONFLICT DO NOTHING
collision -> SELECT observation FOR UPDATE + comparaison exacte
COMMIT unique
Le state référencé doit être déjà durable : record_raw_account_observation ne crée jamais de RawAccountState et ne retransmet pas ses bytes. Le lock FOR KEY SHARE protège la référence pendant l'insertion observation/FK sans prendre un lock de contenu plus fort que nécessaire.
L'idempotence et le conflit réutilisent le chemin persist_account_observation_row déjà validé en pre.005. Une même clé avec observation identique produit AlreadyPresent; une même clé avec contenu/provenance/metadata divergents produit Conflict. Le réseau de l'observation est rejeté avant toute acquisition de connexion lorsqu'il diffère du backend.
La preuve de race/cancellation de cette tranche est volontairement unitaire/structurelle : les canaris imposent l'ordre transaction -> FOR KEY SHARE -> persist/compare -> commit, l'absence de commit anticipé et l'absence de tâche détachée ou de fuite volontaire du handle transactionnel. Une cancellation avant commit droppe donc le Transaction local et laisse le rollback à PostgreSQL. Les scénarios de vraies tâches concurrentes et JoinHandle::abort() restent au test live pre.009.
Restent fermés : list_raw_account_states, cursor KSPA, OFFSET, UPDATE/DELETE, ON CONFLICT DO UPDATE, les quatre implémentations RawAccount* sur PostgresBackend et tout dispatch account dans Store. Les migrations V000/V001/V002 sont inchangées ; le checksum V002 final reste ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e.
24. Verdict pre.007
Pagination RawAccountStateQuery et cursor KSPA : PASS complet opérateur via pre.007-fix.001. Le gate initial avait isolé cinq warnings Clippy clone_on_copy et deux canaris historiques stales ; le fix les corrige sans toucher au code de pagination.
Ordre physique figé :
ASC (slot ASC, pubkey ASC, state_hash ASC)
DESC (slot DESC, pubkey DESC, state_hash DESC)
Deux familles de SELECT sont utilisées pour chaque direction : sans filtre pubkey, l'index V002 (slot, pubkey, state_hash) porte la navigation ; avec filtre pubkey, la PK (pubkey, slot, state_hash) porte le préfixe account puis le même ordre total. Les predicates de continuation utilisent exclusivement les comparaisons tuple > / <. Aucun OFFSET, batch maximum, priorité, clamp ou policy worker n'est introduit.
Le cursor account V1 est fixe à 109 bytes :
0..4 magic KSPA
4 version 1
5..13 last_slot u64 big-endian
13..45 last_pubkey 32 bytes
45..77 last_state_hash 32 bytes
77..109 SHA-256 binding digest
Le digest utilise le domaine KSP/raw-account-state-cursor/v1 et lie le réseau, le filtre pubkey optionnel, la direction, les bornes de slot et la dernière clé complète. Les canaris couvrent round-trip, taille/magic/version/digest hostiles, replay réseau/pubkey/direction/plage et replay cross-family depuis KSPT.
La page conserve la sémantique Store existante : LIMIT + 1 détecte la continuation, le backend n'impose aucun maximum fonctionnel KSP et expose seulement la borne PostgreSQL 9_223_372_036_854_775_806 nécessaire pour convertir requested + 1 en i64. Les rows de liste revalident pubkey/hash à 32 bytes et slot via decimal exact u64, sans narrowing.
Surface runtime ajoutée :
PostgresBackend::list_raw_account_states(
&RawAccountStateQuery,
) -> RawPage<RawAccountStateReference>
Les quatre impl RawAccount* for PostgresBackend et tout dispatch account dans ksp-store-lib::Store restent absents jusqu'à pre.008. Les migrations V000/V001/V002 restent byte-inchangées et le checksum V002 demeure ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e.
25. Verdict pre.007-fix.001
Le fix ne modifie ni src/raw_account.rs, ni src/raw_account/cursor.rs, ni le SQL runtime, ni aucune migration. Les cinq Pubkey::clone() inutiles des tests cursor sont supprimés. Les canaris pre.005/pre.006 sont transformés pour accepter list_raw_account_states, devenu scope légitime en pre.007, tout en continuant d'interdire les surfaces destructives (UPDATE, DELETE, ON CONFLICT DO UPDATE, OFFSET) et les quatre implémentations RawAccount* réservées à pre.008.
Le checksum V002 reste ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e. Le gate opérateur complet du 2026-08-30 est PASS : audits Rust/Markdown, workspace check, Clippy all-targets, Store API, Store façade, backend PostgreSQL (63 tests unitaires), Config et --no-default-features sont verts.
26. Verdict pre.008
Conformance RAW backend/façade : 10/10 matérialisée ; gate initial en échec ciblé sur le canari dependency_boundary.
Inventaire exact attendu et désormais canarisé :
PostgresBackend
RawTransactionRead
RawTransactionWrite
RawTransactionObservationRead
RawTransactionObservationWrite
RawTransactionRetentionRead
RawTransactionRetentionWrite
RawAccountStateRead
RawAccountStateWrite
RawAccountObservationRead
RawAccountObservationWrite
Store
même inventaire exact de 10 capabilities
Les quatre implémentations backend ne créent aucune nouvelle primitive physique : elles adaptent les méthodes étroites get_raw_account_state, list_raw_account_states, persist_raw_account_acquisition, get_raw_account_observation et record_raw_account_observation déjà validées. Les quatre implémentations de Store délèguent uniquement au backend compilé, appliquent validate_operation_network aux inputs réseau-scopés et réutilisent map_postgres_error.
La lecture RawAccountObservationRead reste la seule capability account sans guard réseau pré-dispatch parce que RawObservationKey ne porte aucun réseau ; le backend mono-network reconstruit la référence avec son binding déjà validé. L'acquisition state+observation vérifie séparément les deux réseaux dans la façade puis conserve le guard d'égalité de référence backend avant pool.get().
Canaris transformés :
inventaire exact PostgresBackend = 10
inventaire exact Store = 10
impl account uniquement dans runtime bridge backend
aucun impl account direct dans raw_account.rs
aucun SQL/type PostgreSQL dans ksp-store-lib
validate_operation_network occurrences Store = 14
feature postgres optionnelle inchangée
no-default-features doit continuer à compiler
Aucune migration V000/V001/V002 n'est modifiée ; le checksum V002 reste ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e. ksp-store-api reste inchangée. La preuve réelle PostgreSQL de coexistence, concurrence et cancellation account reste en pre.009.
27. Verdict pre.008-fix.001
Le gate opérateur de pre.008 confirme que les audits Rust/Markdown, le workspace check, ksp-store-api, ksp-store-lib, ksp-config-lib et --no-default-features passent. L'unique blocage est la compilation de ksp-store-postgres-lib/tests/dependency_boundary.rs : runtime est référencé dans le canari d'ownership pre.005 sans y être déclaré, tandis qu'une déclaration devenue inutilisée subsiste dans le canari V002 pre.003.
Le fix déplace uniquement :
let runtime = include_str!("../src/runtime.rs");
vers le canari qui le consomme effectivement. Les assertions de placement 10/10 restent inchangées : les implémentations account demeurent uniques dans runtime.rs, absentes de raw_account.rs, et les couches migration/schema restent sans capability métier. Aucun fichier de production, SQL runtime ou migration V000/V001/V002 n'est modifié.
Le checksum V002 reste ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e. Le gate opérateur complet du 2026-08-30 sur 0.3.4-pre.8.fix.1 est PASS : audits Rust/Markdown, workspace check, Clippy all-targets, Store API, Store façade, backend PostgreSQL (63 tests unitaires + 13 dependency + 7 hardening + 13 public), Config et --no-default-features sont verts.
28. Verdict pre.009
Preuve PostgreSQL live RawAccountState : PASS complet opérateur.
Nouveau test :
crates/ksp-store-postgres-lib/tests/postgres_raw_account_live.rs
#[ignore = "opt-in real PostgreSQL RawAccountState proof; reads one dedicated URI from stdin"]
Invariants d'exploitation :
URI dédiée lue depuis stdin uniquement
aucune lecture d'environnement
aucun affichage de l'URI
refus si une table KSP managée existe déjà
PostgreSQL >= 15
cleanup uniquement après preuve d'absence initiale
reopen health migration_version = 2
Matrice matérialisée :
| Scénario | Preuve pre.009 |
|---|---|
| bootstrap/reopen V002 | oui |
| schema_autoupdate index account | oui |
| slot/lamports/rent_epoch/write_version u64 | oui, u64::MAX |
| data account vide | oui |
| data account 16 MiB exact | oui |
| acquisition insert/idempotence | oui |
| même référence / contenu divergent | Conflict |
| même pubkey+slot / hashes distincts | oui |
| collision observation + rollback state | oui |
| observation supplémentaire identique | AlreadyPresent |
| observation supplémentaire divergente | Conflict |
| référence state absente | ReferenceNotFound |
| metadata Yellowstone | round-trip exact |
| pagination ASC/DESC | oui |
| filtre pubkey | oui |
| cursor cross-query / digest hostile | QueryInvalid |
cursor cross-family KSPT |
QueryInvalid |
| concurrence identique | Inserted + AlreadyPresent |
| concurrence divergente | Inserted + Conflict |
| cancellation réelle | JoinHandle::abort() + state absent |
| coexistence RawTransaction | oui |
| cleanup isolation | oui |
Le test postgres_raw_transaction_live.rs est ajusté uniquement sur son inventaire de schéma managé : il probe et supprime désormais aussi ksp_raw_account_states et ksp_raw_account_observations, nécessaires depuis V002. Aucun scénario métier RawTransaction n'est modifié.
Le gate standard du 2026-08-30 est PASS : audits Rust/Markdown, workspace check, Clippy all-targets, Store API, Store façade, backend PostgreSQL, Config et --no-default-features sont verts. Le live account est ensuite PASS sur PostgreSQL 17 en 2,72 s, avec URI dédiée lue depuis stdin sans écho.
Les migrations V000/V001/V002 restent byte-inchangées ; le checksum V002 reste :
ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e
29. Verdict pre.010
Hardening et complétude cross-family : PASS opérateur complet.
Aucun fichier de production n'est modifié. Les nouveaux canaris backend vérifient que le SQL account reste limité aux lectures, inserts idempotents, locks et pagination keyset ; ils interdisent UPDATE, DELETE, TRUNCATE, OFFSET, ON CONFLICT DO UPDATE et toute policy worker. Le cursor account reste KSPA, domaine KSP/raw-account-state-cursor/v1, 109 bytes, distinct de KSPT.
Le registre V002 est figé à exactement 32 resources : 2 tables, 2 PK, 1 FK observation->state, 26 CHECK et 1 index (slot,pubkey,state_hash) non filtré. Les canaris imposent l'absence de ksp_raw_transactions et ksp_raw_transaction_observations dans V002. Le CHECK de transaction_signature impose seulement 64 bytes lorsqu'elle est présente et aucune FK transaction.
La façade compare directement son inventaire de traits RAW avec celui de PostgresBackend : les deux ensembles doivent être strictement identiques et contenir exactement dix capabilities. Aucune capability account de retention, delete ou compaction n'est admise.
Les fichiers ksp-store-api, ksp-store-lib/src, ksp-store-postgres-lib/src, les tests live et les migrations V000/V001/V002 restent inchangés. Le checksum V002 demeure :
ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e
Le gate opérateur du 2026-08-30 est PASS : audits Rust/Markdown, workspace check, Clippy all-targets, Store API, Store façade, backend PostgreSQL, Config et --no-default-features sont verts. Les canaris supplémentaires de pre.010 passent : inventaire RAW exact 10/10 des deux côtés, V002 exact à 32 resources, SQL account non destructif/keyset-only et séparation cross-family maintenue.
30. Verdict pre.011
Gate technique final : PASS.
Cette tranche ne modifie aucun code de production, test fonctionnel, SQL, migration, Config runtime, manifest de crate, dépendance runtime, README/USAGE, CHANGELOG/ROADMAP ou prompt suivant. Elle synchronise uniquement la version workspace et documente le gate final.
Matrice opérateur attendue :
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-lib --no-default-features
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
Replay PostgreSQL réel ciblé :
read -rsp "Dedicated PostgreSQL URI: " KSP_PG_TEST_URI; echo
printf '%s\n' "$KSP_PG_TEST_URI" | cargo test -p ksp-store-postgres-lib --test postgres_raw_account_live -- --ignored --nocapture --test-threads=1
unset KSP_PG_TEST_URI
Le live account est la preuve finale ciblée : il couvre bootstrap/reopen V002, bornes u64/data, idempotence/conflits, pagination/cursors, concurrence/cancellation et coexistence RawTransaction. Les lives foundation et RawTransaction séparés ne sont pas requis par pre.011.
Le replay opérateur de clôture confirme :
cargo test -p ksp-store-lib --no-default-features PASS
cargo test --workspace PASS
cargo tree façade/backend/features/duplicates exécutés
postgres_raw_account_live PASS — PostgreSQL 17, 3.00 s
Les graphes ne révèlent pas de reverse edge Store ni de dépendance PostgreSQL lorsque la feature façade correspondante est désactivée. Les doublons Cargo observés appartiennent au workspace transverse et ne matérialisent aucune duplication propre à la verticale Store nécessitant un correctif. Le live final lit l'URI dédiée depuis stdin sans l'écho.
Le couloir technique est donc fermé ; aucun pre.011-fix.NNN n'est requis.
31. Verdict pre.012
Réconciliation documentaire finale : matérialisée ; gate documentaire opérateur à exécuter.
Documents durables réconciliés :
crates/ksp-store-lib/README.md
crates/ksp-store-lib/USAGE.md
crates/ksp-store-postgres-lib/README.md
crates/ksp-store-postgres-lib/USAGE.md
docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md
docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md
La documentation décrit maintenant la surface réellement validée : dix capabilities RAW exactes, quatre capabilities RawAccount*, acquisition account atomique/idempotente, observation supplémentaire reference-guarded, pagination keyset (slot,pubkey,state_hash) avec cursor KSPA, séparation stricte avec RawTransaction et absence de rétention/destruction account.
Relus et volontairement inchangés :
README.md racine
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
ksp-config-lib README/USAGE
ksp-store-api — aucun README/USAGE à créer artificiellement
CHANGELOG.md, ROADMAP.md et le prompt de démarrage 0.3.5 restent hors de cette tranche et appartiennent à pre.013. Aucun src/**, test, Config, schema, migration ou dépendance n'est modifié.