Files
khadhroony-solana-project/docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
2026-09-19 08:36:52 +02:00

23 KiB

Acquisition, workers, jobs et pipelines spécialisés

Objet

Ce document définit le lifecycle opérationnel autour des couches :

RAW -> STRUCTURAL -> DECODED -> DOMAIN

Il conserve la séparation stricte entre :

  • pipeline/processor : logique réutilisable d'une transformation ;
  • worker : service continu/live ;
  • job : traitement borné/historique/replay ;
  • application : contrôle/visualisation, sans duplication de la logique métier.

Règle de progression

RAW et STRUCTURAL sont les deux premières couches horizontales. Elles ne nécessitent aucun decoder Program.

Pour chacune, KSP peut terminer successivement :

persistence
-> backfill/replay
-> worker live si nécessaire
-> application de contrôle/inspection si utile

À partir de DECODED, les processors/jobs/workers/scenarios sont introduits avec le groupe Program concerné, en vertical slice, au lieu de créer à l'avance une grande flotte générique de workers de décodage/materialisation sans programme réel.

RAW

Pipeline RAW ingestion

Concept :

homogeneous transport model
        |
        v
conversion RAW DTO
        |
        v
ksp-store-api
        |
        v
persistence D1 RAW
        |
        v
notification after commit

La canonicalisation RawTransaction réutilisable entre producteurs est désormais attribuée à une lower-layer source-neutral dédiée, ksp-raw-transaction-lib, à matérialiser avec le Worker RAW. Elle possède uniquement la normalisation/canonicalisation commune et la construction des modèles RAW/provenance ; elle ne possède ni lifecycle Worker/Job, ni provider, ni routing réseau, ni campagne historique.

ksp-job-backfill-lib

Le premier backfill historique appartient à la couche RAW :

range/cursor historique
        |
        v
ksp-onchain-transport-lib
        |
        v
RAW ingestion
        |
        v
D1 RAW

Le job :

  • utilise ksp-job-api pour identité, lifecycle, annulation abstraite et observation latest-value ;
  • expose LatestAddress, BeforeAddress, AfterAddress et ExplicitSignatures avec bornes explicites de pages/candidats/concurrence ;
  • porte explicitement le réseau logique du Store dans le scope et dans l'identité (network, signature) de chaque transaction candidate ;
  • exclut rôle HTTP, provider, endpoint et protocole du fingerprint sémantique et de l'identité transactionnelle ;
  • hydrate uniquement via la voie observée getTransaction, afin de conserver la provenance du provider/endpoint réellement gagnant ;
  • produit un RAW v1 canonique déterministe puis persiste transaction + observation atomiquement via ksp-store-lib en mode normal ;
  • respecte les tombstones Purged, distingue missing/conflit/idempotence et ne pré-lit pas le Store avant hydratation ;
  • limite les hydrations concurrentes, avance seulement une frontier contiguë durable et retourne un checkpoint opaque caller-owned ;
  • arrête coopérativement les nouvelles admissions lors d'une annulation et draine une persistence Store déjà soumise ;
  • publie des snapshots latest-value sûrs sans payload RAW ni secrets/URLs Transport ;
  • n'effectue aucun décodage Program et n'écrit aucun fait STRUCTURAL/DECODED/DOMAIN.

Le caller desktop spécialisé actuel est ksp-app-backfill-desk. Il compose Config, le pool HTTP et Store, puis remet ces ressources au runtime Backfill. Il peut retenir le checkpoint terminal uniquement en mémoire Rust pour un Resume in-session ; cette rétention applicative ne transforme pas le checkpoint en garantie de reprise durable après redémarrage.

Cette verticale 0.3.6/0.3.7 est la première stratégie de backfill, pas la définition générale du backfill KSP. Son discovery getSignaturesForAddress + hydration getTransaction est HTTP parce que cette méthode a été choisie pour le premier vertical slice. Après stabilisation du worker live et de sa Desk, 0.3.16 ferme d'abord la résilience RAW partagée : divergences compatibles, variantes conflictuelles durables, résolution/restauration canonique, retry Store et reconnexion Transport. 0.3.17 réaudite ensuite ksp-job-backfill-lib pour intégrer les autres stratégies historiques/catch-up pertinentes identifiées par l'audit RAW Transaction de 0.3.9, puis 0.3.18 adapte ksp-app-backfill-desk au Job multi-route.

Worker RAW Transaction live

