Files
khadhroony-solana-project/docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
2026-08-29 11:39:50 +02:00

74 KiB
Raw Blame History

Plan 0.3.1 — Store API RAW foundation

1. Statut et base

Ce plan est établi par 0.3.1-pre.001 à partir de la release stable v0.2.14 et de l'archive historique obligatoire khadhroony-bot3_v0.5.3-pre.005-fix010.zip.

La base KSP vérifiée à l'ouverture est :

workspace.package.version = 0.2.14
deltas/0.2.14/rel.001.md présent
prompts/020-V0_3_1_START_PROMPT.md présent
ksp-program-api présent
ksp-store-api absent
ksp-store-lib absent

L'archive opérateur ne contient pas de metadata Git exploitable ; le tag v0.2.14 ne peut donc pas être revérifié localement. La version Cargo, le delta rel.001, le prompt 020 et la surface Program API publiée concordent avec la base stable attendue.

Le journal opérateur fourni avec l'archive montre un baseline stable intégralement vert avant ouverture de 0.3.1 : audits Rust/Markdown, cargo check --workspace, Clippy et cargo test --workspace passent.

Après application de pre.001, la version Cargo cible est :

0.3.1-pre.1

2. Décision opérateur qui recalibre le prompt initial

Le brainstorming de pre.001 a séparé la création de l'API Store de son implémentation PostgreSQL.

La trajectoire devient d'abord :

0.3.1 = ksp-store-api uniquement
0.3.2 = ouverture conjointe ksp-store-lib + ksp-store-postgres-lib
        fondation runtime/backend PostgreSQL uniquement
        feature postgres par défaut
        PostgreSQL de référence via tokio-postgres
0.3.3 = même paire de crates / persistence PostgreSQL RawTransaction complète
0.3.4 = même paire de crates / RawAccountState + complétude Store RAW

Le redécoupage pre.010 conserve donc les deux crates Store/PostgreSQL ensemble à chaque release, mais sépare la charge en trois slices. Il évite de cumuler dans une seule session création de façade, Config, connexions/migrations, persistence transaction, rétention et persistence account.

Cette décision remplace pour 0.3.1 la mission combinée ksp-store-api + ksp-store-lib décrite dans le prompt de démarrage. pre.001-fix.001 réconcilie immédiatement le ROADMAP.md afin que la trajectoire globale ne conserve pas une séquence désormais fausse.

Conséquences immédiates :

aucune dépendance PostgreSQL en 0.3.1
aucun tokio-postgres dans Cargo en 0.3.1
aucun pool
aucune table
aucun SQL
aucune migration
aucun std.store
aucun ksp-store-lib

0.3.1 doit stabiliser le contrat logique suffisamment pour que la série 0.3.20.3.4 puisse ensuite demander :

quelle représentation PostgreSQL satisfait le mieux ce contrat, puis comment l'implémenter famille par famille sans réduire le contrat API ?

et non :

comment exposer les tables déjà créées ?

3. Mission recalibrée

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 :

modèles objet/struct persistants communs
observations d'acquisition persistantes lorsque leur conservation est utile
références durables
outcomes d'écriture/idempotence
queries cursorisées sans plafond métier arbitraire
contrats/capabilities backend extensibles
cycle de rétention logique du RAW et tombstones anti-rebackfill

Le health/readiness runtime et un éventuel type canonique dédié de wake-up ne sont finalement pas matérialisés dans 0.3.1. Les références durables de l'API suffisent pour la foundation RAW ; le health appartient à la façade runtime ksp-store-lib et peut être cadré dès la fondation 0.3.2, tandis qu'un contrat de notification dédié ne sera ajouté que lorsqu'un consumer/publisher réel en aura besoin conformément aux règles KSP-NOTIFY.

La release doit aussi auditer les autres données on-chain réellement utiles afin de distinguer explicitement :

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 à partir de 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. 0.3.3 et 0.3.4 complèteront cette même façade et le même backend PostgreSQL sans introduire une seconde architecture.

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 :

schéma physique
rows backend
SQL
migrations
pool/connection
transactions SQL
backend selection par lecture Config
tokio-postgres
PostgreSQL/MySQL/SQLite/RocksDB/ClickHouse types
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.

4. Modèle d'extensibilité backend

4.1 Graphe durable

Le graphe cible devient :

                              ksp-store-api
                         modèles + contrats communs
                                   |
                +------------------+------------------+
                |                                     |
                v                                     v
          ksp-store-lib                    ksp-store-postgres-lib
     façade/runtime commun                    impl PostgreSQL
       backend dispatch                         tokio-postgres
                |                                     ^
                | feature postgres (default) ----------+
                |
                +-- feature mysql ------> futur ksp-store-mysql-lib
                +-- autres features ----> futurs backends

Règles de dépendances :

ksp-store-lib              -> ksp-store-api
ksp-store-lib[postgres]    -> ksp-store-postgres-lib
ksp-store-postgres-lib     -> ksp-store-api
futur ksp-store-mysql-lib  -> ksp-store-api

ksp-store-postgres-lib -X-> ksp-store-lib
backend alternatif     -X-> ksp-store-lib

ksp-store-lib est donc le point de consommation normal du workspace, tandis que ksp-store-api reste le contrat d'implémentation partagé entre la façade et les backends.

4.2 Features de ksp-store-lib

La fondation 0.3.2 introduira au minimum :

default = [postgres]
postgres -> dépendance optionnelle ksp-store-postgres-lib

