Files
khadhroony-solana-project/docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md

47 KiB
Raw Blame History

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 ; son gate initial échoue uniquement sur un canari dependency_boundary mal réconcilié et pre.008-fix.001 corrige ce déplacement de variable sans toucher à la verticale. La preuve PostgreSQL live reste réservée à pre.009.

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 dun 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 CURRENT
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

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

  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 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.002 et bridge de migration inchangés ;
  • aucune modification de schéma, repository, dispatch ou capability ;
  • workspace.package.version = 0.3.4-pre.3.fix.1 conformé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 doit être rejoué sur 0.3.4-pre.8.fix.1.