70 KiB
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 :
0.3.1 = ksp-store-api uniquement
0.3.2 = ksp-store-lib + ksp-store-postgres-lib
feature postgres par défaut
PostgreSQL de référence via tokio-postgres
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 0.3.2 puisse ensuite demander :
quelle représentation PostgreSQL satisfait le mieux ce contrat ?
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
health/readiness communs utiles
contrats/capabilities backend extensibles
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 :
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.
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
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 et les futurs backends :
- utiliser une URI/DSN lorsque le moteur possède une forme URI naturelle (
postgresql://..., futurmysql://..., etc.) ; - permettre des options typées backend-specific uniquement lorsqu'elles sont réellement nécessaires et sans
serde_json::Valueopaque comme contrat runtime ; - considérer l'URI entière comme
Secretdè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.exampleau même delta que leur première utilisation ; - ne jamais versionner un vrai
.envni un URI/credential réel ; - ne jamais exposer URI, password, token, query secret ou chemin sensible dans
Debug, logs, erreurs ou health ; - distinguer
backend inconnudebackend KSP connu mais non compiléavant tentative de connexion ; - éviter tout fallback implicite vers
PG*,.pgpassou 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 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é. 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 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 en 0.3.2; 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 : 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 :
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
La façade peut exposer un health commun minimal :
Unknown
Healthy
Degraded
Unhealthy
avec un backend code sûr et un code diagnostic borné si nécessaire.
Ne pas 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. ksp-store-lib ne projette vers le contrat commun 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 ; 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 | 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 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 |
navigation/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
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 15–20 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
Workspace complet, tests Store API, graphes Cargo. Aucun développement fonctionnel nouveau.
pre.009 — Réconciliation documentaire finale
README/USAGE, plan, validation et références durables réellement concernées. Aucun CHANGELOG.md, ROADMAP.md ni prompt suivant.
pre.010 — Préparation de publication minimale
Uniquement :
Cargo.toml
CHANGELOG.md
ROADMAP.md
prompts/021-V0_3_2_START_PROMPT.md
delta pre.010
Le prompt 0.3.2 ouvre ensemble ksp-store-lib et ksp-store-postgres-lib, avec feature postgres par défaut et tokio-postgres strictement dans la crate backend.
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 ksp-store-lib + ksp-store-postgres-lib / PostgreSQL reference via tokio-postgres
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 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.
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
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 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
queries/pagination cursorisée sans plafond métier KSPs
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
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 ksp-store-lib + ksp-store-postgres-lib + tokio-postgres