Les futures features peuvent ajouter mysql, sqlite, rocksdb, clickhouse ou tout autre backend réellement implémenté. Plusieurs features backend peuvent être compilées simultanément ; la feature décide quels backends sont disponibles dans le binaire, jamais lequel est sélectionné au runtime.

La Config décide le backend actif parmi ceux compilés. Un backend KSP connu mais absent des features du binaire doit produire une erreur distincte et stable de type STORE_BACKEND_NOT_COMPILED, sans fallback silencieux vers PostgreSQL ou un autre backend.

4.3 Config, URI et secrets

ksp-store-api, ksp-store-lib et les crates backend ne lisent jamais directement .env ni les variables KSP/KSPB. ksp-config-lib reste propriétaire des documents, placeholders, .env, provenance et sensibilité.

La configuration future suit :

std.store / profil composite
        |
        v
ksp-config-lib
  résolution ${KSP_SECRET_*}
  + validation schema/sémantique
        |
        v
settings runtime ksp-store-lib
        |
        v
backend compilé sélectionné
        |
        v
crate backend privée

Règles fixées pour 0.3.2 puis conservées par 0.3.3, 0.3.4 et les futurs backends :

  • utiliser une URI/DSN lorsque le moteur possède une forme URI naturelle (postgresql://..., futur mysql://..., etc.) ;
  • permettre des options typées backend-specific uniquement lorsqu'elles sont réellement nécessaires et sans serde_json::Value opaque comme contrat runtime ;
  • considérer l'URI entière comme Secret dès qu'elle peut embarquer user/password/token ou autre credential ;
  • obtenir les secrets via des placeholders KSP_SECRET_* résolus par Config, avec inventaire dans .env.example au même delta que leur première utilisation ;
  • ne jamais versionner un vrai .env ni un URI/credential réel ;
  • ne jamais exposer URI, password, token, query secret ou chemin sensible dans Debug, logs, erreurs ou health ;
  • distinguer backend inconnu de backend KSP connu mais non compilé avant tentative de connexion ;
  • éviter tout fallback implicite vers PG*, .pgpass ou autre source d'environnement lue directement par le driver/backend lorsque Config a déjà fourni les settings effectifs.

La forme exacte de StoreSettings et du document std.store appartient au design de fondation 0.3.2; 0.3.1 fixe seulement ces responsabilités et invariants.

4.4 Surface consumer

Les jobs/workers/apps ne dépendent pas directement de ksp-store-postgres-lib ni des futurs backends. Le chemin normal est :

ksp-job-* / ksp-worker-* / app
        -> ksp-store-lib
             -> réexporte les types API communs nécessaires
             -> expose Store/open/read/write/query communs
             -> dispatch vers le backend choisi par Config

Le consumer ne contient aucun #[cfg(feature = "postgres")] métier et ne branche pas sur postgres, mysql ou un nom de moteur pour lire/écrire les données.

4.5 Modèle public et modèle interne

Trois surfaces restent strictement distinctes :

ksp-store-api
    RawTransaction / observations persistantes / provenance / queries / outcomes
    = modèle objet et contrats communs à toutes les implémentations

ksp-store-lib
    Store / lifecycle / dispatch / settings runtime publics communs
    = façade de consommation normale

ksp-store-postgres-lib
    Postgres*Row / SQL / schema / migrations / pool / statements
    = implémentation privée au backend

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 :

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 :

RULES.md
docs/000-README.md
docs/rules/RULES_GENERAL.md
docs/rules/RULES_KSP.md
docs/rules/RULES_RUST.md
docs/rules/RULES_DEPENDENCIES.md
docs/rules/RULES_DOCUMENTATION.md
docs/rules/FILE_CONTRACTS.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/PROMPT_STRUCTURE.md

docs/architecture/000-README.md
docs/architecture/001-PROJECT_OBJECTIVES.md
docs/architecture/002-LAYERS_AND_DEPENDENCIES.md
docs/architecture/003-COMPONENT_CONTRACTS.md
docs/architecture/004-COMPONENT_INVENTORY.md
docs/architecture/005-DEPENDENCY_GRAPH.md
docs/architecture/006-WIRE_AND_PROGRAM.md
docs/architecture/007-EXECUTION_AND_POLICY.md
docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md

Les règles directement structurantes sont :

KSP-API-001..007
KSP-CONFIG-001..018
KSP-NOTIFY-001..006
KSP-STORE-001..002
KSP-DATA-001..004
KSP-PIPE-001..007
DEP-KSP-001..005
DEP-CARGO-001..007
DEP-STORE-001..008
DEP-TRANSPORT-001..005
DEP-PIPE-001..008
DEP-WORKER-001..003
DEP-JOB-001..003

Le résultat normatif principal est :

Store API possède les contrats persistants
Store API -X-> Transport/Program/Materializer
Transport -X-> Store API
conversion explicite à la composition
persist -> commit -> notification
notification != backlog
at-least-once + persistence idempotente

6. N1 RAW : définition retenue

6.1 Rôle et critère d'admission

N1 est la couche acquisition / RAW replayable. Toutes les réponses Transport ne deviennent pas automatiquement des modèles Store.

Un type n'entre dans la taxonomie N1 que s'il apporte au moins une valeur durable au pipeline :

persistence / replay
future décomposition structurelle
future interprétation/décodage
état durable nécessaire au processing

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 :

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

Une matrice de compatibilité source -> modèle doit être produite avant de figer chaque nouvelle famille.

6.2 RawTransaction et observation

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 :

getTransaction
getBlock utilisé comme conteneur d'acquisition
transactionSubscribe lorsque la forme fournit le contenu complet
yellowstone Transaction
future source équivalente auditée

Le contrat sépare :

RawTransaction
    = fait RAW canonique source-independent et replayable

RawTransactionObservation
    = fait qu'une source donnée a observé/acquis cette transaction

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 :

                 RawTransaction T
                        ^
                        |
        +---------------+---------------+
        |               |               |
 HTTP/provider obs  WS/provider obs  gRPC/provider obs

6.3 Logs de transaction vs notification de logs

Les logMessages contenus dans la meta d'une transaction appartiennent au RawTransaction. Ils ne constituent pas une seconde entité N1 RawLog.

Leur extraction individuelle appartient à la future décomposition STRUCTURAL :

RawTransaction
    -> STRUCTURAL
         -> TransactionLogLine / relation instructionnelle future

À l'inverse, logsSubscribe fournit un message realtime centré sur :

slot
signature
error/status
ordered log lines

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.

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 pipeline realtime futur pourra faire :

logsSubscribe
    -> event passif
    -> worker/analyser
         -> éventuellement getTransaction(signature)
         -> persistence RawTransaction

Le Store n'envoie lui-même aucun événement.

6.4 RawAccountState et observation — matrice pre.004

L'audit de la surface KSP réelle confirme qu'un état complet de compte possède une sémantique commune entre HTTP, WebSocket et Yellowstone lorsque la source fournit les bytes complets et un slot durable. Le Store ne conserve pas la forme d'encodage réseau : base58/base64/base64+zstd et protobuf doivent être décodés avant construction du modèle commun.

Le modèle commun matérialisé est :

RawAccountStateReference
    network
    pubkey
    slot
    state_hash

RawAccountState
    reference
    lamports
    owner
    executable
    rent_epoch
    complete data bytes

RawAccountObservation
    observation_key
    account reference
    provenance
    optional write_version
    optional transaction_signature
    optional is_startup

state_hash fait partie de la référence car une même account peut subir plusieurs écritures dans un slot, alors que les surfaces HTTP/WS standards n'exposent pas le write_version Yellowstone. Le digest permet à plusieurs sources observant le même état complet de converger sans promouvoir un ordinal provider-specific dans l'identité commune. Le producer/converter possède le calcul déterministe du digest ; Store API ne choisit pas l'algorithme de hash.

La matrice d'admission courante est :

Source KSP État complet commun Slot durable Admission RawAccountState
HTTP getAccountInfo oui oui oui si account non-null, bytes complets et aucun dataSlice/jsonParsed
HTTP getMultipleAccounts oui oui oui par position non-null si bytes complets ; pubkey reprise depuis la requête
HTTP getProgramAccounts oui conditionnel oui seulement avec résultat contextualisé + bytes complets ; forme bare refusée
WS accountSubscribe oui oui oui si bytes complets ; pubkey reprise depuis l'identité de subscription
WS programSubscribe oui conditionnel oui seulement pour la forme contextualisée + bytes complets ; forme bare event-only
Helius standard account/program WS oui idem standard mêmes règles que le wire Solana standard réutilisé
Yellowstone Account oui oui oui si accounts_data_slice n'a pas tronqué les bytes

Règles négatives :

jsonParsed
request-side data slice
Yellowstone accounts_data_slice
program account sans context/slot
account absent/null
    -X-> RawAccountState persistant incomplet

space n'est pas conservé comme vérité indépendante : lorsqu'on possède les bytes complets, leur longueur est déterministe. Les enrichissements Yellowstone write_version, txn_signature et is_startup appartiennent à RawAccountObservation. Le timestamp serveur et les filters/capture ids restent de la provenance lorsque le converter peut les représenter sans perte utile.

La borne initiale Store-owned est :

complete account data <= 16 MiB

Elle est un admission guard KSP, pas une affirmation sur la limite protocolaire Solana.

6.5 Transaction status — convergence insuffisante pour un modèle Store unique

L'audit pre.004 conclut que les trois surfaces candidates ne représentent pas encore exactement le même fait :

Source Sémantique principale Conclusion 0.3.1
HTTP getSignatureStatuses snapshot interrogé : slot, confirmations, error, confirmation status candidat snapshot durable, non figé
WS signatureSubscribe event one-shot : received puis/ou commitment demandé atteint event runtime, ownership Interface/worker à étudier
Yellowstone TransactionStatus update d'exécution : slot, signature, vote, index, error, sans commitment event/status provider-neutral potentiel, non figé

Créer maintenant un TransactionStatusObservation rempli d'options ferait perdre la distinction entre snapshot interrogé, transition de commitment et update d'exécution. Aucun modèle Store n'est donc ajouté en pre.004.

TODO avant matérialisation :

séparer explicitement snapshot durable vs event realtime
étudier l'ownership ksp-interface-lib des events passifs
prouver la correspondance des états/commitments
prévoir un éventuel wake-up worker/analyser sans notification émise par Store

6.6 Logs, slot, vote, block et Yellowstone Entry — classification fermée pre.004

Classification actuelle après audit :

logsSubscribe
    -> event realtime passif distinct
    -> signature + error + ordered log lines + context slot
    -> pas de RawLog Store
    -> ownership ksp-interface-lib/worker à préciser

slot/root/slotsUpdates
    -> event-only candidat
    -> aucune persistence N1 démontrée

vote
    -> event-only candidat si la forme commune utile est prouvée
    -> aucune persistence N1 par défaut

getBlock / Yellowstone Block
    -> source/conteneur d'acquisition de RawTransaction
    -> RawBlock persistant reste IDEA uniquement

Yellowstone Entry
    -> transport-only
    -> explicitement non retenu actuellement

Les logs contenus dans RawTransaction restent distincts de logsSubscribe : les premiers sont de la matière replayable de transaction et seront extraits en N2 STRUCTURAL ; le second est un événement realtime léger pouvant éventuellement déclencher l'hydratation de la transaction complète.

RawBlock ne doit être rouvert que si un besoin block-level non reconstructible apporte une valeur concrète au pipeline. Recréer le ledger bloc par bloc sans besoin supplémentaire irait à l'encontre de l'objectif KSP de produire des 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 :

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

7.1 Décision de niveau API

ksp-store-api possède un payload de persistence RAW KSP, pas un payload réseau provider.

Le contrat candidat est un conteneur borné et versionné :

RawPayload
    format_id
    format_version
    bytes
    content_hash

format_id identifie un format de persistence KSP source-independent. Il ne vaut pas helius_json, yellowstone_proto ou un autre wire provider.

La conversion :

transport model
    -> conversion explicite
    -> RawPayload KSP

reste hors de ksp-store-api.

7.2 Pas de codec improvisé

0.3.1 ne choisit pas un codec réseau ou Solana concurrent. bincode reste interdit pour les codecs wire KSP.

Le premier format concret de transaction canonique doit pouvoir être produit par la future couche de conversion à partir des wires génériques KSP. Sa définition détaillée peut évoluer pendant 0.3.1 tant que les invariants publics restent :

source-independent
versionné
borné
lossless pour la normalisation générique couverte
Debug sans bytes
hashable/idempotent

Le Store ne prétend pas que des bytes arbitraires sans format connu sont replayables.

7.3 Contrat matérialisé par pre.003

La première surface concrète reste indépendante de tout codec et de tout backend :

RawPayload
    RawFormatId
    format_version: u32 non nul
    bytes: Box<[u8]> non vide
    RawContentHash: [u8; 32]

RawTransactionReference
    RawNetworkId
    RawTransactionSignature: [u8; 64]

RawTransaction
    reference
    slot: u64
    block_time: Option<RawTimestamp>
    payload

RawTransactionObservation
    RawObservationKey: [u8; 32]
    transaction reference
    RawAcquisitionProvenance

Les bornes initiales sont des admission guards Store, jamais des affirmations sur les maxima du protocole Solana :

logical code UTF-8       <= 128 bytes
canonical RAW payload    <= 16 MiB
source payload size meta <= 64 MiB
Unix timestamp           <= 9999-12-31T23:59:59.999Z

RawPayload ne calcule pas lui-même le digest et ne vérifie pas le contenu du format. Le producer/converter propriétaire du format KSP doit fournir des bytes déjà canoniques et leur digest déterministe. Cette séparation permet de conserver ksp-store-api -> ksp-core-lib uniquement.

Le payload et RawTransaction ne sont volontairement pas Clone dans cette foundation afin de ne pas encourager des copies implicites d'un document RAW potentiellement volumineux. Les références, signatures, digests, timestamps et observations compactes restent clonables/copiables lorsqu'approprié.

8. Future décomposition N1 -> niveau STRUCTURAL

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 :

N1 RAW
    -> N2 STRUCTURAL lorsque le type est réellement décomposable
        -> N3 DECODED ultérieur
            -> N4 DOMAIN ultérieur

Cette progression n'est pas une chaîne obligatoire pour toutes les familles N1 :

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 premier et seul cas N2 certain aujourd'hui est RawTransaction.

Le futur flux transactionnel doit permettre notamment :

RawTransaction
    -> 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 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.

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é.

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

La provenance candidate doit pouvoir représenter sans secret :

network/cluster
provider code
endpoint logical id optionnel
transport/protocol code
acquisition method
origin live/backfill/import/replay/repair
commitment optionnel
capture/session/filter logical ids optionnels
observed/received timestamp
source payload size/hash optionnels

Interdits :

endpoint URL
DSN
API key
token/password
payload source complet par simple diagnostic

Les codes provider/protocol/method restent ouverts et bornés ; aucun enum provider fermé n'est introduit dans Store API.

pre.003 matérialise cette politique avec RawProvenanceCode, réutilisé pour les codes logiques provider/protocol/method/endpoint/commitment/session/filter. Les valeurs sont non vides, bornées et limitées à un alphabet logique sûr ; une URL contenant /, des contrôles ou des espaces ne peut donc pas être stockée accidentellement dans ces champs. Cette validation n'autorise pas le caller à y placer un secret alphanumérique : le contrat reste explicitement « logical code only ».

RawAcquisitionProvenance::new(...) ne prend que les champs obligatoires ; les informations optionnelles sont ajoutées par builders dédiés. observed_at, lorsqu'il existe, ne peut pas être postérieur à received_at, et le payload source n'est jamais conservé, seulement sa taille bornée et/ou un digest optionnel.

10. Identité et idempotence

10.1 Principes

L'API ne fournit pas de méthode has_* à appeler avant un write idempotent.

Le backend doit recevoir une opération unique avec ces sémantiques :

clé déjà absente
    -> Inserted

même clé + même contenu canonique
    -> AlreadyPresent

même clé + contenu différent
    -> Conflict

Cela évite un check-then-insert race.

10.2 Transactions

La clé logique d'un RawTransaction est dérivée au minimum du réseau et de la signature canonique. Un hash du contenu canonique protège contre une collision logique avec contenu divergent.

La signature ne doit pas être représentée comme un identifiant SQL i64 dans l'API.

10.3 Observations

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 Account states

La référence matérialisée par pre.004 est :

RawAccountStateReference
    network
    pubkey
    slot
    canonical state hash

Le hash couvre conceptuellement l'état canonique complet et sert à distinguer/converger les écritures multiples possibles dans un même slot sans dépendre de write_version. Le Store API ne calcule pas ce digest et ne transforme pas les bytes de transport.

Une RawAccountObservation possède sa propre RawObservationKey; plusieurs acquisitions HTTP/WS/gRPC peuvent donc viser la même référence d'état sans être fusionnées comme observations.

10.5 Événements et autres familles

Les événements realtime non persistés n'ont pas d'identité Store à inventer. Si une autre famille persistante est admise ultérieurement, 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.

11. API candidate

11.1 Contrats communs

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 des contrats publics d'extension, mais son modèle interne reste privé. À partir de 0.3.2, ksp-store-lib::Store enveloppera progressivement ces contrats et deviendra la seule façade de consommation normale des jobs/workers/apps. La conformance PostgreSQL des familles RAW est ensuite matérialisée par slices en 0.3.3 puis 0.3.4.

11.2 Object-safety et async

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 :

capabilities Send + Sync
méthodes object-safe
futures boxed KSP-owned via un alias StoreApiFuture<'a, T>
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 séparées

Éviter un trait monolithique exigeant tous les types de données à chaque backend.

pre.005 matérialise une composition fine par famille et par direction :

RawTransactionRead
RawTransactionWrite
RawTransactionObservationRead
RawTransactionObservationWrite

RawAccountStateRead
RawAccountStateWrite
RawAccountObservationRead
RawAccountObservationWrite

Les capabilities d'écriture distinguent deux usages :

persist_raw_*_acquisition(raw, observation)
    = création/admission atomique du RAW + observation

record_raw_*_observation(observation)
    = acquisition supplémentaire d'un RAW déjà persistant
    = ne retransmet pas le payload volumineux

Les traits sont Send + Sync, dyn-compatible et retournent StoreApiFuture<'a, T>, alias KSP basé uniquement sur Pin<Box<dyn Future + Send>>. Un backend externe peut donc les implémenter sans async-trait, tokio, ksp-store-lib ou crate backend officielle.

StoreHealth, les listes/queries et les outcomes détaillés ne sont pas artificiellement introduits dans cette tranche. pre.006 finalise les résultats d'écriture et le lifecycle logique avant fermeture de l'API.

Un modèle event-only ne crée aucune capability Store par défaut. Aucun trait monolithique StoreBackend n'est introduit : un backend peut implémenter uniquement les familles réellement supportées.

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é.

La surface matérialisée par pre.005 commence par les opérations unitaires nécessaires :

get_raw_transaction(reference)
persist_raw_transaction_acquisition(transaction, observation)
get_raw_transaction_observation(observation_key)
record_raw_transaction_observation(observation)

get_raw_account_state(reference)
persist_raw_account_acquisition(state, observation)
get_raw_account_observation(observation_key)
record_raw_account_observation(observation)

Les opérations persist_raw_*_acquisition signifient au contrat que le RAW et son observation réussissent atomiquement ou échouent ensemble. PostgreSQL réalisera cela avec une transaction privée dans la slice RawTransaction 0.3.3, puis avec le même invariant pour RawAccountState en 0.3.4; un autre backend utilisera son mécanisme natif.

Les opérations record_raw_*_observation supposent que la référence RAW ciblée existe déjà et permettent de retenir une acquisition supplémentaire sans retransmettre la donnée RAW complète.

Les listes, queries et outcomes détaillés restent à finaliser dans pre.006; le backlog métier reste hors Store API et appartiendra au futur processing/job layer. Le health runtime reste une responsabilité de la future façade ksp-store-lib en 0.3.2. Aucune transaction backend publique n'est nécessaire pour exprimer les opérations Store.

11.5 Outcomes

pre.006 matérialise un vocabulaire commun d'idempotence :

RawEntityWriteOutcome
    Inserted
    AlreadyPresent
    Rehydrated
    SkippedPurged

RawObservationWriteOutcome
    Inserted
    AlreadyPresent
    NotRecorded

RawAcquisitionWriteOutcome
    entity + observation

Inserted et AlreadyPresent sont des succès idempotents. Une même identité logique accompagnée d'un contenu divergent produit ERROR_CODE_RAW_CONFLICT; aucun backend ne peut résoudre ce cas par overwrite silencieux.

Pour RawTransaction, Normal et ForceRehydrate sont deux modes explicites : un tombstone purgé provoque SkippedPurged en mode normal ; seul ForceRehydrate autorise une réhydratation compatible. Le mode forcé n'annule pas les règles de conflit de contenu/format.

11.6 Pagination et queries

Le Store fournit un mécanisme de navigation, pas une policy d'exécution. pre.006 introduit :

RawPageLimit
    > 0
    aucun maximum fonctionnel KSP arbitraire

RawPageCursor
    opaque
    <= 4 KiB pour borner le token hostile

RawPageRequest
RawPage<T>
RawSlotRange
RawSortDirection
RawTransactionQuery
RawAccountStateQuery

Une requête de 5_000_000 éléments n'est pas ramenée silencieusement à 500, 1000 ou une autre policy Store. Si un backend ne peut physiquement servir qu'une partie de la demande en une opération, il peut retourner cette partie avec un cursor de continuation ou une erreur backend réellement liée à sa capacité ; ksp-store-lib ne doit pas inventer un plafond inférieur par prudence.

Le cursor est un token d'implémentation retourné puis réinjecté tel quel par le consumer. Son contenu ne devient pas API, ne transporte jamais un fragment SQL public et sa borne de 4 KiB protège l'admission du token sans limiter le nombre de résultats.

Les queries 0.3.1 décrivent seulement des critères de données (network, slot range, compte optionnel, ordre, page). Elles ne définissent pas de backlog métier, de taille de batch worker, de priorité ou de policy executor. Le futur processing ledger/job layer décidera quoi traiter et dans quel volume.

12. Références, événements et cycle de vie N1

12.1 Références durables

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 :

RawTransactionReference
    network
    signature

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, mais 0.3.1 n'ajoute aucun type de notification dédié. Les références durables déjà exposées par ksp-store-api fournissent l'identité compacte nécessaire à un futur contrat de wake-up si un publisher/consumer réel le justifie. La base et ksp-store-lib ne publient pas eux-mêmes le signal.

Le futur ordre d'orchestration est :

worker/acquisition
    -> Store.persist(...)
    -> succès/commit durable
    -> publisher worker/analyser/runtime
         -> notification canonique de référence si nécessaire

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.

12.3 Preuves de processing versionnées

Un simple champ :

processed = true

n'est pas une preuve durable suffisante.

L'audit kbot2/kbot3 confirme l'intérêt d'un ledger version-aware identifié au minimum par :

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 :

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 :

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 :

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 :

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 — reportés au runtime Store

0.3.1 n'expose aucun StoreHealth dans ksp-store-api. Le health/readiness est un état runtime de la façade et des backends, pas une propriété nécessaire des modèles RAW persistants.

0.3.2 devra cadrer dans ksp-store-lib une projection portable uniquement si les consumers runtime en ont réellement besoin ; les backends pourront fournir leurs diagnostics privés derrière cette projection.

Ne jamais exposer dans l'API commune :

migration SQL state
pool handles
connection count détaillé par driver
server error brut
DSN
SQLSTATE arbitraire présenté comme contrat portable

Les diagnostics spécifiques à PostgreSQL appartiennent à ksp-store-postgres-lib. Si ksp-store-lib expose un health commun en 0.3.2, il ne projette que les états réellement portables et sanitised.

14. Dependency graph 0.3.1

Graphe maximal par défaut :

ksp-store-api
└── ksp-core-lib

Aucune dépendance externe n'est nécessaire pour la foundation candidate :

pas de serde
pas de chrono
pas de tokio
pas de async-trait
pas de futures-util
pas de ksp-interface-lib tant qu'un type Interface réel n'est pas requis

Les timestamps persistants peuvent être représentés par un newtype KSP en unités Unix explicites plutôt que d'ajouter chrono par réflexe. Le choix exact est prouvé dans la tranche de modèle.

15. Audit historique kbot3

15.1 Surface auditée

L'archive historique a été extraite et les surfaces prescrites ont été relues, notamment :

ks-store/Cargo.toml
ks-store/README.md
ks-store/USAGE.md
ks-store/TODO.md
ks-store/src/lib.rs
ks-store/src/store.rs
ks-store/src/contracts/**
ks-store/src/postgres/**
ks-store/migrations/postgres/**
config/store.config.json
config/schemas/store.config.schema.json
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 kbot2/kbot3 Décision Application KSP
séparation façade Store / PostgreSQL PostgreSQL et driver privés REPRENDRE API commune séparée ; paire façade/backend ouverte en 0.3.2 puis complétée par slices 0.3.3/0.3.4
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 REPORTER health runtime portable à cadrer dans la fondation ksp-store-lib 0.3.2; aucun StoreHealth dans 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 RawAccountState/observation stabilisés en N1 ; 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 cursor opaque historique REDESSINER limite caller > 0 sans plafond métier KSP ; cursor hostile borné uniquement
replay contracts sélection + force replay version-aware REPRENDRE futur replay piloté par 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

16.1 Décision

Le choix opérateur est fixé pour la version suivante :

ksp-store-lib
    façade/runtime commun
    feature postgres = feature par défaut
    feature postgres -> ksp-store-postgres-lib

ksp-store-postgres-lib
    backend officiel de référence = PostgreSQL
    driver interne = tokio-postgres

Ce choix n'ajoute aucune dependency à 0.3.1.

16.2 État externe audité au 28 août 2026

Sources primaires actuelles consultées : documentation PostgreSQL et documentation/package tokio-postgres.

Résultats :

PostgreSQL stable courant : 18.6, publié le 13 août 2026
tokio-postgres courant : 0.7.18, publié le 12 juin 2026
edition tokio-postgres : Rust 2024
MSRV tokio-postgres : Rust 1.85
runtime async : Tokio
prepared statements : supportés
transactions / isolation : supportées
COPY in/out : supporté
pipelining : supporté
TLS : connector externe, à choisir explicitement en 0.3.2

tokio-postgres::Config peut être construit et renseigné programmaticalement. 0.3.2 devra éviter les chemins de configuration implicites : l'URI/les options effectives viennent de ksp-config-lib via les settings publics de ksp-store-lib, puis sont converties vers le backend PostgreSQL sans lecture directe de .env, PG* ou .pgpass.

16.3 SQLx

SQLx 0.9.0 a été réaudité comme alternative mais n'est plus retenu après décision opérateur.

La comparaison a néanmoins confirmé l'intérêt d'éviter dans KSP les defaults de connexion influencés par PG*/.pgpass lorsque le backend peut être construit explicitement depuis Config.

La performance ne constitue pas à elle seule la raison du choix : 0.3.2 devra optimiser les vrais chemins KSP avec prepared statements, batching/COPY lorsque justifié, transactions et indexes adaptés.

17. Contrat attendu de 0.3.2 sans le concevoir physiquement ici

0.3.2 introduira deux crates dans la même release :

ksp-store-lib
ksp-store-postgres-lib

ksp-store-lib devra :

dépendre de ksp-store-api
réexporter la surface API commune nécessaire aux consumers
exposer la façade Store et son lifecycle
posséder la sélection/dispatch des backends compilés
activer postgres par défaut
importer ksp-store-postgres-lib seulement sous feature postgres
accepter les settings effectifs fournis par Config
rejeter distinctement un backend connu mais non compilé
ne jamais exposer les types internes d'un backend

ksp-store-postgres-lib devra implémenter le même modèle objet et les mêmes contrats de ksp-store-api et pourra créer librement :

PostgresStoreBackend
Postgres*Row privés
migrations
schema version
prepared statements
indexes
pool
transaction handles privés
mapping Rust API <-> PostgreSQL
tokio-postgres

Les consumers jobs/workers/apps dépendront uniquement de ksp-store-lib, avec la feature par défaut ou une sélection explicite de features. Les crates backend alternatives dépendront de ksp-store-api, pas de ksp-store-lib.

La compatibilité PostgreSQL devra être testée par round-trip des modèles API et par introspection/validation du schema réel.

18. Sorties PostgreSQL du prompt initial explicitement déplacées

Le prompt de démarrage demandait encore à pre.001 un schéma PostgreSQL candidat, une migration policy et un gate PostgreSQL live parce que 0.3.1 devait initialement contenir API et implémentation. La décision de split rend ces sorties prématurées.

Elles sont donc classées ainsi :

Sortie initialement demandée Décision pre.001 Release propriétaire
tables/colonnes/PK/FK/indexes RAW REPORTER 0.3.2 ksp-store-postgres-lib
mapping u64/bytes/timestamps vers PostgreSQL REPORTER 0.3.2
migration layout/version/checksum REPORTER 0.3.2
schema introspection/conformance REPORTER 0.3.2
pool/TLS/timeouts REPORTER 0.3.2
PostgreSQL live gate REPORTER 0.3.2
SQL injection/statement policy exécutable REPORTER 0.3.2

Les invariants API qui contraignent déjà ce futur schéma restent fixés maintenant :

identités logiques non dépendantes d'une PK SQL
idempotence Inserted/AlreadyPresent/Conflict
atomicité RAW + observation
pagination cursorisée sans plafond métier KSP
payload versionné et borné
provenance sans secret
round-trip exact du modèle objet commun

Aucun nom de table candidat n'est donc produit par 0.3.1-pre.001. C'est une conséquence volontaire du split, pas une omission du gate.

La trajectoire roadmap corrigée est :

0.3.1  ksp-store-api RAW/observations
0.3.2  ksp-store-lib + ksp-store-postgres-lib
0.3.3  wires Interface nécessaires à l'acquisition/normalisation future
0.3.4  ksp-job-api + premier backfill
0.3.5  application backfill/inspection RAW

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

20. Stratégie de tests 0.3.1

Prévoir :

unit tests module-local
public API canaries crate-root
external backend canary
object-safe backend contract canary
manifest/dependency firewall
exact public export inventory
exact production module inventory
bounds payload/provenance/cursor
Debug/redaction tests
idempotence outcome semantics
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 cursor/zero-limit admission sans plafond métier arbitraire
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

Le backend externe canary doit implémenter le contrat avec une petite implémentation mémoire de test située hors des modules de production. Il prouve :

aucune dépendance à ksp-store-lib
aucune connaissance PostgreSQL
implémentation des mêmes capabilities API
round-trip via le contrat backend commun

Aucun PostgreSQL live test n'appartient à 0.3.1 puisque le backend PostgreSQL n'existe pas encore.

21. Ownership

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 0.3.1 état canonique commun HTTP/WS/gRPC sous admission stricte
RawAccountObservation ksp-store-api public 0.3.1 provenance/acquisition de l'état compte
TransactionStatusObservation non fixé absent reporté HTTP snapshot, WS transition et Yellowstone update non fusionnés
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 0.3.1 cycle de vie RAW backend-agnostic stabilisé
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 navigation/replay backend-agnostic
health runtime ksp-store-lib hors 0.3.1 0.3.2 à cadrer état runtime, non requis par les modèles RAW
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 puis 0.3.3/0.3.4 fondation puis schémas RAW par slice
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

ksp-store-lib
PostgreSQL runtime
tokio-postgres dependency
pool/TLS PostgreSQL
SQL/tables/indexes/migrations
std.store Config
backend MySQL/SQLite/Oracle/RocksDB/ClickHouse
Transport acquisition runtime
conversion Transport -> RAW fonctionnelle
N2 STRUCTURAL structs et persistence
RAW -> STRUCTURAL processor
N3 DECODED
N4 DOMAIN/materialization
processing ledger concret
worker/job/backfill
application Store/RAW
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, taxonomie N1 et split API/backend

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 + taxonomie

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. Les logs inclus restent dans la transaction.

pre.004 — Matrice cross-source + familles N1 prévues

Audit HTTP/WS/gRPC matérialisé. RawAccountState/observation sont admis avec bytes complets + slot et enrichissements source-specific séparés. TransactionStatusObservation est différé car snapshot HTTP, transition WS et update Yellowstone ne convergent pas encore assez. logsSubscribe/slot/vote restent event-only candidats, block reste conteneur/IDEA et Entry reste rejeté.

pre.005 — Capabilities backend extensibles

Matérialiser les contracts read/write object-safe pour transaction/account et leurs observations, l'alias StoreApiFuture, ainsi qu'un canari backend externe. Les écritures d'acquisition imposent RAW + observation atomiques et les observations supplémentaires peuvent être enregistrées sans retransmettre le RAW. Aucune façade runtime Store, aucun trait monolithique backend et aucun runtime DB.

pre.006 — Queries, outcomes et lifecycle RAW

Finaliser reads/list cursorisés sans plafond métier arbitraire, outcomes idempotents/conflict, atomic acquisition contract, RawRetentionState, tombstone minimal et sémantiques normal-skip/force-rehydrate sans implémenter compression/archive physique ni policy executor.

pre.007 — Boundary/adversarial hardening + completeness

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.

La tranche matérialise aussi le résultat distinct ExpectedStateMismatch pour une compare-and-transition de rétention perdue : un backend ne doit confondre ni une course concurrente, ni un état déjà à la cible, ni une transition effectivement appliquée. Les canaris de complétude figent la surface publique N1, les inventaires de modules/capabilities et l'absence de modèles event-only ou STRUCTURAL/DECODED/DOMAIN dans ksp-store-api.

pre.008 — Gate technique final

Statut : PASS opérateur.

La tranche reste un gate pur : aucun nouveau modèle, capability, test métier, backend, codec, event-only type ou comportement runtime. Elle synchronise uniquement la version workspace et les preuves documentaires du gate final.

Le gate opérateur a été exécuté après cargo clean : audits Rust/Markdown, cargo check --workspace, Clippy, tests ciblés de toutes les crates présentes, cargo test --workspace, builds Tauri des trois Desk et graphes Cargo sont verts. La commande ciblée initiale cargo test -p ksp-program-lib était un nom de package erroné ; cargo test -p ksp-program-api a ensuite été exécuté séparément et passe intégralement.

pre.009 — Réconciliation documentaire finale

Statut : matérialisé par 0.3.1-pre.009.

Le plan, la validation, l'inventaire/graphe d'architecture, les dépendances des futurs consumers Store et IDEAS.md sont réconciliés avec la surface réellement livrée. README.md ne porte pas d'inventaire Store détaillé et aucun USAGE Store n'existe encore : aucune modification artificielle n'y est ajoutée. Aucun CHANGELOG.md, ROADMAP.md ni prompt suivant n'est touché.

pre.010 — Redécoupage documentaire des futures slices Store/PostgreSQL

Statut : matérialisé par 0.3.1-pre.010.

Cette tranche rouvre uniquement la responsabilité documentaire de trajectoire après décision opérateur postérieure à pre.009. Elle découpe l'ancien 0.3.2 surdimensionné en trois releases où ksp-store-lib et ksp-store-postgres-lib progressent toujours ensemble : fondation runtime/backend, RawTransaction, puis RawAccountState + complétude.

Aucun CHANGELOG.md, ROADMAP.md ni prompt suivant n'est touché dans cette tranche afin de respecter la séparation des couloirs de fermeture.

pre.011 — Préparation de publication minimale

Uniquement :

Cargo.toml
CHANGELOG.md
ROADMAP.md
prompts/021-V0_3_2_START_PROMPT.md
delta pre.011

Le prompt 0.3.2 ouvre ensemble ksp-store-lib et ksp-store-postgres-lib, mais uniquement pour la fondation runtime/backend PostgreSQL. Les surfaces RawTransaction et RawAccountState restent réservées respectivement à 0.3.3 et 0.3.4.

rel.001 — Publication stable

Mécanique de publication uniquement.

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 :

0.3.1  ksp-store-api / N1 RAW + observations + lifecycle logique
0.3.2  Store/PostgreSQL foundation / runtime, Config, connexion, migrations, health
0.3.3  Store/PostgreSQL RawTransaction / persistence + observations + query + retention
0.3.4  Store/PostgreSQL RawAccountState + complétude/conformance RAW cross-family
0.3.5  ksp-interface-lib additions nécessaires aux événements/acquisitions partagés
0.3.6  ksp-job-api + premier backfill RAW concret
0.3.7  application backfill/inspection RAW
ensuite worker/service live RAW avant ouverture N2 STRUCTURAL

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.

Le redécoupage pre.010 remplace la renumérotation précédente 0.3.2..0.3.5. Les deux crates ksp-store-lib et ksp-store-postgres-lib restent développées de pair dans les trois slices 0.3.20.3.4; seule la responsabilité fonctionnelle de chaque release est réduite.

25. Critères de fermeture de 0.3.1

La stable 0.3.1 est prête seulement si :

ksp-store-api existe seule dans le domaine Store
RawTransaction + observations stables
aucune source partielle ne fabrique un RawTransaction incomplet
logs transactionnels restent dans RawTransaction jusqu'à N2 STRUCTURAL
RawAccountState/observation audités, modèles et capabilities stabilisés sous admission cross-source stricte
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
queries/pagination cursorisée sans plafond métier KSP
idempotence/conflict semantics explicites
atomic acquisition contract explicite
références durables stables
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
health/readiness runtime et type de wake-up dédié restent explicitement hors `ksp-store-api` `0.3.1`
aucune dépendance PostgreSQL
aucune surface N2 STRUCTURAL/N3 DECODED/N4 DOMAIN implémentée
workspace et graphes entièrement verts
prompt 0.3.2 cohérent avec la fondation conjointe ksp-store-lib + ksp-store-postgres-lib + tokio-postgres, sans absorber RawTransaction/RawAccountState