Le premier worker concret de la couche RAW est retenu sous le nom :

ksp-worker-raw-transaction-ingest-lib

Sa responsabilité est l'acquisition continue de RawTransaction puis la persistance via ksp-store-lib. Il ne décode pas de Program, ne possède aucun SQL/backend physique et ne fait pas de la source réseau une partie de l'identité canonique de transaction.

Le Worker matérialisé possède le lifecycle continu, les settings techniques bornés, la queue d'admission privée, la canonicalisation Common RAW, la persistance atomique backend-neutral, les snapshots latest-value et le shutdown borné. Sa première source productive est désormais Yellowstone standard + hydration HTTP getTransaction, injectée par RawTransactionIngestRuntimeResources sans lecture Config interne. Transaction, TransactionStatus et les transactions de Block deviennent des signaux coalescés puis hydratés ; BlockMeta et Slot restent continuity-only. Le Worker n'ouvre pas un second runtime parallèle et n'expose toujours aucune API publique d'enqueue.

Le modèle cible n'est pas :

worker -> un transport unique

mais :

source(s) / stratégie(s) d'acquisition
        |
        +--> discovery de références
        |        |
        |        +--> hydration éventuelle
        |
        +--> transaction complète directe
        |
        v
normalisation RawTransaction commune
        |
        +--> RawTransaction canonique
        +--> RawTransactionObservation par acquisition utile
        |
        v
ksp-store-lib

La verticale productive accepte désormais de 1 à 32 sources logiques d'un même réseau parmi cinq familles : Yellowstone, Standard WS logsSubscribe, Standard WS blockSubscribe, Helius transactionSubscribe et HTTP live block polling. Ces sources peuvent être complémentaires ou redondantes, mais aucune équivalence n'est supposée par le seul provider/protocole : la convergence canonique reste (network, signature) et la coverage utilise des scopes structuraux explicites.

Le reconnect/replay du stream est une capacité Transport. Le Worker conserve une processing frontier run-local sur le travail réellement observé et une continuity frontier séparée, projette des gaps bornés et leurs méthodes de réparation, mais ne possède aucun checkpoint durable ni campagne historique. Un gap du run courant peut être réconcilié par replay Transport, coverage redondante prouvée, scan HTTP borné, block fetch ou hydration d'une référence connue. Une perte de source ne devient non terminale que si sa plage passée est entièrement réconciliée et si les sources restantes couvrent encore tout le TargetCoverage futur. Toute lacune non bornable/non couverte reste terminale ; aucune récupération n'appelle le Job Backfill.

Résultat de l'audit 0.3.9

L'audit exhaustif est synthétisé dans 011-RAW_TRANSACTION_ACQUISITION.md. Il confirme qu'un besoin d'acquisition Solana/provider ne remonte pas dans ksp-worker-api : le contrat générique reste fermé et le Worker concret porte ses capabilities de source dans sa propre implémentation/runtime resources.

Les familles admises par la synthèse couvrent notamment :

HTTP getSignaturesForAddress + getTransaction
HTTP getBlocks/getBlock et autres stratégies slot/block pertinentes
WS logsSubscribe + hydration HTTP éventuelle
WS signatureSubscribe + hydration éventuelle
WS blockSubscribe lorsqu'une source/provider l'offre réellement
extensions transactionnelles provider-specific, dont Helius transactionSubscribe
Yellowstone transactions
Yellowstone transaction_status
Yellowstone blocks
Yellowstone block_meta
replay/from_slot/catch-up lorsqu'une implémentation/provider le permet
combinaisons multi-provider et multi-transport

La présence d'une voie dans l'architecture signifie qu'elle doit pouvoir être représentée lorsque son usage est pertinent ; son implémentation, son accessibilité commerciale et sa preuve live restent des dimensions séparées. Une même famille protocolaire peut servir au Worker, au Job ou aux deux selon l'intention, sans créer de relation entre ces producteurs.

Pour chaque voie, l'audit couvre au minimum :

