v0.3.1-pre.001-fix-002

This commit is contained in:
2026-08-29 07:28:45 +02:00
parent 1a07574bae
commit 62ed72f2b5
4 changed files with 798 additions and 348 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/022-V0_3_1_STORE_RAW_PLAN.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# Plan `0.3.1` — Store API RAW foundation
@@ -66,24 +66,36 @@ et non :
## 3. Mission recalibrée
`0.3.1` introduit `ksp-store-api` comme **modèle logique commun et API fonctionnelle backend-agnostic du niveau N1 / D1 RAW**.
`0.3.1` introduit `ksp-store-api` comme **modèle logique commun et API fonctionnelle backend-agnostic du niveau N1 RAW/acquisition**.
La crate doit posséder :
```text
modèles objet/struct persistants communs
observations d'acquisition persistantes lorsque leur conservation est utile
références durables
provenance d'acquisition
outcomes d'écriture/idempotence
queries et pagination bornées
health/readiness communs utiles
contrats/capabilities backend extensibles
format canonique de notification de disponibilité si la référence RAW est stabilisée
référence/format canonique de wake-up pour une donnée déjà persistée, conformément aux règles KSP-NOTIFY
cycle de rétention logique du RAW et tombstones anti-rebackfill
```
La release doit aussi auditer les autres données on-chain réellement utiles afin de distinguer explicitement :
```text
modèle N1 persistant/replayable
modèle d'observation persistant
modèle event-only non persisté
DTO Transport seulement
```
La façade runtime concrète `Store`, la sélection d'un backend compilé et l'orchestration commune appartiendront à `ksp-store-lib` en `0.3.2`. Les consumers ordinaires jobs/workers/apps dépendront alors uniquement de `ksp-store-lib`, qui réexportera la surface commune nécessaire de `ksp-store-api`.
Elle ne possède pas :
`ksp-store-api` ne devient pas propriétaire de tous les messages inter-crates. Un modèle passif partagé qui ne représente aucune donnée persistée/rejouable et sert uniquement à transporter un événement entre acquisition et traitement relève préférentiellement de `ksp-interface-lib`. La frontière exacte doit être documentée avant création d'un tel type afin d'éviter deux structs concurrentes représentant le même fait.
`ksp-store-api` ne possède pas :
```text
schéma physique
@@ -95,9 +107,10 @@ transactions SQL
backend selection par lecture Config
tokio-postgres
PostgreSQL/MySQL/SQLite/RocksDB/ClickHouse types
Transport
Transport runtime
Program decoder
Materializer
scheduler/event bus/notification runtime
```
Le modèle public `ksp-store-api` est volontairement réutilisable par toute implémentation future. Un backend peut choisir une représentation physique radicalement différente sans modifier les DTO/entités et opérations logiques consommés par l'extérieur.
@@ -209,7 +222,7 @@ Trois surfaces restent strictement distinctes :
```text
ksp-store-api
RawTransaction / RawLog / provenance / queries / outcomes
RawTransaction / observations persistantes / provenance / queries / outcomes
= modèle objet et contrats communs à toutes les implémentations
ksp-store-lib
@@ -223,6 +236,35 @@ ksp-store-postgres-lib
Aucun type de row backend, handle de pool, statement préparé, migration ou nom d'objet SQL ne traverse `ksp-store-lib` vers ses consumers.
### 4.6 Frontière `ksp-interface-lib` / `ksp-store-api`
Les deux crates ne doivent pas devenir des définitions concurrentes du même modèle.
Règle de décision :
```text
fait durable/replayable/queryable par Store
-> ksp-store-api
observation durable attachée à un fait Store
-> ksp-store-api
message passif event-only échangé entre composants
sans contrat de persistence
-> ksp-interface-lib
signal canonique « donnée Store persistée disponible »
qui ne transporte qu'une référence durable
-> ksp-store-api pour le format uniquement
shape spécifique à HTTP/WS/gRPC/provider
-> Transport seulement
```
Lorsqu'un type Interface correspond exactement à une partie d'un modèle Store, `ksp-store-api` peut le réutiliser uniquement si cela ne crée ni dépendance de protocole ni sémantique incomplète. Sinon la composition effectue une conversion explicite. Aucun copier-coller de structs quasi identiques n'est accepté pour éviter une dérive silencieuse des deux contrats.
Cette règle sera réauditée au moment d'introduire les événements realtime (`logsSubscribe`, slot/vote/status) et les futurs types Interface nécessaires à l'acquisition.
## 5. Sources KSP relues
Le gate `pre.001` a relu les sources prescrites par le prompt :
@@ -282,61 +324,56 @@ notification != backlog
at-least-once + persistence idempotente
```
## 6. N1 / D1 RAW : définition retenue
## 6. N1 RAW : définition retenue
### 6.1 Rôle
### 6.1 Rôle et critère d'admission
N1 est la couche **acquisition persistée / RAW replayable**.
N1 est la couche **acquisition / RAW replayable**. Toutes les réponses Transport ne deviennent pas automatiquement des modèles Store.
Elle ne se limite pas à une table `raw_transactions`. Elle regroupe les faits d'acquisition nécessaires pour rejouer ultérieurement la normalisation générique Solana sans redemander arbitrairement la donnée au provider.
La première surface concrète `0.3.1` retient :
Un type n'entre dans la taxonomie N1 que s'il apporte au moins une valeur durable au pipeline :
```text
RawTransaction
RawTransactionObservation
RawLog
RawLogObservation
provenance commune
références durables communes
persistence / replay
future décomposition structurelle
future interprétation/décodage
état durable nécessaire au processing
```
Les familles suivantes sont explicitement prévues mais reportées tant que leur acquisition/replay exact n'est pas suffisamment auditée :
Les données utiles uniquement comme déclencheur realtime peuvent avoir un modèle passif commun, mais ce modèle est event-only et ne reçoit pas automatiquement une capability Store.
Une même entité N1 ne peut agréger plusieurs sources que si chaque source candidate peut être convertie **sans perte de la sémantique exigée par le contrat commun**. Il est interdit de fabriquer un modèle universel rempli de `Option<T>` uniquement pour faire entrer des réponses incompatibles.
La règle d'admission est donc :
```text
RawAccount / account observation
RawBlock
RawSlot / slot update si un besoin durable distinct existe
autres familles d'acquisition réellement nécessaires
HTTP / WS / gRPC / provider response
|
v
satisfait intégralement le contrat canonique KSP ciblé ?
| |
oui non
| |
v v
même modèle N1 autre modèle / event
+ observation ou DTO Transport seulement
```
Aucun enum fermé ne doit empêcher leur ajout.
Une matrice de compatibilité source -> modèle doit être produite avant de figer chaque nouvelle famille.
### 6.2 Transport n'est pas une famille RAW
### 6.2 `RawTransaction` et observation
Les mécanismes suivants sont des **origines d'acquisition**, pas des modèles persistants distincts :
`RawTransaction` est la première famille persistante certaine de `0.3.1`.
Les sources capables de fournir la transaction complète et la meta nécessaire au replay structurel doivent converger vers le même modèle, indépendamment de l'origine :
```text
HTTP JSON-RPC
WebSocket JSON-RPC
Yellowstone gRPC
provider extension
import
backfill
getTransaction
getBlock utilisé comme conteneur d'acquisition
transactionSubscribe lorsque la forme fournit le contenu complet
yellowstone Transaction
future source équivalente auditée
```
Interdit :
```text
RawHttpTransaction
RawWsTransaction
RawGrpcTransaction
```
La même transaction logique acquise par HTTP, WS et gRPC doit converger vers un `RawTransaction` commun, avec plusieurs observations/provenances si nécessaire.
### 6.3 Transaction canonique et observation
Le contrat sépare :
```text
@@ -347,7 +384,9 @@ RawTransactionObservation
= fait qu'une source donnée a observé/acquis cette transaction
```
Exemple :
Une réponse ne contenant qu'une signature, un status ou un sous-ensemble de champs ne produit jamais un `RawTransaction` artificiellement incomplet.
La même transaction acquise plusieurs fois converge vers une seule identité logique et plusieurs observations :
```text
RawTransaction T
@@ -355,51 +394,119 @@ Exemple :
|
+---------------+---------------+
| | |
HTTP/Helius obs WS/Helius obs gRPC/PublicNode obs
HTTP/provider obs WS/provider obs gRPC/provider obs
```
La duplication d'observation ne duplique pas automatiquement la transaction canonique.
### 6.3 Logs de transaction vs notification de logs
### 6.4 Raw logs
Les `logMessages` contenus dans la meta d'une transaction appartiennent au `RawTransaction`. Ils ne constituent pas une seconde entité N1 `RawLog`.
`RawLog` est une famille N1 distincte et n'est pas confondue avec la future décomposition CORE des logs.
Elle doit préserver l'observation de logs suffisamment fidèlement pour :
Leur extraction individuelle appartient à la future décomposition STRUCTURAL :
```text
conserver l'ordre des lignes
conserver slot/signature/identité applicable
conserver le statut source nécessaire
permettre replay/inspection
permettre de relier un log à une transaction lorsqu'elle est connue
RawTransaction
-> STRUCTURAL
-> TransactionLogLine / relation instructionnelle future
```
Une observation `logsSubscribe` peut également servir de déclencheur pour acquérir ensuite la transaction complète. Le modèle de provenance ne doit pas empêcher une lineage future :
À l'inverse, `logsSubscribe` fournit un message realtime centré sur :
```text
RawLogObservation
-> déclenche acquisition transaction
RawTransactionObservation
-> RawTransaction
slot
signature
error/status
ordered log lines
```
Cette relation causale n'est pas un orchestrateur et n'est pas obligatoirement implémentée dans `0.3.1`.
Ce message peut déclencher un traitement ou l'hydratation de la transaction complète, mais il ne doit pas être confondu avec les logs extraits de `RawTransaction`.
### 6.5 Autres RAW
Le candidat de travail est donc un modèle event-only tel que `TransactionLogNotification`/`RawLogNotification`, dont l'ownership sera tranché entre `ksp-interface-lib` et `ksp-store-api` selon la règle §4.6. **Aucune persistence Store de ces notifications n'est requise par défaut.**
Le design doit permettre d'ajouter plus tard des familles distinctes sans créer un `RawAnything { kind, json }` universel.
Le partage se fait par composition de primitives communes :
Le pipeline realtime futur pourra faire :
```text
RawAcquisitionProvenance
RawPayload
RawContentHash / idempotence key
RawDataReference
PageRequest / Page
logsSubscribe
-> event passif
-> worker/analyser
-> éventuellement getTransaction(signature)
-> persistence RawTransaction
```
et non par effacement des sémantiques propres à chaque famille.
Le Store n'envoie lui-même aucun événement.
### 6.4 `RawAccountState` et observation
Les réponses account complètes provenant de HTTP, WS ou Yellowstone peuvent potentiellement converger vers un même état canonique :
```text
pubkey
slot/context utile
lamports
owner
executable
rent_epoch
data bytes exacts
```
Les détails propres à l'acquisition (`write_version`, signature source, startup, provider, transport, timing, etc.) appartiennent à `RawAccountObservation` lorsqu'ils sont utiles et disponibles.
`RawAccountState`/`RawAccountObservation` doivent être **prévus dans `ksp-store-api`** après validation de la matrice de compatibilité. Leur persistence concrète PostgreSQL peut rester non implémentée au début de `0.3.2`.
Un compte demandé sous une forme déjà interprétée par le provider ne doit pas remplacer arbitrairement les bytes canoniques nécessaires à un futur decoder.
### 6.5 Transaction status
Les surfaces signature/status peuvent représenter un fait distinct d'une transaction complète :
```text
signatureSubscribe
getSignatureStatuses
Yellowstone TransactionStatus
provider equivalent
```
Le candidat `TransactionStatusObservation` doit être étudié et, si la sémantique commune est démontrée, prévu dans l'API même si sa persistence n'est pas immédiatement implémentée.
Il peut plus tard servir à un worker/analyser pour détecter des transitions de commitment/status. Ce déclenchement n'est pas une responsabilité du Store.
### 6.6 Slot, vote, block et Yellowstone Entry
Classification actuelle :
```text
slot/root/slotsUpdates
-> event-only candidat ; persistence non justifiée actuellement
vote
-> event-only candidat si la forme est suffisamment commune entre les ledgers/providers qui l'exposent
getBlock / Yellowstone Block
-> source/conteneur d'acquisition de RawTransaction par défaut
RawBlock persistant seulement si un besoin block-level non reconstructible est démontré
Yellowstone Entry
-> examiné et non retenu actuellement : trop bas niveau et aucune destination replay/decomposition/event métier suffisante identifiée
```
`RawBlock` reste une IDEA, pas un modèle actif. Recréer le ledger bloc par bloc sans besoin supplémentaire irait à l'encontre de l'objectif KSP de transformer la donnée blockchain en unités directement exploitables.
### 6.7 Modèles et capabilities sont indépendants
La présence d'un modèle partagé n'implique pas une persistence.
Exemple :
```text
TransactionLogNotification
= peut exister comme contrat passif event-only
TransactionLogNotificationStore
= ne doit pas exister sans besoin durable démontré
```
De même, `RawAccountState` peut être figé comme modèle commun avant que `ksp-store-postgres-lib` n'implémente sa capability.
Cette séparation permet d'inventorier correctement N1 sans forcer tous les backends à stocker toutes les familles.
## 7. Payload replayable
@@ -446,41 +553,50 @@ hashable/idempotent
Le Store ne prétend pas que des bytes arbitraires sans format connu sont replayables.
## 8. Future décomposition N1 -> niveau générique Solana
## 8. Future décomposition N1 -> niveau STRUCTURAL
La frontière architecturale actuelle reste nommée :
Le nom de travail de N2 devient **STRUCTURAL**. `CORE` est abandonné dans le nouveau plan parce qu'il décrivait mal une opération qui consiste principalement à décomposer des données Solana brutes en sous-composants génériques.
La progression conceptuelle devient :
```text
RAW -> CORE -> DECODE -> SPECIALIZED
N1 RAW
-> N2 STRUCTURAL lorsque le type est réellement décomposable
-> N3 DECODED ultérieur
-> N4 DOMAIN ultérieur
```
Le nom `CORE` n'est pas considéré comme irréversible. Une revue de nomenclature pourra être faite avant l'ouverture effective de D2 sans modifier les responsabilités déjà fixées.
Cette progression n'est **pas une chaîne obligatoire pour toutes les familles N1** :
Le rôle de D2 reste en revanche clair : décomposer/normaliser une transaction RAW en faits Solana génériques indépendants des decoders Program.
```text
RawTransaction -> STRUCTURAL -> DECODED -> DOMAIN
RawAccountState -> peut aller directement vers un decoder futur si aucune décomposition N2 utile n'existe
realtime event -> worker/analyser, sans N2 nécessaire
status/slot/vote -> peut rester un fait/event terminal
```
Le futur flux doit permettre notamment :
Le premier et seul cas N2 certain aujourd'hui est `RawTransaction`.
Le futur flux transactionnel doit permettre notamment :
```text
RawTransaction
-> transaction/message générique
-> account keys
-> instruction top-level #0
-> instruction top-level #1
-> CPI/inner instruction #1.0
-> CPI/inner instruction #1.1
-> logs/meta/balances/return data applicables
-> StructuralTransaction/message
-> account keys/références
-> StructuralInstruction top-level #0
-> StructuralInstruction top-level #1
-> inner/CPI instruction #1.0
-> inner/CPI instruction #1.1
-> transaction logs/meta/balances/return data applicables
```
Chaque instruction/CPI doit ensuite pouvoir être traitée indépendamment :
Chaque instruction/CPI doit posséder une identité/path stable et être traitable indépendamment. Un decoder absent ou défaillant pour une instruction ne bloque jamais les autres unités structurales de la transaction.
```text
instruction générique
-> decoder disponible ?
oui -> decode
non -> Unsupported/NotApplicable durable sans bloquer le reste
```
N2 n'est toutefois pas défini comme « transaction split » : si une autre famille N1 démontre plus tard une vraie décomposition structurelle utile, elle peut rejoindre ce niveau sans changer sa responsabilité.
`0.3.1` ne crée aucun type D2/CORE et ne dépend pas de `ksp-program-api`.
N3 DECODED et N4 DOMAIN ne sont pas conçus dans `0.3.1`. Le plan doit seulement garantir que le RAW transactionnel conserve tout ce qui sera nécessaire à une décomposition STRUCTURAL lossless pour les futurs decoders.
`0.3.1` ne crée aucun type STRUCTURAL et ne dépend pas de `ksp-program-api`.
## 9. Provenance commune
@@ -542,9 +658,9 @@ La signature ne doit pas être représentée comme un identifiant SQL `i64` dans
Chaque observation possède une clé d'idempotence déterministe fournie par le producer/composition selon un contrat documenté. Deux acquisitions légitimes distinctes peuvent donc être conservées même si elles pointent vers le même RAW.
### 10.4 Logs
### 10.4 Événements et autres familles
L'identité exacte d'un `RawLog` sera stabilisée avec son modèle concret ; elle doit être déterministe et ne doit pas dépendre d'un primary key backend.
Les événements realtime non persistés n'ont pas d'identité Store à inventer. Lorsqu'une nouvelle famille persistante est admise (`RawAccountState`, status durable, etc.), son identité d'idempotence doit être définie avec le modèle concret et ne jamais dépendre d'une primary key backend.
Aucune promesse exactly-once distribuée n'est faite.
@@ -552,44 +668,52 @@ Aucune promesse exactly-once distribuée n'est faite.
### 11.1 Contrats communs
`ksp-store-api` définit le modèle et les opérations backend-agnostic qu'une implémentation doit satisfaire. `0.3.1` ne construit pas de backend, ne sélectionne aucun moteur et n'introduit pas encore la façade runtime concrète `Store`.
`ksp-store-api` définit le modèle persistant et les opérations backend-agnostic qu'une implémentation doit satisfaire. `0.3.1` ne construit pas de backend, ne sélectionne aucun moteur et n'introduit pas encore la façade runtime concrète `Store`.
Le backend concret implémente un contrat public d'extension, mais son modèle interne reste privé. En `0.3.2`, `ksp-store-lib::Store` enveloppera ce contrat et deviendra la seule façade de consommation normale des jobs/workers/apps.
Le backend concret implémente des contrats publics d'extension, mais son modèle interne reste privé. En `0.3.2`, `ksp-store-lib::Store` enveloppera ces contrats et deviendra la seule façade de consommation normale des jobs/workers/apps.
### 11.2 Object-safety et async
La future sélection runtime impose que le contrat backend puisse être stocké derrière une abstraction dynamique sans connaître le moteur concret.
La future sélection runtime impose que les capabilities backend puissent être stockées derrière une abstraction dynamique sans connaître le moteur concret.
La stratégie candidate est :
```text
StoreBackend: Send + Sync
capabilities Send + Sync
méthodes object-safe
futures boxed KSP-owned via un alias StoreFuture<'a, T>
future ksp-store-lib::Store possède Arc<dyn StoreBackend>
future ksp-store-lib::Store compose les capabilities disponibles
```
Cette stratégie évite une dépendance `async-trait` uniquement pour masquer la transformation. Le coût d'une box de future est accepté au niveau Store, dominé par l'I/O de persistence, et doit rester mesurable si un chemin futur démontre le contraire.
### 11.3 Capabilities
### 11.3 Capabilities séparées
Éviter un trait de 150 méthodes. La candidate est une composition de capabilities N1 :
Éviter un trait monolithique exigeant tous les types de données à chaque backend.
La candidate est une composition fine :
```text
StoreHealth
RawTransactionStore
RawLogStore
RawTransactionRead
RawTransactionWrite
RawTransactionObservationRead
RawTransactionObservationWrite
future RawAccountStateRead
future RawAccountStateWrite
future TransactionStatus* si persistence réellement retenue
```
Un contrat composite `StoreBackend` peut agréger les capabilities N1 nécessaires à la future façade `ksp-store-lib::Store`.
Un modèle event-only ne crée aucune capability Store par défaut.
Les futures couches D2/D3/D4 ne sont pas ajoutées par anticipation.
La future façade `ksp-store-lib::Store` peut exposer seulement les capabilities réellement compilées/supportées et produire une erreur stable lorsqu'une opération demandée n'est pas disponible.
### 11.4 Opérations atomiques métier
Aucun handle de transaction SQL/public n'est exposé.
Les invariants multi-écritures sont exprimés par des opérations logiques communes. Candidate :
Les invariants multi-écritures sont exprimés par des opérations logiques communes. Candidate initiale :
```text
persist_transaction_acquisition(transaction, observation)
@@ -598,16 +722,13 @@ get_raw_transaction(reference)
list_raw_transactions(query, page)
list_transaction_observations(query, page)
persist_log_acquisition(raw_log, observation)
record_log_observation(observation)
get_raw_log(reference)
list_raw_logs(query, page)
list_log_observations(query, page)
future persist_account_acquisition(state, observation)
future account reads/queries
health()
```
`persist_*_acquisition` signifie au contrat que le RAW et son observation réussissent atomiquement ou échouent ensemble. PostgreSQL réalisera cela avec une transaction privée en `0.3.2`; un autre backend utilisera son mécanisme natif.
`persist_transaction_acquisition` signifie au contrat que le RAW et son observation réussissent atomiquement ou échouent ensemble. PostgreSQL réalisera cela avec une transaction privée en `0.3.2`; un autre backend utilisera son mécanisme natif.
### 11.5 Outcomes
@@ -637,40 +758,120 @@ ordre déterministe par query
Le cursor est un token d'implémentation retourné par la façade et réinjecté tel quel par le consumer. Son contenu ne devient pas API et ne doit pas être utilisé comme transport d'un SQL fragment.
## 12. Références durables et notifications
## 12. Références, événements et cycle de vie N1
L'ownership `KSP-NOTIFY-001..006` reste à `ksp-store-api`.
### 12.1 Références durables
Le candidat est une référence compacte :
`ksp-store-api` doit posséder les références compactes nécessaires aux lectures, au replay et aux relations entre couches, sans encoder une PK SQL.
Pour la première famille :
```text
RawDataReference
Transaction(RawTransactionReference)
Log(RawLogReference)
futures variantes non exhaustives
RawTransactionReference
network
signature
```
Puis un signal logique :
Les autres références sont ajoutées uniquement avec leur modèle réel.
### 12.2 Événements : ownership hors Store runtime
Le Store runtime persiste et lit. Il ne possède ni scheduler, ni event bus, ni mécanisme de publication et ne doit pas s'appuyer sur une notification native de base de données pour piloter le pipeline.
Les règles `KSP-NOTIFY-001..006` restent applicables : `ksp-store-api` peut posséder **le format canonique** d'un wake-up signifiant qu'une donnée déjà commitée est disponible, sous forme de référence durable compacte. Cela ne signifie pas que la base ou `ksp-store-lib` publie elle-même le signal.
Le futur ordre d'orchestration est :
```text
RawDataAvailable {
reference,
}
worker/acquisition
-> Store.persist(...)
-> succès/commit durable
-> publisher worker/analyser/runtime
-> notification canonique de référence si nécessaire
```
Le mécanisme de diffusion est hors scope.
La notification persistée-data reste un wake-up et jamais la source de vérité du backlog. Les messages realtime d'acquisition (`logsSubscribe`, slot/vote, etc.) suivent la frontière §4.6 : `ksp-interface-lib` est le owner préféré lorsqu'ils ne représentent aucune donnée Store persistée.
Ordre obligatoire :
### 12.3 Preuves de processing versionnées
Un simple champ :
```text
persist
commit
notify
processed = true
```
La notification ne transporte pas le payload RAW et ne remplace jamais `list_*`/backlog Store.
n'est pas une preuve durable suffisante.
Si les références concrètes ne sont pas assez stabilisées lors de leur tranche, le type de notification peut être reporté sans déplacer son ownership.
L'audit kbot2/kbot3 confirme l'intérêt d'un ledger version-aware identifié au minimum par :
```text
stage
processor identity/name
processor version
input identity
input hash/version
terminal status
```
Le contrat concret de processing sera introduit avec les jobs/couches concernés, pas dans la foundation RAW par anticipation. En revanche, les modèles N1 et leur rétention doivent rester compatibles avec ce futur ledger et avec le force replay.
### 12.4 Rétention, compression, archivage et purge
N1 ne doit pas imposer la conservation éternelle du payload RAW en stockage chaud.
Le cycle logique candidat, repris puis redessiné depuis kbot2, est :
```text
Full
-> Compacted (optionnel)
Full/Compacted
-> Archived
-> Purged
```
Ces états décrivent **la disponibilité du payload**, pas le succès des processors supérieurs.
La décision d'éligibilité appartient à un futur worker/job/maintenance policy qui vérifie les preuves de processing nécessaires. Le Store applique une transition bornée/atomique mais ne décide pas lui-même qu'une transaction est suffisamment décodée.
Une policy typique pourra exiger par exemple :
```text
STRUCTURAL courant réussi pour le hash RAW courant
unités structurales dans un état terminal selon la coverage policy active
horizon de rétention minimal atteint
aucun traitement/replay explicitement en cours
archive durable confirmée si la policy exige une archive avant purge
```
L'expression « toutes les instructions sont décodées » ne peut pas être un absolu open-world : un nouveau decoder peut apparaître plus tard. Les états terminaux doivent donc être évalués par rapport à une **coverage/version policy explicite**. Une instruction marquée `no_decoder/unsupported` peut rester redécodable ultérieurement depuis N2 STRUCTURAL sans exiger le RAW si N2 a conservé les faits nécessaires.
La purge physique du payload ne supprime pas l'identité logique. Un tombstone minimal durable doit rester afin d'empêcher un backfill normal de réacquérir indéfiniment une transaction déjà traitée :
```text
network
transaction signature / identité logique
slot lorsqu'il est connu
canonical format id/version
content hash
retention state
processing/retention provenance minimale nécessaire
```
Comportement attendu :
```text
backfill normal + tombstone
-> skip / AlreadyKnownPurged
backfill explicitement forcé
-> réhydratation autorisée selon policy
```
Le tombstone ne doit pas prétendre remplacer le RAW pour replay. Si une nouvelle version STRUCTURAL a réellement besoin du document RAW purgé, une réhydratation forcée depuis une archive ou le réseau est nécessaire.
La policy est **spécifique à chaque famille**. `RawAccountState`, qui pourrait aller directement vers un decoder futur, ne peut pas réutiliser automatiquement la même politique de purge que `RawTransaction`.
`0.3.1` doit préparer les contrats logiques de rétention/tombstone sans implémenter compression, stockage d'archive, cron/worker de purge ou maintenance PostgreSQL.
## 13. Health et diagnostics
@@ -742,37 +943,41 @@ ks-config/src/store.rs
docs/architecture/STORAGE_ARCHITECTURE.md
docs/guides/POSTGRES_STORAGE.md
docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md
olddocs/archivekbot2/docs/RAW_STORAGE_LIFECYCLE.md
olddocs/archivekbot2/docs/CORE_EXTRACTION_SELECTION_AND_REPLAY.md
olddocs/archivekbot2/docs/CORE_EXTRACTION_CONTRACTS.md
```
Le Store historique couvre 16 tables N1-N3, 240 ressources SQL atomiques et 79 index attendus. Cette surface confirme l'intérêt de la séparation façade/backend, de l'idempotence et des observations, mais elle est beaucoup trop large pour `0.3.1`.
L'archive contient aussi les documents kbot2 historiques. Ils avaient déjà formalisé deux idées utiles : un cycle RAW `full/compacted/archived/purged` et un processing ledger versionné pour décider skip/replay. KSP reprend les concepts mais rejette le couplage à un simple `processing_state` global et redessine la rétention comme une policy externe au Store.
### 15.2 Matrice d'héritage
| Concept historique | Observation kbot3 | Décision | Application KSP |
|-----------------------------------------|-------------------------------------------------------------------|------------|----------------------------------------------------------------------------------------------------------|
| séparation façade Store / PostgreSQL | PostgreSQL et `sqlx::PgPool` privés | REPRENDRE | API commune séparée; façade + backend PostgreSQL reportés à `0.3.2` |
| `StoreOpenOptions` / backend selection | backend + `serde_json::Value` opaque dans Store | REDESSINER | Config produit les settings communs; `ksp-store-lib` sélectionne un backend compilé et construit `Store` |
| health/readiness | contrat backend-neutral présent | REPRENDRE | health minimal portable dans `ksp-store-api` |
| `contracts/dto/raw.rs` | transaction canonique + observations transaction/account | REDESSINER | transaction + logs + provenance commune, sans JSON universel ni lifecycle D2/D3 |
| `contracts/entity/raw.rs` | rows exposant `i64`, JSON et timestamps backend-shaped | REDESSINER | entités logiques sans PK SQL ni type physique |
| `RawTransactionStore` | `has_*` puis inserts séparés | REDESSINER | write idempotent atomique sans check-then-insert |
| repository traits | capabilities async séparées | REPRENDRE | capabilities N1 séparées + façade commune |
| pagination | 100 par défaut, 500 max, cursor opaque | REPRENDRE | mêmes bornes candidates, cursor toujours opaque |
| replay contracts | filtres mêlés au processing N2/N3 | REPORTER | `0.3.1` fournit seulement lectures RAW nécessaires au futur replay |
| error contracts | erreurs Store historiques | REDESSINER | `ksp_core_lib::Error/Result` + codes Store stables |
| schema validation / migration bootstrap | validation stricte des objets PostgreSQL | REPORTER | responsabili`ksp-store-postgres-lib` en `0.3.2` |
| migrations atomiques | ressources SQL actives nombreuses | REPORTER | aucune migration dans Store API |
| constraints/indexes | 79 index attendus | REPORTER | design physique seulement après stabilisation API |
| idempotence / uniqueness | observation keys + canonical identity | REPRENDRE | sémantique publique déterministe; mécanisme backend privé |
| raw transaction table | table physique `k_sol_raw_transactions` | REPORTER | `RawTransaction` commun maintenant; table privée à `ksp-store-postgres-lib` en `0.3.2` |
| acquisition observations | transaction + account observations | REPRENDRE | concept N1 central; transaction + log initialement |
| processing ledger | couvre N2/N3 processing | REPORTER | appartient aux futures couches de processing, pas N1 foundation |
| CORE tables | transaction, keys, instructions, CPI, logs, balances, return data | REPORTER | future couche D2, aucune surface en `0.3.1` |
| DECODE coverage/events | contrats decoder/materializer | REPORTER | future D3 |
| materialization journal | `k_sol_mat_outputs` | REPORTER | future D3 |
| maintenance truncate/drop | scripts destructifs dédiés | REJETER | aucune API runtime générique destructive Store |
| Config Store | backend + options JSON historiques | REDESSINER | Config reste owner; settings Store/URI secrets adaptés vers `ksp-store-lib` en `0.3.2` |
| runtime diagnostics | résumés sanitised mais backend riches | REDESSINER | health portable dans API; détails backend privés |
| Concept historique | Observation kbot2/kbot3 | Décision | Application KSP |
|------------------------------------------|-------------------------------------------------------------------|------------|-------------------------------------------------------------------------------------------------------------------|
| séparation façade Store / PostgreSQL | PostgreSQL et driver privés | REPRENDRE | API commune séparée ; façade + backend PostgreSQL reportés à `0.3.2` |
| `StoreOpenOptions` / backend selection | backend + options historiques partiellement opaques | REDESSINER | Config produit les settings ; `ksp-store-lib` sélectionne un backend compilé |
| health/readiness | contrat backend-neutral présent | REPRENDRE | health minimal portable dans `ksp-store-api` |
| transaction canonique source-independent | HTTP/WS/gRPC convergent vers une transaction canonique | REPRENDRE | `RawTransaction` commun seulement si la source satisfait le contrat complet |
| acquisition observations | transaction + account observations séparées du RAW | REPRENDRE | concept N1 central ; transaction d'abord, account prévu après matrice de compatibilité |
| ancienne raw WS notification | kbot2 l'a supprimée de la baseline persistante | REPRENDRE | `logsSubscribe`/events restent event-only par défaut ; pas de table de notification brute |
| logs transactionnels | conservés puis extraits dans la couche Core historique | REDESSINER | restent dans `RawTransaction`, puis deviennent des unités N2 STRUCTURAL ; pas de `RawLog` persistant séparé |
| account observations/states | N1 account observation + N2 account state existaient | REDESSINER | prévoir `RawAccountState`/observation ; N2 seulement si une décomposition réellement utile est démontrée |
| repository traits | capabilities async séparées | REPRENDRE | capabilities read/write fines, aucune obligation de supporter toutes les familles |
| pagination | 100 par défaut, 500 max, cursor opaque | REPRENDRE | mêmes bornes candidates, cursor toujours opaque |
| replay contracts | sélection + force replay version-aware | REPRENDRE | futur replay pilopar processor/version/input hash, jamais par simple bool |
| `processing_state` RAW | `received/core_extracted/failed` facilitait la queue | REDESSINER | peut servir d'index de travail futur mais ne constitue jamais la preuve durable unique de traitement |
| processing ledger | stage + processor/version + input/hash + status | REPRENDRE | future preuve durable versionnée ; reportée aux couches/jobs concernés |
| retention lifecycle | `full/compacted/archived/purged` déjà envisagé dans kbot2 | REDESSINER | états logiques backend-agnostic + policy externe + tombstone anti-rebackfill |
| CORE tables | transaction, keys, instructions, CPI, logs, balances, return data | REDESSINER | future couche N2 renommée STRUCTURAL ; aucune surface créée en `0.3.1` |
| DECODE coverage/events | états `decoded/ignored/unsupported/failed` versionnés | REPORTER | future N3 ; utile à la policy de rétention mais pas défini dans la foundation RAW |
| materialization journal | sorties génériques versionnées | REPORTER | future N3/N4 |
| raw transaction table | table physique unique par signature | REPORTER | modèle commun maintenant ; table privée à `ksp-store-postgres-lib` en `0.3.2` |
| schema validation / migrations / indexes | forte surface PostgreSQL | REPORTER | responsabilité `ksp-store-postgres-lib` |
| maintenance truncate/drop | scripts destructifs dédiés | REJETER | aucune API runtime générique destructive ; rétention contrôlée distincte |
| Config Store | backend/options historiques | REDESSINER | Config reste owner ; URI/secrets adaptés vers `ksp-store-lib` en `0.3.2` |
| modèles partagés hors Store | kbot3 recommandait de ne pas dupliquer les modèles communs | REPRENDRE | frontière explicite `ksp-interface-lib` / `ksp-store-api`, conversion explicite lorsque les sémantiques diffèrent |
## 16. Audit PostgreSQL et driver de référence futur
@@ -906,22 +1111,27 @@ La trajectoire roadmap corrigée est :
## 19. Threat model `0.3.1`
| Menace | Réponse de design |
|------------------------------|-----------------------------------------------------------------------|
| payload hostile trop grand | bornes à la construction des contrats RAW avant persistence |
| raw payload leak via `Debug` | implémentations Debug manuelles/redacted pour conteneurs sensibles |
| secret provider/endpoint | provenance ne contient que des ids/codes sûrs, jamais URL/credential |
| duplicate race | write idempotent atomique, jamais `has_*` comme précondition |
| même clé / contenu divergent | conflit explicite, pas d'overwrite silencieux |
| partial RAW + observation | opération `persist_*_acquisition` atomique au contrat |
| cursor hostile | taille/limit bornées; cursor opaque non interprété par consumer |
| query non bornée | aucune liste publique sans `PageRequest` borné |
| backend leak | aucun type driver/row/table/SQL dans la façade |
| backend error leak | mapping vers erreurs KSP sûres; source externe auditée avant chaînage |
| closed-world backend | contrat implémentable hors `ksp-store-lib` |
| closed-world RAW family | types/familles extensibles sans JSON fourre-tout |
| cross-backend mismatch | conformance suite commune sur chaque implémentation |
| scope creep D2/D3/D4 | aucun type CORE/DECODE/SPECIALIZED dans `0.3.1` |
| Menace | Réponse de design |
|--------------------------------|--------------------------------------------------------------------------------------|
| payload hostile trop grand | bornes à la construction des contrats RAW avant persistence |
| raw payload leak via `Debug` | `Debug` manuel/redacted pour conteneurs sensibles |
| secret provider/endpoint | provenance = ids/codes sûrs, jamais URL/credential |
| source partielle | ne produit pas un modèle canonique incomplet ; autre model/event ou Transport only |
| mismatch HTTP/WS/gRPC | matrice d'admission source -> modèle avant stabilisation de chaque famille |
| duplicate race | write idempotent atomique, jamais `has_*` comme précondition |
| même clé / contenu divergent | conflit explicite, pas d'overwrite silencieux |
| partial RAW + observation | opération `persist_*_acquisition` atomique au contrat |
| cursor hostile | taille/limit bornées ; cursor opaque non interprété par consumer |
| query non bornée | aucune liste publique sans `PageRequest` borné |
| backend leak | aucun type driver/row/table/SQL dans la façade |
| event persistence creep | event-only != capability Store ; owner Interface préféré si non persistant |
| stale `processed = true` | future preuve stage+processor/version+input hash, pas un bool comme source de vérité |
| purge prématurée | policy externe + preuves versionnées + transition atomique |
| re-backfill après purge | tombstone minimal durable ; réhydratation uniquement en mode forcé |
| purge casse nouveau structural | archive/réseau forcé nécessaire si nouvelle version N2 exige le RAW |
| closed-world backend | contrat implémentable hors `ksp-store-lib` |
| closed-world RAW family | modèles/capabilities séparés, ajoutables sans JSON fourre-tout |
| scope creep N2/N3/N4 | aucun type STRUCTURAL/DECODED/DOMAIN implémenté dans `0.3.1` |
Les menaces DSN/SQL injection/schema drift/migrations restent documentées pour `0.3.2`, où elles deviennent exécutables. Elles ne justifient pas des types SQL dans l'API.
@@ -940,10 +1150,16 @@ exact production module inventory
bounds payload/provenance/cursor
Debug/redaction tests
idempotence outcome semantics
transaction/log identity validation
transaction identity validation
source-to-model admission matrix canaries
transaction logs remain owned by RawTransaction before STRUCTURAL extraction
account/status model compatibility canaries lorsqu'introduits
event-only model does not imply Store capability
atomic-operation contract fake backend
pagination bounds
notification reference contract si introduit
retention state/tombstone transition invariants
normal backfill skips purged tombstone; forced rehydrate remains explicit
processing proof is not represented by a lone boolean
release completeness
cargo tree -p ksp-store-api --edges normal
cargo tree --duplicates
@@ -962,28 +1178,30 @@ Aucun PostgreSQL live test n'appartient à `0.3.1` puisque le backend PostgreSQL
## 21. Ownership
| Concept | Owner | Visibilité | Introduction | Raison |
|------------------------------|----------------------------|---------------------------------|---------------------|-------------------------------------------|
| `RawTransaction` | `ksp-store-api` | public | `0.3.1` | modèle persistant commun |
| `RawTransactionObservation` | `ksp-store-api` | public | `0.3.1` | provenance/acquisition distincte du RAW |
| `RawLog` | `ksp-store-api` | public | `0.3.1` | seconde famille N1 explicitement demandée |
| `RawLogObservation` | `ksp-store-api` | public | `0.3.1` | provenance logs |
| `RawPayload` | `ksp-store-api` | public | `0.3.1` | contrat persistant replayable versionné |
| RAW references | `ksp-store-api` | public | `0.3.1` | reads, notification, lineage |
| write outcomes | `ksp-store-api` | public | `0.3.1` | sémantique idempotente commune |
| query/page | `ksp-store-api` | public | `0.3.1` | backlog/replay backend-agnostic |
| health portable | `ksp-store-api` | public | `0.3.1` | consumer commun |
| backend extension trait | `ksp-store-api` | public | `0.3.1` | implémentation externe réelle |
| `Store` facade | `ksp-store-lib` | public | `0.3.2` | point de consommation commun |
| backend dispatch/settings | `ksp-store-lib` | public/privé selon contrat | `0.3.2` | features disponibles + sélection Config |
| PostgreSQL settings internes | `ksp-store-postgres-lib` | privé ou constructor-boundary | `0.3.2` | construction du backend de référence |
| PostgreSQL rows | `ksp-store-postgres-lib` | privé | `0.3.2` | mapping physique |
| pool/connection | `ksp-store-postgres-lib` | privé | `0.3.2` | détail runtime |
| SQL/statements | `ksp-store-postgres-lib` | privé | `0.3.2` | détail backend |
| migrations/schema | `ksp-store-postgres-lib` | privé/opérateur | `0.3.2` | détail backend |
| transport -> RAW conversion | composition/pipeline futur | privé/réutilisable selon besoin | release acquisition | `DEP-KSP-003` |
| RAW -> D2 normalization | future pipeline générique | hors `0.3.1` | série suivante | aucune dépendance Program |
| notification mechanism | futur host/runtime | hors API de diffusion | ultérieur | Store API possède seulement le format |
| Concept | Owner candidat/fixé | Visibilité | Introduction | Raison |
|--------------------------------------|-------------------------------|-----------------------------|---------------------------------------|-----------------------------------------------------|
| `RawTransaction` | `ksp-store-api` | public | `0.3.1` | modèle persistant/replayable commun |
| `RawTransactionObservation` | `ksp-store-api` | public | `0.3.1` | acquisition durable distincte du RAW |
| transaction log messages RAW | dans `RawTransaction` | public via modèle RAW | `0.3.1` | font partie de la transaction avant décomposition |
| structural transaction log lines | future couche N2 | hors `0.3.1` | série STRUCTURAL | unités extraites après décomposition |
| `RawAccountState` | `ksp-store-api` | public si audit PASS | prévu `0.3.1`, capability progressive | état canonique commun HTTP/WS/gRPC à confirmer |
| `RawAccountObservation` | `ksp-store-api` | public si audit PASS | prévu `0.3.1`, capability progressive | provenance/account acquisition |
| `TransactionStatusObservation` | à confirmer `ksp-store-api` | public si persistence utile | exploration `0.3.1` | petit état durable/event potentiel |
| log/status/slot/vote event-only | préférer `ksp-interface-lib` | public passif | future tranche Interface si retenue | échange inter-composants sans persistence Store |
| `RawRetentionState` / tombstone | `ksp-store-api` | public logique | à stabiliser en `0.3.1` | cycle de vie RAW backend-agnostic |
| processing ledger versionné | future contrat Store commun | hors surface initiale RAW | avec jobs/N2/N3 | preuve durable stage/version/input, pas bool |
| RAW references | `ksp-store-api` | public | `0.3.1` | reads/replay/lineage |
| write outcomes | `ksp-store-api` | public | `0.3.1` | sémantique idempotente commune |
| query/page | `ksp-store-api` | public | `0.3.1` | backlog/replay backend-agnostic |
| health portable | `ksp-store-api` | public | `0.3.1` | consumer commun |
| backend capabilities | `ksp-store-api` | public | `0.3.1` | implémentations externes |
| `Store` facade | `ksp-store-lib` | public | `0.3.2` | point de consommation commun |
| backend dispatch/settings | `ksp-store-lib` | public/privé selon contrat | `0.3.2` | features disponibles + sélection Config |
| PostgreSQL rows/pool/SQL/migrations | `ksp-store-postgres-lib` | privé/opérateur | `0.3.2` | détails physiques |
| transport -> RAW conversion | composition/pipeline futur | privé/réutilisable | release acquisition | conversion explicite, jamais dépendance inverse |
| RAW -> STRUCTURAL | future pipeline générique | hors `0.3.1` | série suivante | aucune dépendance Program |
| event runtime / scheduler / analyzer | worker/analyser/runtime futur | hors Store | ultérieur | Store ne notifie pas lui-même |
| retention policy/maintenance worker | job/worker/opérateur futur | hors Store API décisionnel | ultérieur | décide l'éligibilité ; Store applique la transition |
## 22. Hors scope strict `0.3.1`
@@ -997,49 +1215,53 @@ std.store Config
backend MySQL/SQLite/Oracle/RocksDB/ClickHouse
Transport acquisition runtime
conversion Transport -> RAW fonctionnelle
D2/CORE structs et persistence
RAW -> D2 normalizer
Program decode
Materializer
processing ledger
N2 STRUCTURAL structs et persistence
RAW -> STRUCTURAL processor
N3 DECODED
N4 DOMAIN/materialization
processing ledger concret
worker/job/backfill
application Store/RAW
notification transport concret
retention/archive/purge lifecycle
truncate/drop/maintenance API
event bus/scheduler/analyser runtime
compression algorithm concret
archive backend concret
retention cron/worker automatique
truncate/drop/maintenance API générique
```
Les **contrats logiques** de rétention/tombstone N1 et la compatibilité avec un futur processing ledger peuvent en revanche être définis dans `ksp-store-api` sans implémenter ces mécanismes.
## 23. Prévision souple recalibrée `0.3.1`
Chaque tranche vise environ 1520 minutes de travail effectif.
### `pre.001` — Audit, modèle N1 et split API/backend
### `pre.001` — Audit, taxonomie N1 et split API/backend
Base, règles, architecture, audit kbot3, audit PostgreSQL/driver futur, RAW transactions/logs, ownership, API candidate, threat model, tests et sizing.
Base, règles, architecture, audit kbot2/kbot3, audit PostgreSQL/driver futur, frontière Interface/Store, classification persistence/event/Transport-only, rétention/tombstones, ownership, threat model, tests et sizing.
### `pre.002` — Scaffold `ksp-store-api`
### `pre.002` — Scaffold `ksp-store-api` + taxonomie
Créer uniquement la crate API, manifest Core-only, façade crate-root et documentation initiale. Aucun modèle fonctionnel lourd.
Créer uniquement la crate API, manifest Core-only, façade crate-root et modules de modèle/capability sans dépendance backend. Verrouiller le vocabulaire RAW/STRUCTURAL et la séparation model/capability.
### `pre.003` — Primitives RAW communes + transaction
Introduire payload/reference/provenance/idempotence/timestamps bornés puis `RawTransaction` et son observation.
Introduire payload/reference/provenance/idempotence/timestamps bornés puis `RawTransaction` et son observation. Les logs inclus restent dans la transaction.
### `pre.004` — RAW logs + extensibilité N1
### `pre.004` — Matrice cross-source + familles N1 prévues
Introduire `RawLog`/observation, vérifier ordering/identity/bounds et verrouiller l'absence de type RAW fourre-tout.
Auditer HTTP/WS/gRPC pour `RawAccountState`/observation et `TransactionStatusObservation`; introduire les modèles communs seulement lorsque la sémantique converge. Classer explicitement logsSubscribe/slot/vote/block/Entry en event, IDEA ou rejet.
### `pre.005` — Capabilities backend extensibles
Implémenter les contrats object-safe et l'external backend canary, sans façade runtime `Store` et sans runtime DB. La façade commune appartient à `ksp-store-lib` en `0.3.2`.
Implémenter les contracts read/write object-safe et l'external backend canary, sans façade runtime `Store` et sans runtime DB. Un modèle prévu n'oblige pas chaque backend à implémenter sa capability.
### `pre.006` — Queries, pagination, outcomes et notification reference
### `pre.006` — Queries, outcomes et lifecycle RAW
Finaliser reads/list/backlog bornés, atomic acquisition contract et référence de notification si suffisamment stable.
Finaliser reads/list/backlog bornés, atomic acquisition contract, `RawRetentionState`, tombstone minimal et sémantiques normal-skip/force-rehydrate sans implémenter compression/archive physique.
### `pre.007` — Adversarial/API hardening + completeness
### `pre.007` — Boundary/adversarial hardening + completeness
Payload/cursor/provenance hostile, Debug, external backend, exact exports/modules, dependency firewall et scope négatif D2/D3/D4.
Payload/cursor/provenance hostile, Interface/Store ownership canaries, admission matrix, retention races, exact exports/modules, external backend, dependency firewall et scope négatif N2/N3/N4.
### `pre.008` — Gate technique final
@@ -1067,22 +1289,24 @@ Le prompt `0.3.2` ouvre ensemble `ksp-store-lib` et `ksp-store-postgres-lib`, av
Mécanique de publication uniquement.
La release reste estimée clôturable dans une session si les tranches conservent cette granularité et si aucun codec/normalizer D2 n'est tiré dans le scope.
La release reste estimée clôturable dans une session si les tranches conservent cette granularité et si aucun processor STRUCTURAL/decoder n'est tiré dans le scope.
## 24. Trajectoire `0.3.x` résultante
La prévision durable devient, sous réserve des gates de chaque release :
```text
0.3.1 ksp-store-api / N1 RAW contract
0.3.1 ksp-store-api / N1 RAW + observations + lifecycle logique
0.3.2 ksp-store-lib + ksp-store-postgres-lib / PostgreSQL reference via tokio-postgres
0.3.3 generic Interface/wire additions nécessaires à acquisition/normalisation
0.3.3 ksp-interface-lib additions nécessaires aux événements/acquisitions partagés
0.3.4 ksp-job-api + premier backfill RAW concret
0.3.5 application backfill/inspection RAW
ensuite worker/service live RAW avant ouverture D2
ensuite worker/service live RAW avant ouverture N2 STRUCTURAL
```
Cette renumérotation remplace la prévision `0.3.1..0.3.4` de la base `v0.2.14`; `pre.001-fix.001` l'écrit immédiatement dans le `ROADMAP.md` pour éviter de poursuivre avec une trajectoire fausse.
Les événements realtime non persistés et les modèles Interface peuvent être avancés ou retardés selon le premier consumer réel ; ils ne doivent pas être artificiellement absorbés par Store.
La renumérotation `0.3.1..0.3.5` issue de `pre.001-fix.001` reste valide.
## 25. Critères de fermeture de `0.3.1`
@@ -1090,10 +1314,16 @@ La stable `0.3.1` est prête seulement si :
```text
ksp-store-api existe seule dans le domaine Store
modèle objet RAW commun stable
RawTransaction + observations stables
RawLog + observations stables
future extension N1 non bloquée
aucune source partielle ne fabrique un RawTransaction incomplet
logs transactionnels restent dans RawTransaction jusqu'à N2 STRUCTURAL
RawAccountState/observation audités et modèle prévu si compatibilité cross-source prouvée
TransactionStatusObservation classé explicitement
logsSubscribe/slot/vote classés event-only/Interface, reportés ou rejetés explicitement
RawBlock reste IDEA sans persistence tant qu'un besoin block-level n'est pas démontré
Yellowstone Entry reste explicitement hors taxonomie active
frontière ksp-interface-lib / ksp-store-api documentée et sans modèles concurrents
models et capabilities séparés
contrats backend communs stables
backend externe implémentable sans ksp-store-lib
aucun type backend/SQL public
@@ -1101,9 +1331,12 @@ queries/pagination bornées
idempotence/conflict semantics explicites
atomic acquisition contract explicite
références durables stables
notification format introduit ou reporté explicitement
RawRetentionState/tombstone empêchent le rebackfill normal après purge
force rehydrate reste explicitement distinct
aucun simple processed bool n'est présenté comme preuve suffisante de traitement
future preuve processing versionnée stage/processor/input reste possible
aucune dépendance PostgreSQL
aucune surface D2/D3/D4
aucune surface N2 STRUCTURAL/N3 DECODED/N4 DOMAIN implémentée
workspace et graphes entièrement verts
prompt `0.3.2` cohérent avec ksp-store-lib + ksp-store-postgres-lib + tokio-postgres
prompt 0.3.2 cohérent avec ksp-store-lib + ksp-store-postgres-lib + tokio-postgres
```