32 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 de migration. Les repositories et les quatre 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 | non encore ajoutées | PENDING pre.003 |
| index métier | (slot,pubkey,state_hash) non encore ajouté |
PENDING pre.003 |
| index owner/provider/time/status | aucun prévu | PASS negative scope |
| archive/tombstone account | aucune | PASS negative scope |
| V002 checksum intermédiaire | 30ac8749…26311f55 sur les 5 resources pre.002 |
PROVISIONAL |
| V002 checksum final | après contraintes/index de pre.003 | PENDING pre.003 |
8. Idempotence et concurrence
| Scénario | Résultat attendu | État |
|---|---|---|
| 2 acquisitions identiques, même ref + même observation key | 1 Inserted/Inserted ; 1 AlreadyPresent/AlreadyPresent | PENDING impl/live |
| 2 acquisitions même ref, contenu divergent | un gagnant durable ; autre RAW_CONFLICT |
PENDING impl/live |
| même pubkey+slot, state_hash différents | les deux states sont représentables | PENDING impl/live |
| observation key identique, observations divergentes | une observation gagne ; autre RAW_CONFLICT |
PENDING impl/live |
| record observation identique concurrent | 1 Inserted ; 1 AlreadyPresent | PENDING impl/live |
| record observation divergente concurrent | 1 Inserted ; 1 RAW_CONFLICT |
PENDING impl/live |
| annulation après insertion state avant observation | aucune moitié durable | PENDING impl/live |
Le test de contenu divergent doit comparer les fields complets et ne jamais traiter l'égalité de state_hash comme preuve suffisante.
9. Threat model
| Menace | Mitigation figée | Preuve |
|---|---|---|
| URI pointe vers DB d’un autre réseau | binding ksp_store_identity + guard réseau avant opération |
unit + live |
| pubkey/hash/signature DB de mauvaise largeur | CHECK SQL + mapping DataInvalid statique |
unit hostile rows |
| slot/lamports/rent_epoch/write_version > i64::MAX | NUMERIC(20,0) + conversion décimale fallible | unit limites u64 |
| data account > 16 MiB | admission API + CHECK octet_length <= 16777216 |
unit + live boundary |
| data account vide | aucune borne basse SQL ; vide explicitement valide | unit |
| observation sans state | FK composite + record pré-vérifie state | unit + live |
| échec observation après state insert | une transaction PostgreSQL commune | live cancellation/rollback |
| même ref, contenu divergent | ON CONFLICT DO NOTHING + SELECT FOR UPDATE + comparaison exacte | unit + live concurrent |
| même pubkey/slot, hashes distincts | PK (pubkey,slot,state_hash) |
unit + live |
| collision observation key | comparaison exacte de tous les champs ; conflict sinon | unit + live |
| cursor transaction injecté dans account | magic/domain account distincts | unit cursor |
| cursor account rejoué sur autre query | digest lie network/pubkey/range/direction/last key | unit cursor matrix |
| ordre même slot ambigu | ordre total (slot,pubkey,state_hash) |
unit + live pagination |
| metadata Yellowstone promue au state | is_startup/transaction_signature/write_version uniquement observation | schema + mapping canary |
| transaction_signature impose transaction row | aucune FK vers ksp_raw_transactions |
schema canary |
| SQL/server text fuit | projection PostgresBackendError statique | hardening |
| V000/V001 modifiées | checksums figés et canaries exactes | migration tests |
| index non justifié | un seul index métier V002 pour query unfiltered | schema completeness |
10. Audit historique kbot3
| Élément | Classement | Disposition |
|---|---|---|
| unicité observation_key et FK comme invariants physiques | REPRENDRE | garder le principe, avec clés KSP actuelles |
| index account+slot pour navigation déterministe | REPRENDRE | réutiliser le principe seulement pour les queries réellement exposées |
| NUMERIC(20,0) pour lamports/rent_epoch | REPRENDRE | étendre à slot/write_version pour le domaine u64 complet |
| BIGSERIAL/id technique comme identité | REDESSINER | identité durable = (pubkey, slot, state_hash) |
| pubkey/owner/hash en TEXT/base58 | REDESSINER | BYTEA fixed-width |
account data en base64 + space indépendant |
REDESSINER | BYTEA complet ; longueur dérivée ; aucun double truth |
| observation duplique l’état canonique | REDESSINER | observation référence un state complet séparé |
| slot BIGINT/i64 | REDESSINER | NUMERIC(20,0) sans narrowing |
| normalisation N1 -> CORE + processing ledger/version | REPORTER | future CORE/jobs/workers ; hors Store RAW 0.3.4 |
| batch maximum 1000 / policy de sélection | REPORTER | policy worker/job ; pas pagination Store |
| indexes owner/provider/method/time/status | REPORTER | aucune query publique actuelle ne les justifie |
| status/error_message persistés dans observation RAW | REJETER | observation réussie uniquement ; erreurs sûres hors ligne métier |
| sqlx et Store monolithique | REJETER | backend tokio-postgres séparé derrière ksp-store-lib |
| maintenance CASCADE/TRUNCATE comme surface normale | REJETER | aucune destructive capability account |
Le pipeline historique k_sol_obs_account_observations -> k_sol_core_account_states reste une source de comparaison, pas une architecture cible. La normalisation versionnée et ses caps de batch sont hors scope 0.3.4.
11. Canaries 0.3.3 à faire évoluer
À l'ouverture effective de la surface account :
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 | FIXED |
| pre.004 | mapping privé state/observation + get reads + hostile rows | PLANNED |
| pre.005 | acquisition atomique state+observation + idempotence/conflict | PLANNED |
| pre.006 | observation supplémentaire + races/cancellation unitaires | PLANNED |
| pre.007 | list RawAccountStateQuery + keyset cursor V1 account | PLANNED |
| pre.008 | 4 impl backend + 4 dispatch Store + conformance 10/10 | PLANNED |
| pre.009 | preuve PostgreSQL live account + coexistence RawTransaction | PLANNED |
| pre.010 | hardening/completeness cross-family + canaries ownership | PLANNED |
| pre.011 | gate technique final + replay live ciblé + graphes | PLANNED |
| pre.012 | réconciliation docs finale sans rouvrir le code | PLANNED |
| pre.013 | publication prep minimale + prompt 0.3.5 | PLANNED |
| rel.001 | stabilisation/tag après gate opérateur | PLANNED |
15. Negative scope final
| Surface | Doit rester absent en 0.3.4 |
|---|---|
| ksp-store-api redesign | oui |
| account retention/archive/purge | oui |
| CORE/STRUCTURAL persistence | oui |
| workers/jobs/backfills | oui |
| Store batch/priorité policy | oui |
| owner/provider/time/status queries/indexes | oui |
| Transport -> Store coupling | oui |
| PostgreSQL types via ksp-store-lib | oui |
| nouveau backend | oui |
16. Gate opérateur prévu pour clôture technique
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 : PENDING pre.003. Repository et capabilities account : PENDING pre.004+.
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.
L'audit Rust du fix doit être PASS avant pre.004. Le gate Cargo complet du fix reste à rejouer par l'opérateur.