Dimension Question à trancher
transport/protocole HTTP, WS standard, extension provider, Yellowstone ou autre ?
réseau Mainnet, Devnet, Testnet réellement disponibles et utiles ?
disponibilité gratuite/payante/provider-dependent ; limites actuelles à réauditer ?
temporalité live, catch-up, historique, replay récent ?
discovery comment la transaction est-elle découverte ?
transaction complète reçue directement ou hydration nécessaire ?
filtres programmes/comptes/signatures/slots/status et bornes ?
ordering/duplicates quelles garanties existent et quelles duplications sont normales ?
reconnect/replay que se passe-t-il après coupure ?
gap recovery quelle autre stratégie répare les trous ?
backpressure quelles limites et comportements si le consumer ralentit ?
commitment/finality quels niveaux sont disponibles et comment les interpréter ?
provenance quelles métadonnées sûres alimentent RawTransactionObservation ?
limites/quota RPS, connexions, subscriptions, credits ou autres limites actuelles ?
gap KSP Transport surface déjà disponible ou adaptation nécessaire dans 0.3.10 à 0.3.14 ?
gap KSP Config profil/secret/capability déjà disponible ou adaptation nécessaire ?
usage continuous ingest, gap repair, historical backfill ou combinaison ?

Helius et Config

L'audit de 0.3.9 doit réexaminer les offres et documentations Helius courantes au moment du travail. Les tiers, quotas et capabilities provider ne sont pas figés par ce document.

Décisions déjà acquises :

KSP_SECRET_HELIUS_API_KEY existe déjà côté environnement KSP
Helius HTTP et WS Mainnet/Devnet doivent être considérés comme futures sources candidates
les profils/endpoints réellement nécessaires sont ajoutés seulement dans la release Worker qui les consomme, principalement 0.3.13/0.3.14
aucune URL Helius nouvelle n'est ajoutée pendant 0.3.8
Config reste l'unique propriétaire des secrets et de leur résolution
les fonctionnalités standard et advanced/enhanced sont capability-gated, jamais supposées par le seul nom du provider

L'archive kbot3 doit être relue uniquement comme référence fonctionnelle pour identifier les méthodes/sources déjà exploitées ou envisagées. Aucun code, DTO, client, URL hardcodée, modèle Config ou dépendance kbot3 n'est repris comme source d'implémentation.

mainnet et mainnet-beta

L'audit 0.3.9 a conclu que mainnet est l'identité logique canonique KSP du réseau de production Solana. mainnet-beta reste un alias legacy/externe ou un libellé provider lorsqu'une API externe l'emploie réellement ; il ne constitue plus l'identité persistée cible de Store/RAW/Config.

Depuis 0.3.9-pre.006-fix.003, les profils Mainnet engagés dans Config/Store/Transport utilisent mainnet, de même que les tests et exemples runtime associés. KSP ne crée donc pas deux identités persistées pour le même cluster. Les anciennes données D1 RAW portant mainnet-beta sont considérées comme expérimentales et peuvent être droppées/recréées ; aucune migration destructive n'est imposée avant finalisation des Jobs/Workers RAW.

Les frontières externes restent libres de documenter ou d'accepter un nom provider legacy lorsque nécessaire, sans recopier ce nom dans RawNetworkId canonique.

Idempotence et multi-source

La convergence multi-source réutilise les invariants Store acquis :

identité canonique RawTransaction = réseau logique + signature
source/provider/protocole != identité canonique
acquisitions distinctes -> observations/provenances distinctes lorsque pertinentes
même contenu canonique -> idempotence
même identité avec contenu incompatible -> conflit explicite, jamais écrasement silencieux

La déduplication ne doit donc pas supprimer la provenance utile sous prétexte que la transaction canonique existe déjà.

Rôle de ksp-app-raw-transaction-ingest-desk

La Desk prévue après le worker choisit et supervise les source(s)/méthode(s) offertes par la composition réellement disponible. Elle ne possède pas la logique de discovery, hydration, déduplication, replay ou persistance.

Elle doit pouvoir représenter selon les capacités finales du Worker après 0.3.14 :

une source unique
plusieurs sources redondantes
une combinaison discovery + hydration
une stratégie live + gap repair

Le détail des RAW persistés reste la responsabilité de Store Desk.

STRUCTURAL

Pipeline RAW -> STRUCTURAL

La normalisation STRUCTURAL est générique Solana :

D1 RAW
   |
   v
Solana generic normalizer
   |
   v
D2 STRUCTURAL

Dépendances interdites :

STRUCTURAL normalizer -X-> ksp-program-api
STRUCTURAL normalizer -X-> ksp-program-lib
STRUCTURAL normalizer -X-> ksp-materializer-api

ksp-interface-lib peut fournir les wires Solana génériques nécessaires à la structure blockchain.

STRUCTURAL job

Un job borné peut rejouer :

RAW persisted range
        |
        v
STRUCTURAL normalizer
        |
        v
