32 KiB
Validation 0.3.3 — Store/PostgreSQL RawTransaction vertical slice
1. Objet
Cette validation accompagne :
0.3.3 — Store/PostgreSQL RawTransaction vertical slice
Elle démarre en 0.3.3-pre.001 comme matrice de preuve. Les gates opérateur de pre.001 et pre.002 sont verts. Les lignes non encore implémentées restent explicitement À FAIRE; elles ne sont pas présentées comme acquises.
2. Baseline stable
| Preuve | Attendu pre.001 |
Statut |
|---|---|---|
| Base de travail | archive/tag v0.3.2 |
PASS |
| workspace package version avant overlay | 0.3.2 |
PASS |
Gate opérateur 0.3.2 |
audits/check/clippy/tests Store+Config/no-default-features propres | PASS |
PostgreSQL live métier 0.3.2 |
absent ; seule fondation opt-in existe | PASS |
| Prompt release | prompts/022-V0_3_3_START_PROMPT.md |
PASS |
| Archive historique kbot3 | audit ciblé uniquement | PASS |
2.1 Inventaire exact 0.3.2
ksp-store-api modules=capability,error,model root pub use=60
ksp-store-lib modules=constants,error,health,settings,store root pub use=84, default feature=postgres
ksp-store-postgres-lib modules=constants,error,health,migration,runtime root pub use=7
V000__bootstrap.sql SHA-256=d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
std.store format_version 1
std.store profiles devnet/devnet, mainnet/mainnet-beta, testnet/testnet
foundation live PASS PostgreSQL 17 en pre.008 et pre.010
Le test live est #[ignore] dans les gates ordinaires mais sa preuve réelle antérieure est documentée dans 019-V0_3_2_STORE_POSTGRES_FOUNDATION.md.
3. Scope exact des capacités
| Capacité | Backend PostgreSQL | Façade Store | Preuve finale |
|---|---|---|---|
RawTransactionRead |
À FAIRE | À FAIRE | unit + integration + live |
RawTransactionWrite |
À FAIRE | À FAIRE | unit + concurrency live |
RawTransactionObservationRead |
À FAIRE | À FAIRE | unit + live |
RawTransactionObservationWrite |
À FAIRE | À FAIRE | unit + concurrency live |
RawTransactionRetentionRead |
À FAIRE | À FAIRE | unit + live |
RawTransactionRetentionWrite |
À FAIRE | À FAIRE | unit + race live |
Aucune capacité RawAccountState, structurale N2, job ou worker n'est admise dans cette matrice.
3.1 Matrice RawTransactionRead
| Axe | Preuve/attendu |
|---|---|
| input | reference pour get; query pour list |
| invariants pré-I/O | network exact; cursor lié à query |
| transaction SQL | aucune; statement cohérent |
| rows touchées | transaction + archive pour get; index transaction pour list |
| idempotence | N/A |
| conflit | N/A; corruption != conflict métier |
| absence/not-found | get None; list vide |
| error mapping | query/wrong-network/data/query-failed sûrs |
| concurrence | statement snapshot; aucun snapshot inter-pages |
| test déterministe | full/archive/purged, u64 max, ranges, cursor hostile |
| test PostgreSQL live | get exact + pages multi-slot/ties dans deux directions |
3.2 Matrice RawTransactionWrite
| Axe | Preuve/attendu |
|---|---|
| input | transaction + observation + acquisition mode |
| invariants pré-I/O | deux networks exacts; observation reference = canonical reference |
| transaction SQL | une transaction atomique |
| rows touchées | canonical + observation + archive si état existant Archived |
| idempotence | contenu exact identique -> AlreadyPresent |
| conflit | divergence même signature -> ERROR_CODE_RAW_CONFLICT |
| absence/not-found | insert; Purged Normal compatible -> SkippedPurged/NotRecorded |
| error mapping | API model/conflict; physical write/data sûrs |
| concurrence | unique insert + FOR UPDATE du gagnant |
| test déterministe | equality/conflict/tombstone/modes/redaction |
| test PostgreSQL live | identical/divergent concurrent + rollback + force |
3.3 Matrice RawTransactionObservationRead
| Axe | Preuve/attendu |
|---|---|
| input | observation key |
| invariants pré-I/O | key API valide; database network déjà bound |
| transaction SQL | aucune |
| rows touchées | observation |
| idempotence | N/A |
| conflit | N/A |
| absence/not-found | None |
| error mapping | stored-row invalid/query-failed sûrs |
| concurrence | observation immuable, statement snapshot |
| test déterministe | provenance complète/optionnelle + hostile row |
| test PostgreSQL live | observation round-trip exact |
3.4 Matrice RawTransactionObservationWrite
| Axe | Preuve/attendu |
|---|---|
| input | observation |
| invariants pré-I/O | observation transaction network exact |
| transaction SQL | oui |
| rows touchées | canonical verrouillé + observation |
| idempotence | same key + fields identical -> AlreadyPresent |
| conflit | same key + divergence -> ERROR_CODE_RAW_CONFLICT |
| absence/not-found | canonical absent -> safe not-found; Purged -> NotRecorded |
| error mapping | provenance/conflict API + not-found/write sûrs |
| concurrence | canonical FOR UPDATE + unique observation key |
| test déterministe | equality/missing/purged/error canaries |
| test PostgreSQL live | identical/divergent observation races |
3.5 Matrice RawTransactionRetentionRead
| Axe | Preuve/attendu |
|---|---|
| input | transaction reference |
| invariants pré-I/O | network exact |
| transaction SQL | aucune |
| rows touchées | canonical metadata |
| idempotence | N/A |
| conflit | N/A |
| absence/not-found | state unknown -> None; tombstone only Purged |
| error mapping | wrong-network/stored-metadata invalid sûrs |
| concurrence | état committé par statement |
| test déterministe | Full/Archived/Purged + tombstone minimal |
| test PostgreSQL live | reads après chaque transition |
3.6 Matrice RawTransactionRetentionWrite
| Axe | Preuve/attendu |
|---|---|
| input | retention transition |
| invariants pré-I/O | network exact; Compacted non supporté rejeté avant pool |
| transaction SQL | oui |
| rows touchées | canonical + archive |
| idempotence | current == target -> AlreadyAtTarget |
| conflit | expected perdu -> ExpectedStateMismatch |
| absence/not-found | unknown reference -> safe not-found |
| error mapping | invalid transition API; unsupported/physical write sûrs |
| concurrence | FOR UPDATE canonique |
| test déterministe | transition matrix + Compacted unsupported |
| test PostgreSQL live | transition/purge/write/force races |
4. Design physique V001
| Élément | Décision pre.001 |
Statut |
|---|---|---|
| Migration | V001 embedded, après généralisation du moteur V000 | PASS design |
| Database identity | singleton ksp_store_identity |
PASS design |
| Réseau | un réseau exact par base V001 | PASS design |
| Transaction key | signature BYTEA(64) logique |
PASS design |
| Observation key | BYTEA(32) |
PASS design |
| Slot | NUMERIC(20,0) ; aucun narrowing u64 |
PASS design |
| Format version | BIGINT, check domaine u32 non nul |
PASS design |
| Timestamp | BIGINT, check borne RawTimestamp |
PASS design |
| Payload | BYTEA, 1..=16 MiB |
PASS design |
| Archive | relation dédiée hors index hot-path | PASS design |
| Purge | tombstone minimal dans ligne canonique | PASS design |
| Compacted | non simulé ; rejet stable tant qu'aucun codec réel | PASS design |
| Navigation | index partiel (slot, signature) hors purged |
PASS design |
5. Inventaire SQL minimal prévu
5.1 Tables
ksp_store_identity
ksp_raw_transactions
ksp_raw_transaction_observations
ksp_raw_transaction_archive_payloads
Statut pre.003 : matérialisé dans V001. Les quatre tables ci-dessus sont créées par la migration embedded V001__raw_transaction.sql; aucune capability/repository ne les consomme encore.
5.2 Indexes
PK ksp_raw_transactions(signature)
PK ksp_raw_transaction_observations(observation_key)
PK ksp_raw_transaction_archive_payloads(signature)
ix_ksp_raw_transactions_slot_signature(slot, signature)
WHERE retention_state <> 'purged'
Tout index additionnel doit être justifié par une requête effectivement ajoutée à la release.
6. Mapping numérique sans narrowing
| Valeur API | Représentation SQL | Cas limites obligatoires |
|---|---|---|
slot: u64 |
NUMERIC(20,0) |
0, i64::MAX, i64::MAX + 1, u64::MAX |
format_version: u32 |
BIGINT |
1, i32::MAX + 1, u32::MAX |
RawTimestamp |
BIGINT |
0, max documenté, max+1 DB hostile |
| source payload size | BIGINT |
0, 64 MiB, >64 MiB DB hostile |
| page limit | bind signé de limit+1 seulement si représentable |
i64::MAX - 1, valeur supérieure rejetée |
Critère : aucune branche ne doit utiliser as i64, as i32, clamp ou saturation pour faire tenir une valeur valide API.
7. Binding réseau
7.1 Bootstrap
| Cas | Résultat attendu |
|---|---|
| V001 appliquée pour la première fois | identity créée avec le réseau runtime dans la transaction de bootstrap |
| réouverture même réseau | succès idempotent |
| réouverture autre réseau | erreur terminale sûre |
| V001 présente mais identity absente | mismatch/corruption, jamais rebind silencieuse |
| identity malformée | erreur terminale sûre |
| migration pending + auto_migrate disabled | backend non prêt, aucune persistence métier |
7.2 Pré-I/O capability
Pour tout input portant un RawNetworkId, un mismatch avec Store.network ou PostgresBackend.network doit être détecté avant acquisition d'un client du pool.
8. Idempotence canonique
| État existant | Input | Mode | Outcome attendu |
|---|---|---|---|
| absent | canonique+observation valides | Normal | Inserted / Inserted |
| Full identique | observation identique | Normal | AlreadyPresent / AlreadyPresent |
| Full identique | observation nouvelle | Normal | AlreadyPresent / Inserted |
| Full divergent | n'importe quelle observation | Normal | ERROR_CODE_RAW_CONFLICT, aucun write |
| Archived identique | observation nouvelle | Normal | AlreadyPresent / Inserted |
| Purged compatible | observation | Normal | SkippedPurged / NotRecorded |
| Purged divergent sur tombstone | observation | Normal | ERROR_CODE_RAW_CONFLICT |
| Purged compatible | observation | ForceRehydrate | Rehydrated / Inserted ou AlreadyPresent |
| Purged divergent | observation | ForceRehydrate | ERROR_CODE_RAW_CONFLICT |
L'égalité canonique compare les octets réels lorsqu'ils sont encore retenus. Le content hash seul n'est jamais une preuve suffisante dans Full ou Archived.
9. Atomicité canonique + observation
9.1 Cas obligatoires
| Scénario | Preuve attendue |
|---|---|
| nouvel insert, observation valide | les deux commit |
| nouvel insert, observation divergente sur key déjà existante | rollback canonique + observation |
| canonical conflict | aucune nouvelle observation |
| cancellation avant commit | aucun succès partiel visible |
| erreur SQL observation | rollback canonique |
9.2 FK
Le FK observation -> transaction est :
- non nullable ;
- sans
ON DELETE SET NULL; - impossible à contourner dans le repository métier ;
- cohérent avec le fait que purge conserve la ligne canonique/tombstone.
10. Concurrence
| Race | Attendu |
|---|---|
| deux inserts identiques même signature | un Inserted, l'autre AlreadyPresent; données identiques |
| deux inserts divergents même signature | un gagnant, l'autre ERROR_CODE_RAW_CONFLICT |
| deux observations identiques même key | un Inserted, l'autre AlreadyPresent |
| deux observations divergentes même key | un gagnant, l'autre conflit |
| deux transitions vers même target | un transitionne, l'autre AlreadyAtTarget |
| transitions avec expected incompatibles | gagnant déterministe + ExpectedStateMismatch |
| purge vs acquisition | sérialisation sur ligne canonique |
| purge vs ForceRehydrate | aucun état moitié purgé/moitié full |
| deux ForceRehydrate | idempotence ou conflit selon contenu |
La preuve live doit utiliser de vraies tâches concurrentes, pas une simulation séquentielle renommée « concurrency ».
11. Observation write séparée
| Cas | Attendu |
|---|---|
| canonique Full présent, key absente | Inserted |
| canonique Archived présent, key absente | Inserted |
| key identique | AlreadyPresent |
| key divergente | ERROR_CODE_RAW_CONFLICT |
| canonique absent | erreur sûre, aucune création implicite |
| canonique Purged | NotRecorded |
| mauvais réseau | rejet pré-I/O |
L'absence du canonical requis est classée store.raw_reference_not_found. Ce code runtime ne modifie pas les invariants de construction de ksp-store-api.
12. Lecture
12.1 RawTransaction
| État physique | get_raw_transaction |
|---|---|
| Full cohérent | Some(RawTransaction) depuis payload hot |
| Archived cohérent | Some(RawTransaction) depuis archive relation |
| Purged | None |
| row inconnue | None |
| état/bytes/taille/numérique incohérents | erreur sûre de donnée physique |
12.2 Observation
| Cas | Résultat |
|---|---|
| key connue | observation reconstruite avec network du backend bound |
| key inconnue | None |
| bytes/provenance invalides en DB | erreur sûre |
13. Pagination
13.1 Ordre
Ascending = (slot ASC, signature ASC)
Descending = (slot DESC, signature DESC)
13.2 Dataset obligatoire
Le test doit contenir au minimum :
- plusieurs slots ;
- au moins trois signatures distinctes sur le même slot ;
- suffisamment de rows pour trois pages ;
- une row Purged exclue de la liste ;
- bornes start/end présentes et absentes.
13.3 Cursor V1
| Test | Attendu |
|---|---|
| round-trip | exact |
| taille != 109 | rejet |
| magic modifié | rejet |
| version inconnue | rejet |
| digest modifié | rejet |
| direction différente | rejet |
| range différente | rejet |
| network différent | rejet |
| last slot hors range | rejet |
| random bytes <=4096 | rejet sans panic/echo |
| cursor >4096 au niveau API | déjà rejeté par RawPageCursor |
13.4 Pas de cap métier
Aucun test ne doit imposer 100, 500 ou 1000 comme limite Store. La seule erreur de taille côté backend doit correspondre à la représentation physique nécessaire au LIMIT + 1.
14. Rétention
14.1 États supportés physiquement en 0.3.3
Full
Archived
Purged
Compacted reste un état logique API connu, explicitement optionnel depuis le plan 0.3.1, mais non représenté mensongèrement par PostgreSQL 0.3.3. Le code store.postgres_retention_compaction_unsupported est matérialisé dès pre.003 dans ksp-store-postgres-lib et ksp-store-lib avec la même valeur KSP ; son usage opérationnel par les transitions sera branché en pre.007.
14.2 Matrice
| Current | Expected | Target | Attendu PostgreSQL 0.3.3 |
|---|---|---|---|
| Full | Full | Archived | Transitioned |
| Archived | Full | Archived | AlreadyAtTarget |
| Archived | Archived | Purged | Transitioned |
| Purged | Archived | Purged | AlreadyAtTarget |
| Full | Archived | Purged | input transition invalide au niveau API |
| Full | Full | Compacted | store.postgres_retention_compaction_unsupported, pré-I/O |
| Compacted attendu | Compacted | Archived | store.postgres_retention_compaction_unsupported, pré-I/O |
| Full | Archived | Archived | ExpectedStateMismatch |
14.3 Tombstone
Après purge, la reconstruction autorisée contient exactement :
reference
slot
format_id
format_version
content_hash
Le block time et les octets de payload ne restent pas dans la table hot ni dans la table archive.
15. ForceRehydrate
| Cas | Attendu |
|---|---|
| Purged + tombstone compatible | restaure Full et payload, outcome Rehydrated |
| Purged + slot divergent | conflict |
| Purged + format id/version divergent | conflict |
| Purged + content hash divergent | conflict |
| Full/Archived identique + Force | idempotence normale, pas faux Rehydrated |
| race avec purge | sérialisation ; état final cohérent |
16. Erreurs et redaction
16.1 Canary hostile
Les tests devront injecter des valeurs sentinelles dans :
URI
server error text
SQL text simulé
signature
observation key
content hash
payload
provider/protocol/method
cursor bytes
row DB invalide
Aucune sentinelle ne doit apparaître dans Display, Debug ou message sûr retourné.
16.2 Classification
| Classe | Interface attendue |
|---|---|
| input/model invalide | codes ksp-store-api acquis |
| canonical/observation divergent | ERROR_CODE_RAW_CONFLICT |
| retention representation non supportée | store.postgres_retention_compaction_unsupported |
| réseau incohérent | code statique, pas de réseau hostile rendu |
| DB row invalide | code statique de corruption/data invalid |
| query/write PostgreSQL | code statique backend/runtime |
| pool/cancel/migration | catégories runtime existantes ou extension statique justifiée |
SQLSTATE et texte PostgreSQL ne font pas partie du contrat public.
17. Migration engine
17.1 Multi-version
Preuves acquises en pre.002 avant création réelle de V001 :
- V000 reste byte-identique et conserve le checksum
d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450; - le registre embedded privé est ordonné et commence à V000 ;
- le moteur valide un historique comme préfixe exact du registre ;
- un registre synthétique
[V000, V001]prouve qu'un historique V000 seul retourne l'index pending V001 sans être classé mismatch ; - nom/checksum divergent, historique vide avec metadata existante ou trou de préfixe -> mismatch ;
- version supérieure au registre connu -> schema newer, sans down migration ;
auto_migrate = falserefuse un registre incomplet parmigration_pending;- les migrations pending sont appliquées dans l'ordre sous la transaction et l'advisory lock déjà acquis ;
- chaque migration vérifie encore la forme de la metadata avant d'insérer sa ligne d'historique.
Preuves matérialisées en pre.003 avec la vraie V001 :
- registre réel
[V000, V001]et version courante dérivée1; - historique V000 seul reconnu comme préfixe exact avec V001 pending ;
- historique V000+V001 exact reconnu comme complet ;
- checksum V001 figé à
6fe57ed0313d2ed295280dd6e49f6d86695d4e4effee2724a25e36db1ea17761; - binding réseau réel branché dans le hook
StoreIdentity; - inventaire physique exact : identity, canonical, observations, archive, index partiel ;
- bornes SQL statiquement alignées sur
u64,u32,RawTimestamp, payload 16 MiB et source payload 64 MiB.
L'application V000 -> V001, le rollback réel du hook, la réouverture même/autre réseau et la divergence de checksum sur PostgreSQL réel restent à prouver dans le gate live dédié pre.009; ils ne sont pas déclarés PASS dans cette tranche.
17.2 Binding réseau atomique
pre.002 prépare la frontière sans créer l'identité : RawNetworkId est transmis au moteur et chaque migration possède un hook privé exécuté sous la même transaction/advisory lock. Deux contextes sont distingués : AppliedNow pour une migration qui vient d'être exécutée et Existing pour une migration déjà présente dans l'historique lors d'une réouverture. V000 déclare explicitement le hook neutre.
pre.003 branche le hook V001 réel : en contexte AppliedNow, il insère la singleton puis relit/valide ksp_store_identity avant insertion de l'historique V001 ; en contexte Existing, il relit/valide strictement l'identité sans jamais la recréer. Une absence, un nombre de lignes inattendu, une forme invalide ou un réseau différent sont classés MigrationMismatch avec phase statique et sans rendre le réseau stocké. L'atomicité PostgreSQL réelle de ce chemin sera prouvée dans pre.009.
18. Boundaries
Les scans/tests doivent garantir :
ksp-store-lib -X-> tokio-postgres/deadpool-postgres/rustls
ksp-store-lib -X-> SQL literal
ksp-store-lib -X-> std::env / dotenv / libpq files
ksp-store-postgres-lib -X-> ksp-store-lib
ksp-store-postgres-lib -X-> direct Config ownership
ksp-store-api -X-> backend physical dependency
La façade ne publie aucun Client, Transaction, pool, TLS type, SQL row ou type backend.
19. Feature matrix
| Build | Attendu |
|---|---|
ksp-store-lib default |
feature postgres active, six capacités dispatchables une fois implémentées |
ksp-store-lib --no-default-features |
compile ; aucune dépendance backend tirée |
| config backend postgres sans feature | erreur backend_not_compiled avant I/O |
| malformed postgres URI avec feature | erreur safe pré-I/O comme 0.3.2 |
20. Audit historique — preuves de non-régression
Les tests/source scans devront empêcher le retour des patterns historiques rejetés :
has_raw_transaction_signature
BIGSERIAL id comme identity publique
slot BIGINT dans V001
canonical_json JSONB
processing_state
ON DELETE SET NULL
raw SQL error text dans Error
cap 500/1000 dans Store pagination
21. Planning de validation par tranche
pre.001
- documents plan/validation ;
- version
0.3.3-pre.1; - aucun SQL métier ;
- gate workspace actuel.
pre.002
- migration registry multi-version : PASS statique ;
- V000/checksum conservés : PASS ;
- validation préfixe/mismatch/newer : PASS unit design + canaris source ;
- préparation hook binding réseau transactionnel : PASS ;
- aucune V001 métier créée : PASS.
pre.003
- V001 + tables/constraints/indexes : PASS statique ;
- binding réseau hook
AppliedNow/Existing: PASS statique ; - checksum/inventory/bounds tests : PASS statique ;
- code
postgres_retention_compaction_unsupportedbackend + façade : PASS ; - preuve PostgreSQL réelle V001 : différée à
pre.009.
pre.004
- row codecs et lectures ;
- malformed DB matrix.
pre.005
- atomic writes/idempotence/conflict ;
- observation writes.
pre.006
- list/cursor/order/ranges ;
- hostile cursor matrix.
pre.007
- archive/purge/tombstone/ForceRehydrate ;
- Compacted unsupported stable.
pre.008
- façade/feature dispatch ;
- no physical leak ;
- pre-I/O mismatch.
pre.009
- PostgreSQL live opt-in complet.
pre.010
- hardening/completeness.
pre.011
- gate technique final.
pre.012
- réconciliation documentaire.
pre.013
- publication prep.
rel.001
- publication stable.
22. Gate courant pre.002
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.3
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
23. Critère de clôture de release
0.3.3 ne pourra être déclarée stable que si :
- les six capacités sont réellement implémentées sur
PostgresBackendet dispatchées parStore; - aucune valeur valide API n'est narrowée ;
- l'idempotence distingue strictement identité identique et contenu divergent ;
- canonical+observation est atomique ;
- pagination est déterministe et cursorisée ;
- réseau physique incorrect est refusé ;
- rétention supportée est honnête et atomique ;
Compactedn'est pas prétendu sans représentation réelle ;- tombstone et ForceRehydrate respectent les contrats acquis ;
- les erreurs ne fuient aucun secret/SQL/server text ;
- le live PostgreSQL opt-in couvre concurrence et rollback ;
- tous les gates techniques/doc sont propres ;
- aucun scope
RawAccountState/worker/app/N2 n'a été ouvert.