43 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 matérialise le mapping privé et les deux lectures get account ; pre.004-fix.001 corrige la conformité Clippy du mapping Row et un canari inutilisé. Les writes, la pagination, le dispatch Store et les quatre implémentations de capabilities account restent volontairement pending.
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 | RECHECK |
| 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
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 statique ; gate opérateur complet à rejouer.
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.