D2 STRUCTURAL

sans redemander les données au réseau.

STRUCTURAL worker

Le STRUCTURAL worker continu peut consommer le backlog RAW nouvellement persisté et produire STRUCTURAL.

Le Store reste source de vérité du backlog ; les notifications ne sont qu'un wake-up.

DECODED et DOMAIN

Introduction par groupe fonctionnel

KSP ne crée pas d'abord un unique « worker decoder de tout Solana » puis tous les materializers plusieurs séries plus tard.

Chaque groupe prioritaire introduit les capacités nécessaires :

STRUCTURAL inputs du groupe
      |
      v
Program decoder
      |
      v
decoded facts
      |
      v
generic materialization / DECODED persistence
      |
      v
DOMAIN projection si utile

Puis le même groupe avance vers préparation d'exécution, policy, execution et scénarios Devnet.

Les jobs de replay et workers live correspondants réutilisent les mêmes processors du groupe.

Ordre fonctionnel

Direction actuelle :

Solana Core Programs
-> SPL token/trading
-> token metadata
-> Anchor
-> Meteora
-> Raydium
-> Pump
-> Orca
-> Market Desk V1
-> Jupiter / OKX routing
-> Market Desk V2
-> trading-adjacent
-> general decoding

Un satellite protocolaire reste avec son groupe : Meteora vaults avec Meteora, Pump fee avec Pump, etc.

Worker API

ksp-worker-api reste une lifecycle API générique pour services continus. Elle est volontairement stabilisée avant l'audit détaillé du premier worker RAW afin de ne pas encoder Solana, Transport, Store ou une source d'acquisition particulière dans son contrat.

Le pattern latest-value de ksp-job-api peut être réutilisé conceptuellement lorsqu'il convient, mais Worker et Job conservent des sémantiques distinctes : un worker est un service continu qui peut rester actif indéfiniment, tandis qu'un job représente un traitement borné/terminable. Une dépendance ksp-worker-api -> ksp-job-api n'est pas supposée ; la réutilisation concrète doit être justifiée par un contrat réellement commun.

Contrats communs actuels :

WorkerId
WorkerKindCode
WorkerState
WorkerHealth
WorkerActivity
WorkerLifecycle
WorkerStopToken
WorkerSnapshotSequence
WorkerSnapshot
WorkerSnapshotSource

Le snapshot commun est fixe et ne porte aucun payload métier. WorkerSnapshotSource suit une sémantique latest-value object-safe. WorkerStopToken exprime une intention coopérative partagée.

La crate n'expose aucune opération runtime universelle start, stop, restart ou reconfigure. Le Worker concret possède son runtime et traduit ses opérations de contrôle en transitions WorkerLifecycle et snapshots communs.

Job API

ksp-job-api reste distinct de Worker API et volontairement runtime-neutral.

Contrats communs actuels :

JobId
JobKindCode
JobState
JobCompletion
JobLifecycle
JobCancellationToken
JobNotificationSequence
JobNotification<Snapshot>
JobSnapshotSource

Le lifecycle commun est borné aux transitions explicitement validées entre Created, Running, Cancelling et les états terminaux Completed(Complete|Partial), Cancelled, Failed. Il ne définit ni pause, ni resume, ni scheduler, ni runtime d'exécution générique.

JobSnapshotSource suit une sémantique latest-value : un listener lit une valeur complète courante puis peut attendre une séquence plus récente ; les valeurs intermédiaires peuvent être coalescées. Le snapshot métier reste possédé par le job concret.

L'annulation commune est une intention coopérative. Le job concret décide quelles attentes peuvent être interrompues et quelles opérations engagées doivent être drainées.

Aucune ksp-job-control-lib n'est créée sans duplication concrète.

Backlog, claim et idempotence

Pour les processors asynchrones :

notification wake-up
      +
periodic polling
      |
      v
Store backlog query
      |
      v
claim/lease bounded batch
      |
      v
processor
      |
      v
output + durable outcome

Les notifications ne remplacent jamais le Store.

La taille du batch, la priorité et la stratégie de sélection appartiennent au worker/job/executor. Store fournit les primitives de query/cursor/claim nécessaires et peut refléter une contrainte physique du backend, mais ksp-store-lib n'invente pas un plafond métier global inférieur à la capacité réellement disponible.

La sémantique cible reste at-least-once avec idempotence durable, plutôt qu'un faux exactly-once.

Processing outcomes

Les états exacts seront définis avec le premier processor durable, mais doivent distinguer au minimum les familles conceptuelles :

  • success ;
  • transient failure ;
  • deterministic failure ;
  • not applicable ;
  • unsupported ;
  • superseded/replayed lorsque pertinent.

Reprise après crash

Un worker/job qui promet une reprise après crash doit reconstruire son état depuis des données durables : inputs persistés, claims/leases/outcomes lorsqu'ils existent, cursors/checkpoints et versions de processor.

Le premier backfill RAW retourne un BackfillCheckpoint caller-owned lié au JobId et au fingerprint de scope. La crate ne persiste pas ce checkpoint elle-même : tant qu'un caller ne l'enregistre pas durablement, il s'agit d'une primitive de reprise contrôlée, pas d'une promesse crash-safe automatique.

La mémoire du processus ne constitue jamais l'unique source d'une garantie de reprise durable.

Logging

Tous les pipelines/workers/jobs runtime utilisent ksp-logging-lib.

Les événements utiles comprennent notamment :

  • start/stop ;
  • batch/range ;
  • progression ;
  • retries ;
  • claim/lease ;
  • rate-limit/backpressure ;
  • processing outcome ;
  • checkpoint ;
  • reconfiguration desired/effective ;
  • erreurs redacted.

Dépendances de composition

RAW backfill

État matérialisé après extraction de la normalisation commune :

ksp-job-backfill-lib
    -> ksp-job-api
    -> ksp-core-lib
    -> ksp-logging-lib
    -> ksp-onchain-transport-lib
    -> ksp-raw-transaction-lib
    -> ksp-store-lib       # façade Store ; default-features=false côté Job
    -> futures-util/tokio  # runtime privé de Backfill
    -> serde_json/sha2     # discovery/checkpoint/observation propres au Job

Le Job conserve seul ses scopes, campagnes, checkpoints et lifecycle. La canonicalisation transactionnelle partagée appartient désormais à ksp-raw-transaction-lib; 0.3.17 étendra les stratégies historiques sans déplacer ce rôle.

RAW worker

Fondation et acquisition live matérialisées :

ksp-worker-raw-transaction-ingest-lib
    -> ksp-core-lib
    -> ksp-worker-api
    -> ksp-raw-transaction-lib
    -> ksp-store-lib       # façade Store ; default-features=false ; aucun backend physique direct
    -> ksp-logging-lib
    -> ksp-onchain-transport-lib
    -> sha2 / tokio        # observation key + runtime privé

ksp-app-raw-transaction-ingest-desk
    -> ksp-config-lib
    -> ksp-worker-api
    -> ksp-worker-raw-transaction-ingest-lib
    -> ksp-onchain-transport-lib
    -> ksp-store-lib

Le Worker conserve seul son runtime continu, ses sources actives, sa continuité et son lifecycle. Il n'appelle ni ne pilote le Job Backfill et n'expose aucun ingress public : ses sources internes validées alimentent les queues bornées et subissent leur backpressure. Le Desk possède uniquement la composition et la supervision ; plusieurs routes d'un même réseau peuvent avoir des Workers indépendants sur le Store partagé sans déplacer acquisition ou persistance vers Tauri/TypeScript.

Les événements Interface peuvent servir de signal provider-neutral à la composition live, mais ne constituent jamais le backlog durable. Après crash ou perte d'un événement, la reprise s'appuie sur Store et sur les primitives de replay/hydratation appropriées.

STRUCTURAL job/worker

STRUCTURAL job / STRUCTURAL worker
    -> ksp-interface-lib si wires génériques nécessaires
    -> ksp-store-api
    -> ksp-core-lib
    -> ksp-logging-lib

Pas de Program API.

Groupes DECODED/DOMAIN

Le composant de composition du groupe peut utiliser :

ksp-program-api
ksp-program-lib ou extension compatible
ksp-materializer-api
ksp-materializer-lib ou implementation compatible
ksp-store-api

selon les capacités réellement introduites.

Questions laissées ouvertes

  • politique d'alias externe mainnet-beta à matérialiser uniquement aux frontières qui en ont réellement besoin, sans créer une seconde identité Store ;
  • modèle de claim/lease PostgreSQL pour les futurs processors continus ;
  • taille de batch et stratégie backpressure des workers de processing ;
  • découpage des workers DECODED/DOMAIN par groupe lorsque les premiers groupes existent ;
  • mécanisme IPC des applications de contrôle futures.