19 Commits

Author SHA1 Message Date
c9047af626 v0.3.9-rel.001 2026-09-05 20:47:42 +02:00
52d692e8ec v0.3.9-pre.009 2026-09-05 20:30:45 +02:00
5c3c8b3fef v0.3.9-pre.008 2026-09-05 17:39:50 +02:00
b0079fc3ee v0.3.9-pre.007-fix.002 2026-09-05 15:27:43 +02:00
ecfa123a79 v0.3.9-pre.007-fix.001 2026-09-05 14:54:01 +02:00
9da372e4a2 v0.3.9-pre.007 2026-09-05 14:36:12 +02:00
9a998e7b52 v0.3.9-pre.006-fix.003 2026-09-05 13:53:36 +02:00
c3c9a80310 v0.3.9-pre.006-fix.002 2026-09-05 12:32:20 +02:00
116eabba7d v0.3.9-pre.006-fix.001 2026-09-05 02:24:47 +02:00
e6c2649410 v0.3.9-pre.006 2026-09-05 00:10:15 +02:00
e85d6f6963 v0.3.9-pre.005 2026-09-04 21:53:36 +02:00
eb39aaa904 v0.3.9-pre.004 2026-09-04 21:23:46 +02:00
bb7ee3cc3a v0.3.9-pre.003 2026-09-04 14:45:05 +02:00
84647c12ea v0.3.9-pre.002-fix.002 2026-09-04 14:29:24 +02:00
156d237460 v0.3.9-pre.002-fix.001 2026-09-04 14:23:54 +02:00
7d0710a1e6 v0.3.9-pre.002 2026-09-04 14:21:05 +02:00
5c797827f7 v0.3.9-pre.001-fix.002 2026-09-04 14:11:21 +02:00
b87b7f4505 v0.3.9-pre.001-fix.001 2026-09-04 13:16:08 +02:00
92f08ca5a8 v0.3.9-pre.001 2026-09-04 12:47:53 +02:00
82 changed files with 6718 additions and 180 deletions

View File

@@ -1,5 +1,5 @@
# file: .env.example # file: .env.example
# version: 13 # version: 14
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths. # KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable. # The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
@@ -28,16 +28,16 @@ KSP_WALLETS_DIRECTORY=wallets
KSP_PUBLIC_SOLANA_DEVNET_HTTP_URL=https://api.devnet.solana.com KSP_PUBLIC_SOLANA_DEVNET_HTTP_URL=https://api.devnet.solana.com
# Optional public Solana Mainnet HTTP endpoint override used by config/std.transport.json and its example. # Optional public Solana Mainnet HTTP endpoint override used by config/std.transport.json and its example.
# The committed Transport document falls back to https://api.mainnet-beta.solana.com when this variable is absent. # The committed Transport document falls back to https://api.mainnet.solana.com when this variable is absent.
KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL=https://api.mainnet-beta.solana.com KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL=https://api.mainnet.solana.com
# Optional public Solana Devnet WebSocket endpoint override used by config/std.transport.json. # Optional public Solana Devnet WebSocket endpoint override used by config/std.transport.json.
# The committed Transport document falls back to wss://api.devnet.solana.com when this variable is absent. # The committed Transport document falls back to wss://api.devnet.solana.com when this variable is absent.
KSP_PUBLIC_SOLANA_DEVNET_WS_URL=wss://api.devnet.solana.com KSP_PUBLIC_SOLANA_DEVNET_WS_URL=wss://api.devnet.solana.com
# Optional public Solana Mainnet WebSocket endpoint override used by config/std.transport.json and its example. # Optional public Solana Mainnet WebSocket endpoint override used by config/std.transport.json and its example.
# The committed Transport document falls back to wss://api.mainnet-beta.solana.com when this variable is absent. # The committed Transport document falls back to wss://api.mainnet.solana.com when this variable is absent.
KSP_PUBLIC_SOLANA_MAINNET_WS_URL=wss://api.mainnet-beta.solana.com KSP_PUBLIC_SOLANA_MAINNET_WS_URL=wss://api.mainnet.solana.com
# Optional complete private-provider HTTP endpoint URL used only by the Transport example when explicitly selected. # Optional complete private-provider HTTP endpoint URL used only by the Transport example when explicitly selected.
# Keep provider credentials in a KSP_SECRET_* variable; do not copy a real credential-bearing URL into committed JSON. # Keep provider credentials in a KSP_SECRET_* variable; do not copy a real credential-bearing URL into committed JSON.

View File

@@ -1,8 +1,22 @@
<!-- file: CHANGELOG.md --> <!-- file: CHANGELOG.md -->
<!-- version: 28 --> <!-- version: 29 -->
# Changelog KSP # Changelog KSP
## 0.3.9 — Worker API générique + audit exhaustif d'acquisition RawTransaction — 2026-09-05
`0.3.9` introduit `ksp-worker-api` comme façade générique, courte et runtime-neutral pour services continus. La surface stable reste Core-only et expose `WorkerId`, `WorkerKindCode`, `WorkerState`, `WorkerHealth`, `WorkerActivity`, `WorkerLifecycle`, `WorkerStopToken`, `WorkerSnapshotSequence`, `WorkerSnapshot`, `WorkerSnapshotFuture` et `WorkerSnapshotSource`. Les identités sont bornées et redacted, le lifecycle possède des transitions explicites avec terminaux immuables, le stop token est partagé/idempotent, la séquence ne wrappe pas et le snapshot source latest-value reste object-safe, `Send + Sync` et implémentable depuis l'extérieur. L'API ne possède aucun runtime `start/stop` universel, aucune sémantique Job/checkpoint, aucun domaine Solana et aucune dépendance Transport/Store/Config/Tauri.
Après freeze de cette API, la seconde moitié de la release réalise puis consolide l'audit exhaustif des voies d'acquisition `RawTransaction`. Le document durable `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` est réécrit comme une synthèse unique Store-centrique : `RawTransaction` est le cœur RAW durable, tandis que `RawTransactionObservation` conserve les provenances multiples. L'audit distingue systématiquement possibilité protocolaire/provider, support architectural KSP, support implémenté et preuve live ; il couvre HTTP, WebSocket standard, Helius `transactionSubscribe`, Yellowstone gRPC, acquisition par blocs/slots, discovery + hydration, replay/gap repair, archives et sources EARLY/pre-execution. Les matrices recensent aussi les principaux providers/réseaux/tier/coûts connus sans réduire l'architecture aux seuls comptes gratuits actuellement disponibles.
La séparation des producteurs RAW est figée. `ksp-job-backfill-lib` reste un producteur historique **paramétré, borné et terminable** ; le futur `ksp-worker-raw-transaction-ingest-lib` sera un producteur live **continu start/stop sans requête métier historique**. Ils ne s'appellent pas, ne se supervisent pas et ne collaborent pas : chacun alimente indépendamment le même Store. HTTP, WS, gRPC, replay et archive sont des capabilities orthogonales à ces rôles ; un Worker peut utiliser HTTP pour hydration ou réparation de sa propre continuité live, tandis qu'un Job pourra utiliser gRPC/replay si une campagne historique bornée le justifie. Le handoff `0.3.10` retient `ksp-raw-transaction-lib` comme lower-layer source-neutral commune afin d'extraire la canonicalisation RAW v1 aujourd'hui locale au Backfill sans duplication ni edge Job ↔ Worker ; les golden bytes/hash RAW v1 doivent rester inchangés.
La release normalise aussi l'identité réseau KSP : `mainnet` devient l'identité canonique dans Config/Store/Transport/tests et `mainnet-beta` reste seulement un alias legacy/externe lorsque la frontière provider l'exige. Les données Mainnet N1 encore utilisées comme données de test ne dictent aucune compatibilité durable et aucune migration SQL n'est introduite pour préserver l'ancien libellé. Pendant le gate final, les baselines workspace sont également relevées à `jsonschema ^0.53` et `yellowstone-grpc-proto ^12.7`; Yellowstone 12.7 ajoute le RPC serveur `SubscribeGossip`, ce qui nécessite uniquement l'adaptation des fixtures `Geyser` avec une réponse `UNIMPLEMENTED` et n'ouvre aucune capability Gossip de production.
Le gate technique final passe les audits Rust/Markdown, `cargo check --workspace`, Clippy workspace/all-targets/all-features avec `-D warnings`, 385/385 tests unitaires `ksp-onchain-transport-lib`, 43/43 canaries `release_completeness`, `cargo test --workspace --all-targets --all-features`, le bundle complet `ksp-worker-api` et les graphes Cargo jusqu'à `cargo tree --duplicates`; seuls les smokes/probes explicitement opt-in restent ignorés. La réconciliation documentaire `pre.008` réaligne ensuite README/USAGE/indexes/architecture/plan/validation et son gate opérateur repasse audits Rust/Markdown plus `cargo check --workspace` en `0.3.9-pre.8`.
`prompts/029-V0_3_10_START_PROMPT.md` ouvre `0.3.10` exclusivement depuis le tag stable `v0.3.9`. La prochaine release doit d'abord matérialiser `ksp-raw-transaction-lib`, migrer le Backfill vers cette canonicalisation commune sans changement fonctionnel, puis construire `ksp-worker-raw-transaction-ingest-lib` multi-source dès V1. Son `pre.001` est obligatoirement un gate d'audit/sizing : inventaire exact des gaps Transport/Config, runtime Worker, capabilities live, continuité, preuves provider et dépendances avant toute implémentation lourde.
## 0.3.8 — Store Desk V1 RAW, inspection backend-neutral et trajectoire Worker multi-source — 2026-09-04 ## 0.3.8 — Store Desk V1 RAW, inspection backend-neutral et trajectoire Worker multi-source — 2026-09-04
`0.3.8` introduit `ksp-app-store-desk`, application Tauri KSP read-only dédiée à l'inspection du Store. Le package reprend le gabarit Desk commun (splash/shell/assets/styles/tracing), compose `ksp-config-lib`, `ksp-logging-lib` et la seule façade `ksp-store-lib`, et interdit à l'application tout accès direct à `ksp-store-api`, `ksp-store-postgres-lib`, SQL, driver/pool PostgreSQL, Transport on-chain ou ressource physique. Le bootstrap ouvre un Store lié au profil/réseau composite, expose health/runtime backend-neutral et effectue un shutdown borné à la fermeture de la fenêtre principale. `0.3.8` introduit `ksp-app-store-desk`, application Tauri KSP read-only dédiée à l'inspection du Store. Le package reprend le gabarit Desk commun (splash/shell/assets/styles/tracing), compose `ksp-config-lib`, `ksp-logging-lib` et la seule façade `ksp-store-lib`, et interdit à l'application tout accès direct à `ksp-store-api`, `ksp-store-postgres-lib`, SQL, driver/pool PostgreSQL, Transport on-chain ou ressource physique. Le bootstrap ouvre un Store lié au profil/réseau composite, expose health/runtime backend-neutral et effectue un shutdown borné à la fermeture de la fenêtre principale.

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 475 # version: 489
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"] members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api"]
[workspace.package] [workspace.package]
version = "0.3.8" version = "0.3.9"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
@@ -25,7 +25,7 @@ fs2 = { version = "^0.4" }
futures-util = { version = "^0.3", default-features = false } futures-util = { version = "^0.3", default-features = false }
getrandom = { version = "^0.4", default-features = false } getrandom = { version = "^0.4", default-features = false }
http = { version = "^1.5", default-features = false } http = { version = "^1.5", default-features = false }
jsonschema = { version = "^0.52", default-features = false } jsonschema = { version = "^0.53", default-features = false }
reqwest = { version = "^0.13", default-features = false } reqwest = { version = "^0.13", default-features = false }
rustls = { version = "^0.23", default-features = false } rustls = { version = "^0.23", default-features = false }
rustls-native-certs = { version = "^0.8", default-features = false } rustls-native-certs = { version = "^0.8", default-features = false }
@@ -49,7 +49,7 @@ tokio-tungstenite = { version = "^0.30", default-features = false }
tonic = { version = "^0.14", default-features = false } tonic = { version = "^0.14", default-features = false }
tonic-prost = { version = "^0.14", default-features = false } tonic-prost = { version = "^0.14", default-features = false }
ts-rs = { version = "^12.0" } ts-rs = { version = "^12.0" }
yellowstone-grpc-proto = { version = "^12.6", default-features = false } yellowstone-grpc-proto = { version = "^12.7", default-features = false }
zeroize = { version = "^1.9" } zeroize = { version = "^1.9" }
[workspace.lints.rust] [workspace.lints.rust]

View File

@@ -1,5 +1,5 @@
<!-- file: README.md --> <!-- file: README.md -->
<!-- version: 10 --> <!-- version: 11 -->
# Khadhroony Solana Project # Khadhroony Solana Project
@@ -55,7 +55,9 @@ La couche RAW dispose d'une façade Store backend-neutral et d'un premier job hi
`ksp-app-store-desk` fournit l'inspection desktop read-only du Store RAW via `ksp-store-lib` : health/runtime backend-neutral, tables server-side `RawTransaction` et `RawAccountState`, observations associées, détails avec previews bornées et lecture des états de rétention/tombstones. DataTables possède l'unique pagination visible de l'inspection random-access ; la pagination cursor/keyset des consumers machine reste distincte et intacte. `ksp-app-store-desk` fournit l'inspection desktop read-only du Store RAW via `ksp-store-lib` : health/runtime backend-neutral, tables server-side `RawTransaction` et `RawAccountState`, observations associées, détails avec previews bornées et lecture des états de rétention/tombstones. DataTables possède l'unique pagination visible de l'inspection random-access ; la pagination cursor/keyset des consumers machine reste distincte et intacte.
Ces contrats restent distincts des futurs workers continus : un job borné n'est ni un service worker ni un pipeline générique imposé aux autres couches. `ksp-worker-api` fournit désormais la fondation générique des services continus : identité Worker, lifecycle borné, health/activity, intention de stop coopératif et observation latest-value. Cette API reste Core-only, runtime-neutral et sans connaissance Solana, Transport, Store ou Job. Elle ne démarre ni n'arrête elle-même un runtime concret.
Le futur `ksp-worker-raw-transaction-ingest-lib` sera un consumer concret distinct du Job Backfill : il fonctionnera en continu entre Start et Stop, sans scope historique métier, afin d'acquérir et persister des `RawTransaction` selon les sources/capabilities configurées. `ksp-job-backfill-lib` conserve de son côté son rôle historique paramétré et borné. Les deux producteurs restent indépendants et convergent uniquement vers les mêmes contrats RAW/Store.
## Points d'entrée ## Points d'entrée

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md --> <!-- file: ROADMAP.md -->
<!-- version: 103 --> <!-- version: 104 -->
# Roadmap KSP # Roadmap KSP
@@ -101,8 +101,8 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
- [X] `0.3.6``ksp-job-api` + `ksp-job-backfill-lib` stables pour la première verticale historique `RawTransaction` : lifecycle/cancellation/latest-value runtime-neutral, scopes `Latest`/`Before`/`After`/signatures explicites, découverte/hydratation Transport observée, RAW v1 canonique, persistance Store atomique/idempotente, concurrence bornée, frontier contiguë, checkpoint/reprise caller-owned, snapshots sûrs et hardening externe sans dépendance backend/provider directe. - [X] `0.3.6``ksp-job-api` + `ksp-job-backfill-lib` stables pour la première verticale historique `RawTransaction` : lifecycle/cancellation/latest-value runtime-neutral, scopes `Latest`/`Before`/`After`/signatures explicites, découverte/hydratation Transport observée, RAW v1 canonique, persistance Store atomique/idempotente, concurrence bornée, frontier contiguë, checkpoint/reprise caller-owned, snapshots sûrs et hardening externe sans dépendance backend/provider directe.
- [X] `0.3.7``ksp-app-backfill-desk` livrée comme Desk Tauri KSP spécialisée : composition Config Mainnet/Devnet/Testnet, quatre scopes HTTP, validation/start single-run, monitoring latest-value `ksp-backfill-status`, Cancel ciblé/idempotent, Resume in-session par checkpoint Rust-only réémis pour un nouveau JobId, autocomplete libre depuis `ksp-core-lib`, hardening IPC/dependency boundaries et build Linux `.deb`/`.rpm`/`.AppImage`. Aucun SQL/backend/provider physique ni checkpoint durable nest exposé. - [X] `0.3.7``ksp-app-backfill-desk` livrée comme Desk Tauri KSP spécialisée : composition Config Mainnet/Devnet/Testnet, quatre scopes HTTP, validation/start single-run, monitoring latest-value `ksp-backfill-status`, Cancel ciblé/idempotent, Resume in-session par checkpoint Rust-only réémis pour un nouveau JobId, autocomplete libre depuis `ksp-core-lib`, hardening IPC/dependency boundaries et build Linux `.deb`/`.rpm`/`.AppImage`. Aucun SQL/backend/provider physique ni checkpoint durable nest exposé.
- [X] `0.3.8``ksp-app-store-desk` V1 RAW livrée sur le gabarit KSP courant comme application Tauri read-only backend-neutral. La Desk compose Config + Logging + `ksp-store-lib`, expose health/runtime sûrs, DataTables `serverSide` pour `RawTransaction`, `RawAccountState` et leurs observations, détails bornés, provenance sûre et états de rétention/tombstone, sans SQL/backend physique/Transport dans l'application. La nouvelle inspection random-access `offset + limit + counts exacts` reste distincte de la pagination machine cursor/keyset conservée pour workers/backfills/replays. Le gate final et les builds Linux `.deb`/`.rpm` sont verts. - [X] `0.3.8``ksp-app-store-desk` V1 RAW livrée sur le gabarit KSP courant comme application Tauri read-only backend-neutral. La Desk compose Config + Logging + `ksp-store-lib`, expose health/runtime sûrs, DataTables `serverSide` pour `RawTransaction`, `RawAccountState` et leurs observations, détails bornés, provenance sûre et états de rétention/tombstone, sans SQL/backend physique/Transport dans l'application. La nouvelle inspection random-access `offset + limit + counts exacts` reste distincte de la pagination machine cursor/keyset conservée pour workers/backfills/replays. Le gate final et les builds Linux `.deb`/`.rpm` sont verts.
- [ ] `0.3.9` Introduire `ksp-worker-api` comme API **générique et volontairement courte** de lifecycle/health/progression/snapshot pour services continus, distincte de `ksp-job-api` et sans dépendance Solana/Transport/Store/Tauri. Une fois l'API Worker fonctionnellement fermée, terminer la release par un audit fonctionnel exhaustif des sources/méthodes d'acquisition `RawTransaction` : HTTP, WS standard, extensions provider, Yellowstone gRPC, blocks/slots, discovery + hydration, replay/gap-repair et combinaisons multi-provider. Cet audit prépare `0.3.10` sans ajouter d'endpoint ni de Config provider dans `0.3.9`. - [X] `0.3.9``ksp-worker-api` stable comme API générique et volontairement courte pour services continus : identité/kind bornés, lifecycle explicite, health/activity sûrs, stop token partagé, séquence/snapshot latest-value et `WorkerSnapshotSource` object-safe, avec dépendance Core-only et sans runtime/start-stop universel, Solana, Transport, Store, Config ou Tauri. Après freeze Worker API, l'audit `RawTransaction` a été consolidé dans une synthèse Store-centrique exhaustive séparant possibilités/support/preuve et classant HTTP, WS standard/provider, Yellowstone gRPC, blocs/slots, discovery + hydration, replay de continuité, archives et sources EARLY. Le Job Backfill historique paramétré et le Worker Ingest live start/stop sont deux producteurs indépendants du même Store ; `mainnet` est désormais l'identité KSP canonique et `mainnet-beta` un alias legacy/externe. Le handoff retient `ksp-raw-transaction-lib` comme lower-layer commune de canonicalisation RAW v1 pour `0.3.10`, sans edge Job ↔ Worker.
- [ ] `0.3.10` — Introduire `ksp-worker-raw-transaction-ingest-lib` **multi-source dès V1**. Le worker compose les stratégies retenues par l'audit `0.3.9` comme sources alternatives, complémentaires, redondantes ou spécialisées live/catch-up/gap-repair, converge vers `RawTransaction + RawTransactionObservation`, persiste par `ksp-store-lib` et utilise `ksp-worker-api` pour supervision/cancellation. Cette release porte aussi, et seulement si l'audit les justifie, les adaptations nécessaires de `ksp-onchain-transport-lib`/`ksp-config-lib` : futures sources Helius HTTP/WS Mainnet/Devnet avec réutilisation de `KSP_SECRET_HELIUS_API_KEY`, capabilities/tier réaudités au moment du travail, et éventuelle canonicalisation `mainnet`/`mainnet-beta` uniquement avec stratégie de compatibilité sûre. Aucun decode Program ni dépendance backend/provider directe dans le worker. - [ ] `0.3.10` — Introduire `ksp-raw-transaction-lib` puis `ksp-worker-raw-transaction-ingest-lib` **multi-source dès V1**. La lower-layer commune extrait de `ksp-job-backfill-lib` la canonicalisation RAW v1 sans changer ses golden bytes/hash ni ses campagnes historiques. Le worker continu est démarré/arrêté sans paramètres métier de campagne, ouvre les sources/rôles activés par la composition Config, acquiert depuis son démarrage, persiste `RawTransaction + RawTransactionObservation`, publie ses snapshots/notifications indépendamment de leurs lecteurs et répare uniquement ses propres gaps de continuité live. HTTP, WS, Yellowstone gRPC et extensions provider sont des capabilities orthogonales : HTTP peut servir au live/hydration et gRPC/WS ne deviennent jamais synonymes de Worker. La release porte seulement les adaptations Transport/Config/common réellement justifiées par l'audit (`get_block_observed`, projections full source-neutral, provenance sûre, replay `from_slot`, profils/capabilities nécessaires), avec support architectural conservé même lorsque certains smokes provider restent bloqués par tier. Aucun nouveau backfill multi-stratégie, decode Program, backend physique direct ni dépendance Worker ↔ Job.
- [ ] `0.3.11` — Introduire `ksp-app-raw-transaction-ingest-desk`, Desk Tauri KSP spécialisée qui choisit et supervise **une ou plusieurs sources/méthodes** réellement admises par `0.3.10` : lifecycle, health, rates, backpressure, reconnect/recovery, gap state et compteurs sûrs. La Desk ne réimplémente ni discovery, hydration, déduplication, reprise ni persistence ; l'inspection détaillée des RAW persistés reste la responsabilité de `ksp-app-store-desk`. - [ ] `0.3.11` — Introduire `ksp-app-raw-transaction-ingest-desk`, Desk Tauri KSP spécialisée qui choisit et supervise **une ou plusieurs sources/méthodes** réellement admises par `0.3.10` : lifecycle, health, rates, backpressure, reconnect/recovery, gap state et compteurs sûrs. La Desk ne réimplémente ni discovery, hydration, déduplication, reprise ni persistence ; l'inspection détaillée des RAW persistés reste la responsabilité de `ksp-app-store-desk`.
- [ ] `0.3.12` — Étendre `ksp-job-backfill-lib` et `ksp-app-backfill-desk` au **backfill multi-source/multi-stratégie** à partir de la même matrice d'acquisition auditée en `0.3.9`. Conserver la stratégie actuelle `getSignaturesForAddress + getTransaction` comme première voie HTTP valide, puis ajouter seulement les voies historiques/catch-up/gap-repair réellement pertinentes et sûres (par exemple block/slot, replay provider lorsqu'il existe), sans supposer qu'une source live WS constitue un historique universel. - [ ] `0.3.12` — Étendre `ksp-job-backfill-lib` et `ksp-app-backfill-desk` au **backfill multi-source/multi-stratégie** à partir de la même matrice d'acquisition auditée en `0.3.9`. Conserver la stratégie actuelle `getSignaturesForAddress + getTransaction` comme première voie HTTP valide, puis ajouter seulement les voies historiques/catch-up/gap-repair réellement pertinentes et sûres (par exemple block/slot, replay provider lorsqu'il existe), sans supposer qu'une source live WS constitue un historique universel.
@@ -114,9 +114,9 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
### TODO/IDEAS — taxonomie N1, processing et rétention ### TODO/IDEAS — taxonomie N1, processing et rétention
- [ ] **TODO** — maintenir la matrice dadmission HTTP/WS/gRPC/provider lors de toute nouvelle famille N1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte. - [ ] **TODO** — maintenir la matrice dadmission HTTP/WS/gRPC/provider lors de toute nouvelle famille N1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte.
- [ ] **TODO `0.3.9`**produire après fermeture de `ksp-worker-api` une matrice RAW Transaction unique et réutilisable par `0.3.10` puis `0.3.12`, couvrant discovery, hydration/direct payload, filtres, ordering/duplicates, reconnect/replay, gap repair, backpressure, commitment, provenance, quotas, gaps Transport/Config et applicability continuous-ingest/catch-up/backfill. - [X] **TODO `0.3.9`**matrice RAW Transaction unique produite et consolidée dans `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`, avec séparation possibilités/support/preuve, capabilities orthogonales aux producteurs, provenance/déduplication/continuité et handoffs indépendants `0.3.10` Worker live / `0.3.12` Backfill historique.
- [ ] **TODO Helius futur** ne pas ajouter d'URL/endpoints Helius en `0.3.8` ni pendant la construction générique de Worker API. L'audit `0.3.9` réévalue les capabilities/tier courants ; `0.3.10` peut ensuite ajouter les profils HTTP/WS réellement nécessaires en réutilisant exclusivement `KSP_SECRET_HELIUS_API_KEY` via Config. - [ ] **TODO Helius `0.3.10`** — ajouter uniquement les profils/capabilities Helius réellement nécessaires au Worker live après revalidation des surfaces/tier courants ; réutiliser exclusivement `KSP_SECRET_HELIUS_API_KEY` via Config et la surface `transactionSubscribe` déjà Transport-owned, sans SDK provider ni second client parallèle.
- [ ] **TODO réseau**auditer `mainnet` vs `mainnet-beta` avant tout renommage : Store persisté, Config, Transport, checkpoints/fingerprints et aliases externes doivent converger vers une identité KSP unique pour le même cluster ; aucune migration/canonicalisation n'est supposée avant cette preuve. - [X] **TODO réseau**identité KSP canonique fixée à `mainnet` dans Config/Store/Transport/tests ; `mainnet-beta` reste uniquement un alias legacy/externe lorsque la frontière provider l'exige. Les données N1 RAW antérieures restent non autoritaires pendant cette phase et aucune compatibilité de base de test n'impose l'ancien libellé.
- [X] `RawAccountState` + observation — contrat commun stabilisé en `0.3.1` avec bytes complets + slot, provenance séparée et enrichissements source-specific optionnels ; la persistence PostgreSQL physique est complétée en `0.3.4` avec les quatre capabilities account et la conformance RAW 10/10. - [X] `RawAccountState` + observation — contrat commun stabilisé en `0.3.1` avec bytes complets + slot, provenance séparée et enrichissements source-specific optionnels ; la persistence PostgreSQL physique est complétée en `0.3.4` avec les quatre capabilities account et la conformance RAW 10/10.
- [ ] **TODO** — statut/commitment transactionnel restant : `0.3.5` couvre uniquement le fait passif dexécution `slot + signature + outcome`; réauditer séparément `signatureSubscribe` et `getSignatureStatuses` lorsquun consumer de commitment/snapshot réel apparaît, sans fusionner snapshot, transition et execution update dans un modèle Option-soup. - [ ] **TODO** — statut/commitment transactionnel restant : `0.3.5` couvre uniquement le fait passif dexécution `slot + signature + outcome`; réauditer séparément `signatureSubscribe` et `getSignatureStatuses` lorsquun consumer de commitment/snapshot réel apparaît, sans fusionner snapshot, transition et execution update dans un modèle Option-soup.
- [ ] **IDEA** — logs realtime enrichis : `logsSubscribe` alimente déjà la projection minimale `TransactionExecutionEvent`, mais un éventuel `TransactionLogEvent` portant les lignes de log reste différé dans `docs/IDEAS.md` jusquà démonstration dun consumer et de bornes explicites. `logMessages` reste dans `RawTransaction` jusquà STRUCTURAL ; le wake-up post-commit reste distinct et Store API-owned conformément à `KSP-NOTIFY-*`. - [ ] **IDEA** — logs realtime enrichis : `logsSubscribe` alimente déjà la projection minimale `TransactionExecutionEvent`, mais un éventuel `TransactionLogEvent` portant les lignes de log reste différé dans `docs/IDEAS.md` jusquà démonstration dun consumer et de bornes explicites. `logMessages` reste dans `RawTransaction` jusquà STRUCTURAL ; le wake-up post-commit reste distinct et Store API-owned conformément à `KSP-NOTIFY-*`.

View File

@@ -29,7 +29,7 @@
}, },
{ {
"profile_id": "mainnet", "profile_id": "mainnet",
"network": "mainnet-beta", "network": "mainnet",
"backend": "postgres", "backend": "postgres",
"postgres": { "postgres": {
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}", "connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",

View File

@@ -31,8 +31,8 @@
"name": "mainnet_public", "name": "mainnet_public",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}", "url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"request_timeout_ms": 15000, "request_timeout_ms": 15000,
"max_idle_connections_per_host": 8, "max_idle_connections_per_host": 8,
@@ -57,7 +57,7 @@
"name": "mainnet_private", "name": "mainnet_private",
"enabled": true, "enabled": true,
"provider": "private-provider", "provider": "private-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "${KSP_SECRET_SOLANA_HTTP_URL:-https://example.invalid}", "url": "${KSP_SECRET_SOLANA_HTTP_URL:-https://example.invalid}",
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"request_timeout_ms": 10000, "request_timeout_ms": 10000,
@@ -85,15 +85,15 @@
"name": "mainnet_public_ws", "name": "mainnet_public_ws",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "solana_standard", "kind": "solana_standard",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}" "url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet.solana.com}"
}, },
{ {
"name": "mainnet_helius_ws", "name": "mainnet_helius_ws",
"enabled": true, "enabled": true,
"provider": "helius", "provider": "helius",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "helius_laserstream", "kind": "helius_laserstream",
"url": "wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-replace-me}", "url": "wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-replace-me}",
"session": { "session": {

View File

@@ -29,7 +29,7 @@
}, },
{ {
"profile_id": "mainnet", "profile_id": "mainnet",
"network": "mainnet-beta", "network": "mainnet",
"backend": "postgres", "backend": "postgres",
"postgres": { "postgres": {
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}", "connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",

View File

@@ -144,8 +144,8 @@
"name": "solana_mainnet_public", "name": "solana_mainnet_public",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}", "url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"request_timeout_ms": 15000, "request_timeout_ms": 15000,
"max_idle_connections_per_host": 8, "max_idle_connections_per_host": 8,
@@ -172,9 +172,9 @@
"name": "solana_mainnet_public_ws", "name": "solana_mainnet_public_ws",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "solana_standard", "kind": "solana_standard",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}" "url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet.solana.com}"
} }
] ]
}, },
@@ -185,8 +185,8 @@
"name": "solana_mainnet_public", "name": "solana_mainnet_public",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}", "url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"request_timeout_ms": 15000, "request_timeout_ms": 15000,
"max_idle_connections_per_host": 8, "max_idle_connections_per_host": 8,
@@ -225,7 +225,7 @@
"name": "publicnode_solana_mainnet_http", "name": "publicnode_solana_mainnet_http",
"enabled": true, "enabled": true,
"provider": "publicnode", "provider": "publicnode",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "https://solana-rpc.publicnode.com", "url": "https://solana-rpc.publicnode.com",
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"request_timeout_ms": 15000, "request_timeout_ms": 15000,
@@ -267,9 +267,9 @@
"name": "solana_mainnet_public_ws", "name": "solana_mainnet_public_ws",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "solana_standard", "kind": "solana_standard",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}" "url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet.solana.com}"
} }
] ]
}, },
@@ -280,8 +280,8 @@
"name": "solana_mainnet_public", "name": "solana_mainnet_public",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}", "url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"request_timeout_ms": 15000, "request_timeout_ms": 15000,
"max_idle_connections_per_host": 8, "max_idle_connections_per_host": 8,
@@ -308,9 +308,9 @@
"name": "solana_mainnet_public_ws", "name": "solana_mainnet_public_ws",
"enabled": true, "enabled": true,
"provider": "solana-public", "provider": "solana-public",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "solana_standard", "kind": "solana_standard",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}" "url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet.solana.com}"
} }
], ],
"grpc_endpoints": [ "grpc_endpoints": [
@@ -318,7 +318,7 @@
"name": "publicnode_solana_mainnet_yellowstone", "name": "publicnode_solana_mainnet_yellowstone",
"enabled": true, "enabled": true,
"provider": "publicnode", "provider": "publicnode",
"cluster": "mainnet-beta", "cluster": "mainnet",
"protocol": "solana_yellowstone", "protocol": "solana_yellowstone",
"url": "https://solana-yellowstone-grpc.publicnode.com:443", "url": "https://solana-yellowstone-grpc.publicnode.com:443",
"secret_metadata": [ "secret_metadata": [

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/unit_tests/backfill_request.rs // file: crates/ksp-app-backfill-desk/unit_tests/backfill_request.rs
// version: 3 // version: 4
fn ready_options() -> crate::BackfillDeskOptionsDto { fn ready_options() -> crate::BackfillDeskOptionsDto {
let limits = crate::backfill_request_limits(); let limits = crate::backfill_request_limits();
@@ -22,7 +22,7 @@ fn ready_options() -> crate::BackfillDeskOptionsDto {
return crate::BackfillDeskOptionsDto { return crate::BackfillDeskOptionsDto {
commitments: crate::backfill_commitment_codes(), commitments: crate::backfill_commitment_codes(),
composition_ready: true, composition_ready: true,
configured_networks: vec!["mainnet-beta".to_owned()], configured_networks: vec!["mainnet".to_owned()],
http_routes: vec![ http_routes: vec![
crate::BackfillHttpRouteOptionDto { crate::BackfillHttpRouteOptionDto {
pooled: true, pooled: true,
@@ -36,7 +36,7 @@ fn ready_options() -> crate::BackfillDeskOptionsDto {
program_id_options: crate::program_id_autocomplete_options(), program_id_options: crate::program_id_autocomplete_options(),
scope_kinds: crate::backfill_scope_kind_codes(), scope_kinds: crate::backfill_scope_kind_codes(),
store_diagnostic: std::option::Option::None, store_diagnostic: std::option::Option::None,
store_network: std::option::Option::Some("mainnet-beta".to_owned()), store_network: std::option::Option::Some("mainnet".to_owned()),
store_ready: true, store_ready: true,
transport_diagnostic: std::option::Option::None, transport_diagnostic: std::option::Option::None,
transport_ready: true, transport_ready: true,
@@ -81,7 +81,7 @@ fn latest_address_maps_to_backend_network_role_commitment_and_bounds() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
assert_eq!(mapped.network().as_str(), "mainnet-beta"); assert_eq!(mapped.network().as_str(), "mainnet");
assert_eq!(mapped.role().as_str(), "backfill_pool"); assert_eq!(mapped.role().as_str(), "backfill_pool");
assert_eq!(mapped.commitment(), ksp_job_backfill_lib::BackfillCommitment::Finalized); assert_eq!(mapped.commitment(), ksp_job_backfill_lib::BackfillCommitment::Finalized);
assert_eq!(mapped.scope().kind(), ksp_job_backfill_lib::BackfillScopeKind::LatestAddress); assert_eq!(mapped.scope().kind(), ksp_job_backfill_lib::BackfillScopeKind::LatestAddress);
@@ -193,7 +193,7 @@ fn request_preview_never_contains_address_or_signature_values() {
if let std::result::Result::Ok(serialized) = serialized { if let std::result::Result::Ok(serialized) = serialized {
assert!(!serialized.contains("11111111111111111111111111111111")); assert!(!serialized.contains("11111111111111111111111111111111"));
assert!(!serialized.contains(&"1".repeat(64))); assert!(!serialized.contains(&"1".repeat(64)));
assert!(serialized.contains("mainnet-beta")); assert!(serialized.contains("mainnet"));
assert!(serialized.contains("backfill_pool")); assert!(serialized.contains("backfill_pool"));
} }
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/unit_tests/backfill_run.rs // file: crates/ksp-app-backfill-desk/unit_tests/backfill_run.rs
// version: 3 // version: 4
#[test] #[test]
fn generated_run_ids_are_backend_owned_bounded_and_unique_in_session() { fn generated_run_ids_are_backend_owned_bounded_and_unique_in_session() {
@@ -30,7 +30,7 @@ fn cancellable_runtime(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None, std::result::Result::Err(_) => return std::option::Option::None,
}; };
let network = match ksp_store_lib::RawNetworkId::new("mainnet-beta") { let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None, std::result::Result::Err(_) => return std::option::Option::None,
}; };

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/unit_tests/backfill_status.rs // file: crates/ksp-app-backfill-desk/unit_tests/backfill_status.rs
// version: 1 // version: 2
fn initial_notification() -> std::option::Option<ksp_job_api::JobNotification<ksp_job_backfill_lib::BackfillJobSnapshot>> { fn initial_notification() -> std::option::Option<ksp_job_api::JobNotification<ksp_job_backfill_lib::BackfillJobSnapshot>> {
let signature = match ksp_job_backfill_lib::BackfillSignature::new("1".repeat(ksp_job_backfill_lib::MIN_BACKFILL_SIGNATURE_TEXT_BYTES)) { let signature = match ksp_job_backfill_lib::BackfillSignature::new("1".repeat(ksp_job_backfill_lib::MIN_BACKFILL_SIGNATURE_TEXT_BYTES)) {
@@ -14,7 +14,7 @@ fn initial_notification() -> std::option::Option<ksp_job_api::JobNotification<ks
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None, std::result::Result::Err(_) => return std::option::Option::None,
}; };
let network = match ksp_store_lib::RawNetworkId::new("mainnet-beta") { let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None, std::result::Result::Err(_) => return std::option::Option::None,
}; };

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs // file: crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs
// version: 1 // version: 2
#[test] #[test]
fn store_health_mapping_is_explicit_and_conservative() { fn store_health_mapping_is_explicit_and_conservative() {
@@ -10,7 +10,7 @@ fn store_health_mapping_is_explicit_and_conservative() {
#[test] #[test]
fn store_transport_network_coherence_requires_exact_logical_identity() { fn store_transport_network_coherence_requires_exact_logical_identity() {
assert!(super::validate_network_coherence("devnet", "devnet").is_ok()); assert!(super::validate_network_coherence("devnet", "devnet").is_ok());
let mismatch = super::validate_network_coherence("mainnet-beta", "devnet"); let mismatch = super::validate_network_coherence("mainnet", "devnet");
assert!(mismatch.is_err()); assert!(mismatch.is_err());
if let std::result::Result::Err(error) = mismatch { if let std::result::Result::Err(error) = mismatch {
assert_eq!(error.code(), crate::ERROR_CODE_STORE_NETWORK_MISMATCH); assert_eq!(error.code(), crate::ERROR_CODE_STORE_NETWORK_MISMATCH);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/unit_tests/dto_common.rs // file: crates/ksp-app-store-desk/unit_tests/dto_common.rs
// version: 2 // version: 3
#[test] #[test]
fn command_error_projection_keeps_only_stable_public_fields() { fn command_error_projection_keeps_only_stable_public_fields() {
@@ -19,7 +19,7 @@ fn store_runtime_status_dto_contains_only_safe_portable_fields() {
diagnostic: std::option::Option::None, diagnostic: std::option::Option::None,
health_state: "ready".to_owned(), health_state: "ready".to_owned(),
migration_version_decimal: std::option::Option::Some("2".to_owned()), migration_version_decimal: std::option::Option::Some("2".to_owned()),
network: std::option::Option::Some("mainnet-beta".to_owned()), network: std::option::Option::Some("mainnet".to_owned()),
pending_migration_count: 0, pending_migration_count: 0,
pool_available: 1, pool_available: 1,
pool_capacity: 8, pool_capacity: 8,

View File

@@ -8,7 +8,6 @@
], ],
"permissions": [ "permissions": [
"core:default", "core:default",
"tracing:default", "tracing:default"
"dialog:default"
] ]
} }

View File

@@ -11,7 +11,6 @@
"@fltsci/tauri-plugin-tracing": "^0.3", "@fltsci/tauri-plugin-tracing": "^0.3",
"@fortawesome/fontawesome-free": "^7.3", "@fortawesome/fontawesome-free": "^7.3",
"@tauri-apps/api": "^2.11", "@tauri-apps/api": "^2.11",
"@tauri-apps/plugin-dialog": "^2.7",
"bootstrap": "^5.3", "bootstrap": "^5.3",
"datatables.net-bs5": "^3.0", "datatables.net-bs5": "^3.0",
"datatables.net-select-bs5": "^4.0", "datatables.net-select-bs5": "^4.0",

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/USAGE.md --> <!-- file: crates/ksp-config-lib/USAGE.md -->
<!-- version: 14 --> <!-- version: 15 -->
# Utilisation de ksp-config-lib # Utilisation de ksp-config-lib
@@ -250,7 +250,7 @@ Targets committed :
```text ```text
devnet -> network devnet -> KSP_SECRET_STORE_DEVNET_POSTGRES_URI devnet -> network devnet -> KSP_SECRET_STORE_DEVNET_POSTGRES_URI
mainnet -> network mainnet-beta -> KSP_SECRET_STORE_MAINNET_POSTGRES_URI mainnet -> network mainnet -> KSP_SECRET_STORE_MAINNET_POSTGRES_URI
testnet -> network testnet -> KSP_SECRET_STORE_TESTNET_POSTGRES_URI testnet -> network testnet -> KSP_SECRET_STORE_TESTNET_POSTGRES_URI
``` ```

View File

@@ -29,7 +29,7 @@
}, },
{ {
"profile_id": "mainnet", "profile_id": "mainnet",
"network": "mainnet-beta", "network": "mainnet",
"backend": "postgres", "backend": "postgres",
"postgres": { "postgres": {
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}", "connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",

View File

@@ -76,7 +76,7 @@
"name": "fixture_helius_ws", "name": "fixture_helius_ws",
"enabled": true, "enabled": true,
"provider": "helius", "provider": "helius",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "helius_laserstream", "kind": "helius_laserstream",
"url": "wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-fixture-helius-key}" "url": "wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-fixture-helius-key}"
} }

View File

@@ -45,7 +45,7 @@
"name": "fixture_http", "name": "fixture_http",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "https://fallback.invalid", "url": "https://fallback.invalid",
"connect_timeout_ms": 1000, "connect_timeout_ms": 1000,
"request_timeout_ms": 3000, "request_timeout_ms": 3000,
@@ -73,7 +73,7 @@
"name": "fixture_ws", "name": "fixture_ws",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "solana_standard", "kind": "solana_standard",
"url": "wss://fallback.invalid" "url": "wss://fallback.invalid"
} }
@@ -83,7 +83,7 @@
"name": "fixture_grpc", "name": "fixture_grpc",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"protocol": "solana_yellowstone", "protocol": "solana_yellowstone",
"url": "https://grpc.invalid", "url": "https://grpc.invalid",
"metadata": [ "metadata": [
@@ -115,7 +115,7 @@
"name": "fixture_http", "name": "fixture_http",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "https://fallback.invalid", "url": "https://fallback.invalid",
"connect_timeout_ms": 1000, "connect_timeout_ms": 1000,
"request_timeout_ms": 3000, "request_timeout_ms": 3000,
@@ -143,7 +143,7 @@
"name": "fixture_ws", "name": "fixture_ws",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "solana_standard", "kind": "solana_standard",
"url": "wss://fallback.invalid" "url": "wss://fallback.invalid"
} }
@@ -153,7 +153,7 @@
"name": "fixture_grpc", "name": "fixture_grpc",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"protocol": "solana_yellowstone", "protocol": "solana_yellowstone",
"url": "https://grpc.invalid", "url": "https://grpc.invalid",
"metadata": [ "metadata": [
@@ -180,7 +180,7 @@
"name": "fixture_http", "name": "fixture_http",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"url": "https://fallback.invalid", "url": "https://fallback.invalid",
"connect_timeout_ms": 1000, "connect_timeout_ms": 1000,
"request_timeout_ms": 3000, "request_timeout_ms": 3000,
@@ -208,7 +208,7 @@
"name": "fixture_ws", "name": "fixture_ws",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"kind": "solana_standard", "kind": "solana_standard",
"url": "wss://fallback.invalid" "url": "wss://fallback.invalid"
} }
@@ -218,7 +218,7 @@
"name": "fixture_grpc", "name": "fixture_grpc",
"enabled": true, "enabled": true,
"provider": "fixture-provider", "provider": "fixture-provider",
"cluster": "mainnet-beta", "cluster": "mainnet",
"protocol": "solana_yellowstone", "protocol": "solana_yellowstone",
"url": "https://grpc.invalid", "url": "https://grpc.invalid",
"metadata": [], "metadata": [],

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/unit_tests/store.rs // file: crates/ksp-config-lib/unit_tests/store.rs
// version: 4 // version: 5
#[test] #[test]
fn committed_store_profile_maps_exact_runtime_settings_and_secret_fallback() { fn committed_store_profile_maps_exact_runtime_settings_and_secret_fallback() {
@@ -122,7 +122,7 @@ fn named_store_targets_select_one_network_and_database_without_runtime_multiplex
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new()); let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
for (target_id, network, expected_uri) in [ for (target_id, network, expected_uri) in [
("devnet", "devnet", "postgresql://devnet.invalid/ksp_devnet"), ("devnet", "devnet", "postgresql://devnet.invalid/ksp_devnet"),
("mainnet", "mainnet-beta", "postgresql://mainnet.invalid/ksp_mainnet"), ("mainnet", "mainnet", "postgresql://mainnet.invalid/ksp_mainnet"),
("testnet", "testnet", "postgresql://testnet.invalid/ksp_testnet"), ("testnet", "testnet", "postgresql://testnet.invalid/ksp_testnet"),
] { ] {
let resolved = engine.load_resolved_store_config(std::option::Option::Some(target_id), &environment); let resolved = engine.load_resolved_store_config(std::option::Option::Some(target_id), &environment);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/unit_tests/transport.rs // file: crates/ksp-config-lib/unit_tests/transport.rs
// version: 12 // version: 13
#[test] #[test]
fn fixture_transport_profile_maps_complete_runtime_contract() { fn fixture_transport_profile_maps_complete_runtime_contract() {
@@ -68,7 +68,7 @@ fn fixture_transport_profile_maps_complete_runtime_contract() {
let helius = &ws.endpoints()[1]; let helius = &ws.endpoints()[1];
assert_eq!(helius.name(), "fixture_helius_ws"); assert_eq!(helius.name(), "fixture_helius_ws");
assert_eq!(helius.provider().as_str(), "helius"); assert_eq!(helius.provider().as_str(), "helius");
assert_eq!(helius.cluster().as_str(), "mainnet-beta"); assert_eq!(helius.cluster().as_str(), "mainnet");
assert_eq!(helius.protocol(), ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream); assert_eq!(helius.protocol(), ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream);
assert_eq!(helius.url().as_str(), "wss://mainnet.helius-rpc.com/?api-key=fixture-helius-key"); assert_eq!(helius.url().as_str(), "wss://mainnet.helius-rpc.com/?api-key=fixture-helius-key");
let _connect_future = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::connect(helius.clone()); let _connect_future = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::connect(helius.clone());
@@ -122,13 +122,13 @@ fn committed_transport_document_maps_default_and_explicit_profiles() {
if let std::result::Result::Ok(mainnet) = mainnet { if let std::result::Result::Ok(mainnet) = mainnet {
assert_eq!(mainnet.profile_id(), "mainnet_public"); assert_eq!(mainnet.profile_id(), "mainnet_public");
assert_eq!(mainnet.selection_source(), crate::ConfigProfileSelectionSource::Explicit); assert_eq!(mainnet.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
assert_eq!(mainnet.settings().endpoints()[0].cluster().as_str(), "mainnet-beta"); assert_eq!(mainnet.settings().endpoints()[0].cluster().as_str(), "mainnet");
assert_eq!(mainnet.settings().endpoints()[0].url().as_str(), "https://api.mainnet-beta.solana.com"); assert_eq!(mainnet.settings().endpoints()[0].url().as_str(), "https://api.mainnet.solana.com");
assert!(mainnet.grpc_settings().is_none(), "generic Mainnet profile must stay distinct from the PublicNode gRPC profile"); assert!(mainnet.grpc_settings().is_none(), "generic Mainnet profile must stay distinct from the PublicNode gRPC profile");
let ws = mainnet.ws_settings(); let ws = mainnet.ws_settings();
assert!(ws.is_some(), "committed V3 Mainnet profile should expose WebSocket settings"); assert!(ws.is_some(), "committed V3 Mainnet profile should expose WebSocket settings");
if let std::option::Option::Some(ws) = ws { if let std::option::Option::Some(ws) = ws {
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.mainnet-beta.solana.com"); assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.mainnet.solana.com");
} }
} }
} }
@@ -190,7 +190,7 @@ fn committed_v3_publicnode_profiles_map_provider_neutral_yellowstone_grpc_with_n
( (
"publicnode_mainnet", "publicnode_mainnet",
"publicnode_solana_mainnet_yellowstone", "publicnode_solana_mainnet_yellowstone",
"mainnet-beta", "mainnet",
"https://solana-yellowstone-grpc.publicnode.com:443", "https://solana-yellowstone-grpc.publicnode.com:443",
"KSP_SECRET_PUBLICNODE_MAINNET_GRPC_X_TOKEN", "KSP_SECRET_PUBLICNODE_MAINNET_GRPC_X_TOKEN",
"PUBLICNODE-MAINNET-GRPC-X-TOKEN-CANARY", "PUBLICNODE-MAINNET-GRPC-X-TOKEN-CANARY",
@@ -480,7 +480,7 @@ fn helius_laserstream_mainnet_and_devnet_api_key_map_to_protocol_and_safe_redact
assert_eq!(ws.endpoints().len(), 2); assert_eq!(ws.endpoints().len(), 2);
let endpoint = &ws.endpoints()[1]; let endpoint = &ws.endpoints()[1];
assert_eq!(endpoint.provider().as_str(), "helius"); assert_eq!(endpoint.provider().as_str(), "helius");
assert_eq!(endpoint.cluster().as_str(), "mainnet-beta"); assert_eq!(endpoint.cluster().as_str(), "mainnet");
assert_eq!(endpoint.protocol(), ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream); assert_eq!(endpoint.protocol(), ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream);
assert_eq!(endpoint.url().as_str(), "wss://mainnet.helius-rpc.com/?api-key=helius-api-key-canary"); assert_eq!(endpoint.url().as_str(), "wss://mainnet.helius-rpc.com/?api-key=helius-api-key-canary");
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/unit_tests/request.rs // file: crates/ksp-job-backfill-lib/unit_tests/request.rs
// version: 2 // version: 3
fn signature(character: char) -> std::option::Option<crate::BackfillSignature> { fn signature(character: char) -> std::option::Option<crate::BackfillSignature> {
return match crate::BackfillSignature::new(character.to_string().repeat(crate::MIN_BACKFILL_SIGNATURE_TEXT_BYTES)) { return match crate::BackfillSignature::new(character.to_string().repeat(crate::MIN_BACKFILL_SIGNATURE_TEXT_BYTES)) {
@@ -235,7 +235,7 @@ fn pre_005_scope_kind_and_anchor_are_distinct_semantics() {
#[test] #[test]
fn pre_011_resume_reissues_checkpoint_for_new_job_without_changing_scope_semantics() { fn pre_011_resume_reissues_checkpoint_for_new_job_without_changing_scope_semantics() {
let network = match network("mainnet-beta") { let network = match network("mainnet") {
std::option::Option::Some(value) => value, std::option::Option::Some(value) => value,
std::option::Option::None => return, std::option::Option::None => return,
}; };

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md --> <!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
<!-- version: 23 --> <!-- version: 24 -->
# Utilisation de `ksp-onchain-transport-lib` # Utilisation de `ksp-onchain-transport-lib`
@@ -281,7 +281,7 @@ let grpc_endpoint = match ksp_onchain_transport_lib::YellowstoneGrpcEndpointSett
"publicnode_mainnet_yellowstone", "publicnode_mainnet_yellowstone",
true, true,
ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new("publicnode"), ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new("publicnode"),
ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new("mainnet-beta"), ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new("mainnet"),
grpc_url, grpc_url,
ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings::default(), ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings::default(),
) )

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs // file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 41 // version: 42
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage. //! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
@@ -1000,14 +1000,10 @@ fn release_v0_2_8_pre_010_live_smoke_policy_preserves_secret_and_dependency_owne
#[test] #[test]
fn release_v0_2_9_pre_002_materializes_minimal_yellowstone_engine_without_provider_or_ws_coupling() { fn release_v0_2_9_pre_002_materializes_minimal_yellowstone_engine_without_provider_or_ws_coupling() {
let manifest_directory = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let manifest_directory = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace = manifest_directory.parent().and_then(std::path::Path::parent).expect("Transport test must resolve workspace root");
let root_manifest = std::fs::read_to_string(workspace.join("Cargo.toml")).expect("workspace manifest must be readable");
let transport_manifest = std::fs::read_to_string(manifest_directory.join("Cargo.toml")).expect("Transport manifest must be readable"); let transport_manifest = std::fs::read_to_string(manifest_directory.join("Cargo.toml")).expect("Transport manifest must be readable");
let settings_source = include_str!("../src/grpc_settings.rs"); let settings_source = include_str!("../src/grpc_settings.rs");
let channel_source = include_str!("../src/grpc_channel.rs"); let channel_source = include_str!("../src/grpc_channel.rs");
let crate_root = include_str!("../src/lib.rs"); let crate_root = include_str!("../src/lib.rs");
assert!(root_manifest.contains("tonic = { version = \"^0.14\", default-features = false }"));
assert!(root_manifest.contains("yellowstone-grpc-proto = { version = \"^12.6\", default-features = false }"));
assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"channel\"")); assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"channel\""));
assert!(transport_manifest.contains("yellowstone-grpc-proto.workspace = true")); assert!(transport_manifest.contains("yellowstone-grpc-proto.workspace = true"));
assert!(!transport_manifest.contains("yellowstone-grpc-client")); assert!(!transport_manifest.contains("yellowstone-grpc-client"));
@@ -1028,15 +1024,11 @@ fn release_v0_2_9_pre_002_materializes_minimal_yellowstone_engine_without_provid
#[test] #[test]
fn release_v0_2_9_pre_003_adds_tls_metadata_and_exactly_seven_standard_unary_methods_without_subscribe() { fn release_v0_2_9_pre_003_adds_tls_metadata_and_exactly_seven_standard_unary_methods_without_subscribe() {
let manifest_directory = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let manifest_directory = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace = manifest_directory.parent().and_then(std::path::Path::parent).expect("Transport test must resolve workspace root");
let root_manifest = std::fs::read_to_string(workspace.join("Cargo.toml")).expect("workspace manifest must be readable");
let transport_manifest = std::fs::read_to_string(manifest_directory.join("Cargo.toml")).expect("Transport manifest must be readable"); let transport_manifest = std::fs::read_to_string(manifest_directory.join("Cargo.toml")).expect("Transport manifest must be readable");
let settings_source = include_str!("../src/grpc_settings.rs"); let settings_source = include_str!("../src/grpc_settings.rs");
let channel_source = include_str!("../src/grpc_channel.rs"); let channel_source = include_str!("../src/grpc_channel.rs");
let unary_source = include_str!("../src/grpc_unary.rs"); let unary_source = include_str!("../src/grpc_unary.rs");
let crate_root = include_str!("../src/lib.rs"); let crate_root = include_str!("../src/lib.rs");
assert!(root_manifest.contains("http = { version = \"^1.5\", default-features = false }"));
assert!(root_manifest.contains("tonic-prost = { version = \"^0.14\", default-features = false }"));
assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"channel\", \"tls-aws-lc\", \"tls-webpki-roots\"] }")); assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"channel\", \"tls-aws-lc\", \"tls-webpki-roots\"] }"));
assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"codegen\", \"server\"] }")); assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"codegen\", \"server\"] }"));
assert!(transport_manifest.contains("tonic-prost.workspace = true")); assert!(transport_manifest.contains("tonic-prost.workspace = true"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/yellowstone_publicnode_smoke.rs // file: crates/ksp-onchain-transport-lib/tests/yellowstone_publicnode_smoke.rs
// version: 5 // version: 6
//! Opt-in live PublicNode Mainnet/Testnet smokes for authenticated provider-neutral Yellowstone gRPC Subscribe. //! Opt-in live PublicNode Mainnet/Testnet smokes for authenticated provider-neutral Yellowstone gRPC Subscribe.
@@ -146,13 +146,8 @@ async fn assert_publicnode_slot_stream(name: &str, cluster: &str, url: &str, x_t
#[ignore = "opt-in live PublicNode Mainnet Yellowstone gRPC smoke; reads the first personal x-token line from stdin and performs an external TLS/Subscribe request"] #[ignore = "opt-in live PublicNode Mainnet Yellowstone gRPC smoke; reads the first personal x-token line from stdin and performs an external TLS/Subscribe request"]
async fn publicnode_mainnet_yellowstone_streams_slots_with_network_scoped_secret_x_token() { async fn publicnode_mainnet_yellowstone_streams_slots_with_network_scoped_secret_x_token() {
let tokens = publicnode_x_tokens(); let tokens = publicnode_x_tokens();
assert_publicnode_slot_stream( assert_publicnode_slot_stream("publicnode_mainnet_yellowstone", "mainnet", "https://solana-yellowstone-grpc.publicnode.com:443", tokens.mainnet.as_str())
"publicnode_mainnet_yellowstone", .await;
"mainnet-beta",
"https://solana-yellowstone-grpc.publicnode.com:443",
tokens.mainnet.as_str(),
)
.await;
} }
#[tokio::test(flavor = "current_thread")] #[tokio::test(flavor = "current_thread")]

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs // file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
// version: 3 // version: 4
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum FixtureMode { enum FixtureMode {
@@ -171,6 +171,15 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
return std::result::Result::Err(tonic::Status::unimplemented("deshred is outside KSP 0.2.9")); return std::result::Result::Err(tonic::Status::unimplemented("deshred is outside KSP 0.2.9"));
} }
type SubscribeGossipStream = futures_util::stream::Empty<std::result::Result<yellowstone_grpc_proto::geyser::SubscribeUpdateGossip, tonic::Status>>;
async fn subscribe_gossip(
&self,
_request: tonic::Request<yellowstone_grpc_proto::geyser::SubscribeGossipRequest>,
) -> std::result::Result<tonic::Response<Self::SubscribeGossipStream>, tonic::Status> {
return std::result::Result::Err(tonic::Status::unimplemented("gossip is outside KSP 0.2.9"));
}
async fn subscribe_replay_info( async fn subscribe_replay_info(
&self, &self,
request: tonic::Request<yellowstone_grpc_proto::geyser::SubscribeReplayInfoRequest>, request: tonic::Request<yellowstone_grpc_proto::geyser::SubscribeReplayInfoRequest>,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_unary.rs // file: crates/ksp-onchain-transport-lib/unit_tests/grpc_unary.rs
// version: 3 // version: 4
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct FixtureGeyser; struct FixtureGeyser;
@@ -31,6 +31,19 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
return std::result::Result::Err(tonic::Status::unimplemented("deshred is outside KSP 0.2.9")); return std::result::Result::Err(tonic::Status::unimplemented("deshred is outside KSP 0.2.9"));
} }
type SubscribeGossipStream = std::pin::Pin<
std::boxed::Box<
dyn futures_util::Stream<Item = std::result::Result<yellowstone_grpc_proto::geyser::SubscribeUpdateGossip, tonic::Status>> + Send + 'static,
>,
>;
async fn subscribe_gossip(
&self,
_request: tonic::Request<yellowstone_grpc_proto::geyser::SubscribeGossipRequest>,
) -> std::result::Result<tonic::Response<Self::SubscribeGossipStream>, tonic::Status> {
return std::result::Result::Err(tonic::Status::unimplemented("gossip is outside KSP 0.2.9"));
}
async fn subscribe_replay_info( async fn subscribe_replay_info(
&self, &self,
request: tonic::Request<yellowstone_grpc_proto::geyser::SubscribeReplayInfoRequest>, request: tonic::Request<yellowstone_grpc_proto::geyser::SubscribeReplayInfoRequest>,
@@ -128,7 +141,7 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
return std::result::Result::Err(error); return std::result::Result::Err(error);
} }
return std::result::Result::Ok(tonic::Response::new(yellowstone_grpc_proto::geyser::GetVersionResponse { return std::result::Result::Ok(tonic::Response::new(yellowstone_grpc_proto::geyser::GetVersionResponse {
version: "fixture-yellowstone-12.6".to_owned(), version: "fixture-yellowstone-12.7".to_owned(),
})); }));
} }
} }
@@ -238,7 +251,7 @@ async fn yellowstone_unary_fixture_covers_all_seven_standard_methods_and_metadat
assert_eq!(validity.slot(), 404); assert_eq!(validity.slot(), 404);
assert!(validity.valid()); assert!(validity.valid());
let version = client.get_version().await.expect("version fixture must succeed"); let version = client.get_version().await.expect("version fixture must succeed");
assert_eq!(version.version(), "fixture-yellowstone-12.6"); assert_eq!(version.version(), "fixture-yellowstone-12.7");
let rendered = format!("{client:?} {channel:?}"); let rendered = format!("{client:?} {channel:?}");
assert!(!rendered.contains("GRPC-SECRET-CANARY")); assert!(!rendered.contains("GRPC-SECRET-CANARY"));
assert!(!rendered.contains(server.endpoint_url.as_str())); assert!(!rendered.contains(server.endpoint_url.as_str()));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-api/tests/public_api.rs // file: crates/ksp-store-api/tests/public_api.rs
// version: 8 // version: 9
//! Integration canaries for the public `ksp-store-api` surface. //! Integration canaries for the public `ksp-store-api` surface.
@@ -30,7 +30,7 @@ fn public_pre_002_core_facade_remains_available_from_crate_root() {
#[test] #[test]
fn public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_root() { fn public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_root() {
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) { let network = match ksp_store_api::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
@@ -65,7 +65,7 @@ fn public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_r
let observation = ksp_store_api::RawTransactionObservation::new(ksp_store_api::RawObservationKey::new([3_u8; 32]), reference, provenance); let observation = ksp_store_api::RawTransactionObservation::new(ksp_store_api::RawObservationKey::new([3_u8; 32]), reference, provenance);
assert_eq!(transaction.slot(), 123); assert_eq!(transaction.slot(), 123);
assert_eq!(transaction.payload().bytes(), &[1_u8, 2_u8, 3_u8]); assert_eq!(transaction.payload().bytes(), &[1_u8, 2_u8, 3_u8]);
assert_eq!(observation.transaction().network().as_str(), "mainnet-beta"); assert_eq!(observation.transaction().network().as_str(), "mainnet");
assert_eq!(observation.provenance().acquisition_method().as_str(), "getTransaction"); assert_eq!(observation.provenance().acquisition_method().as_str(), "getTransaction");
assert_eq!(ksp_store_api::ERROR_CODE_RAW_PAYLOAD_INVALID.domain(), "store_api"); assert_eq!(ksp_store_api::ERROR_CODE_RAW_PAYLOAD_INVALID.domain(), "store_api");
return; return;
@@ -83,7 +83,7 @@ fn public_pre_003_surface_keeps_backend_and_structural_types_out() {
#[test] #[test]
fn public_pre_004_raw_account_state_and_observation_are_constructible_from_crate_root() { fn public_pre_004_raw_account_state_and_observation_are_constructible_from_crate_root() {
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) { let network = match ksp_store_api::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
@@ -159,7 +159,7 @@ fn public_pre_006_query_outcome_and_retention_contracts_are_available_from_crate
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) { let network = match ksp_store_api::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-api/tests/security_hardening.rs // file: crates/ksp-store-api/tests/security_hardening.rs
// version: 2 // version: 3
//! Adversarial and retention-race canaries for the Store API RAW foundation. //! Adversarial and retention-race canaries for the Store API RAW foundation.
@@ -62,7 +62,7 @@ fn pre_007_raw_debug_surfaces_do_not_render_payload_hash_signature_or_account_by
}; };
assert!(!std::format!("{payload:?}").contains(HOSTILE_MARKER)); assert!(!std::format!("{payload:?}").contains(HOSTILE_MARKER));
assert_eq!(std::format!("{:?}", payload.content_hash()), "RawContentHash(..)"); assert_eq!(std::format!("{:?}", payload.content_hash()), "RawContentHash(..)");
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) { let network = match ksp_store_api::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
@@ -125,7 +125,7 @@ fn pre_007_retention_outcome_distinguishes_lost_compare_and_transition_race() {
#[test] #[test]
fn v0_3_8_pre_003_inspection_summaries_and_counts_reject_payload_shaped_or_inconsistent_state() { fn v0_3_8_pre_003_inspection_summaries_and_counts_reject_payload_shaped_or_inconsistent_state() {
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) { let network = match ksp_store_api::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };

View File

@@ -1,8 +1,8 @@
// file: crates/ksp-store-api/unit_tests/model/raw_account.rs // file: crates/ksp-store-api/unit_tests/model/raw_account.rs
// version: 1 // version: 2
fn network() -> std::option::Option<crate::RawNetworkId> { fn network() -> std::option::Option<crate::RawNetworkId> {
return match crate::RawNetworkId::new("mainnet-beta".to_owned()) { return match crate::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None, std::result::Result::Err(_) => std::option::Option::None,
}; };

View File

@@ -1,10 +1,10 @@
// file: crates/ksp-store-api/unit_tests/model/raw_inspection.rs // file: crates/ksp-store-api/unit_tests/model/raw_inspection.rs
// version: 2 // version: 3
//! Unit tests for backend-neutral RAW inspection contracts. //! Unit tests for backend-neutral RAW inspection contracts.
fn network() -> std::option::Option<crate::RawNetworkId> { fn network() -> std::option::Option<crate::RawNetworkId> {
return match crate::RawNetworkId::new("mainnet-beta".to_owned()) { return match crate::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None, std::result::Result::Err(_) => std::option::Option::None,
}; };
@@ -61,7 +61,7 @@ fn inspection_queries_keep_backend_neutral_filters_and_random_access_page() {
}; };
let transaction = let transaction =
crate::RawTransactionInspectionQuery::new(network.clone(), slots, crate::RawSortDirection::Descending, crate::RawInspectionPageRequest::new(50, limit)); crate::RawTransactionInspectionQuery::new(network.clone(), slots, crate::RawSortDirection::Descending, crate::RawInspectionPageRequest::new(50, limit));
assert_eq!(transaction.network().as_str(), "mainnet-beta"); assert_eq!(transaction.network().as_str(), "mainnet");
assert_eq!(transaction.page().offset(), 50); assert_eq!(transaction.page().offset(), 50);
assert_eq!(transaction.slots().end_inclusive(), std::option::Option::Some(20)); assert_eq!(transaction.slots().end_inclusive(), std::option::Option::Some(20));
assert_eq!(transaction.direction(), crate::RawSortDirection::Descending); assert_eq!(transaction.direction(), crate::RawSortDirection::Descending);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-api/unit_tests/model/raw_pagination.rs // file: crates/ksp-store-api/unit_tests/model/raw_pagination.rs
// version: 1 // version: 2
#[test] #[test]
fn page_limit_rejects_only_zero_and_keeps_no_ksp_policy_maximum() { fn page_limit_rejects_only_zero_and_keeps_no_ksp_policy_maximum() {
@@ -32,7 +32,7 @@ fn cursor_is_opaque_bounded_and_debug_does_not_render_bytes() {
#[test] #[test]
fn transaction_query_preserves_caller_limit_range_direction_and_network() { fn transaction_query_preserves_caller_limit_range_direction_and_network() {
let network = match crate::RawNetworkId::new("mainnet-beta".to_owned()) { let network = match crate::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
@@ -45,7 +45,7 @@ fn transaction_query_preserves_caller_limit_range_direction_and_network() {
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
let query = crate::RawTransactionQuery::new(network, slots, crate::RawSortDirection::Ascending, crate::RawPageRequest::first(limit)); let query = crate::RawTransactionQuery::new(network, slots, crate::RawSortDirection::Ascending, crate::RawPageRequest::first(limit));
assert_eq!(query.network().as_str(), "mainnet-beta"); assert_eq!(query.network().as_str(), "mainnet");
assert_eq!(query.slots().start_inclusive(), std::option::Option::Some(10)); assert_eq!(query.slots().start_inclusive(), std::option::Option::Some(10));
assert_eq!(query.page().limit().get(), 5_000_000); assert_eq!(query.page().limit().get(), 5_000_000);
assert_eq!(query.direction(), crate::RawSortDirection::Ascending); assert_eq!(query.direction(), crate::RawSortDirection::Ascending);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-api/unit_tests/model/raw_primitives.rs // file: crates/ksp-store-api/unit_tests/model/raw_primitives.rs
// version: 1 // version: 2
fn code(value: &str) -> std::option::Option<crate::RawProvenanceCode> { fn code(value: &str) -> std::option::Option<crate::RawProvenanceCode> {
return match crate::RawProvenanceCode::new(value.to_owned()) { return match crate::RawProvenanceCode::new(value.to_owned()) {
@@ -17,7 +17,7 @@ fn timestamp(unix_millis: u64) -> std::option::Option<crate::RawTimestamp> {
#[test] #[test]
fn raw_codes_are_bounded_nonempty_and_reject_url_like_or_control_values() { fn raw_codes_are_bounded_nonempty_and_reject_url_like_or_control_values() {
let network = crate::RawNetworkId::new("mainnet-beta".to_owned()); let network = crate::RawNetworkId::new("mainnet".to_owned());
assert!(network.is_ok()); assert!(network.is_ok());
let format = crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()); let format = crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned());
assert!(format.is_ok()); assert!(format.is_ok());

View File

@@ -1,8 +1,8 @@
// file: crates/ksp-store-api/unit_tests/model/raw_retention.rs // file: crates/ksp-store-api/unit_tests/model/raw_retention.rs
// version: 2 // version: 3
fn reference() -> std::option::Option<crate::RawTransactionReference> { fn reference() -> std::option::Option<crate::RawTransactionReference> {
let network = match crate::RawNetworkId::new("mainnet-beta".to_owned()) { let network = match crate::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None, std::result::Result::Err(_) => return std::option::Option::None,
}; };

View File

@@ -1,8 +1,8 @@
// file: crates/ksp-store-api/unit_tests/model/raw_transaction.rs // file: crates/ksp-store-api/unit_tests/model/raw_transaction.rs
// version: 1 // version: 2
fn network() -> std::option::Option<crate::RawNetworkId> { fn network() -> std::option::Option<crate::RawNetworkId> {
return match crate::RawNetworkId::new("mainnet-beta".to_owned()) { return match crate::RawNetworkId::new("mainnet".to_owned()) {
std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None, std::result::Result::Err(_) => std::option::Option::None,
}; };
@@ -57,7 +57,7 @@ fn raw_transaction_identity_is_network_plus_signature_and_payload_remains_whole(
let signature = crate::RawTransactionSignature::new([9_u8; 64]); let signature = crate::RawTransactionSignature::new([9_u8; 64]);
let reference = crate::RawTransactionReference::new(network, signature); let reference = crate::RawTransactionReference::new(network, signature);
let transaction = crate::RawTransaction::new(reference, 42, std::option::Option::None, payload); let transaction = crate::RawTransaction::new(reference, 42, std::option::Option::None, payload);
assert_eq!(transaction.reference().network().as_str(), "mainnet-beta"); assert_eq!(transaction.reference().network().as_str(), "mainnet");
assert_eq!(transaction.reference().signature(), signature); assert_eq!(transaction.reference().signature(), signature);
assert_eq!(transaction.slot(), 42); assert_eq!(transaction.slot(), 42);
assert!(transaction.block_time().is_none()); assert!(transaction.block_time().is_none());

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/unit_tests/raw_account.rs // file: crates/ksp-store-postgres-lib/unit_tests/raw_account.rs
// version: 7 // version: 8
fn network() -> ksp_store_api::RawNetworkId { fn network() -> ksp_store_api::RawNetworkId {
return match ksp_store_api::RawNetworkId::new("devnet") { return match ksp_store_api::RawNetworkId::new("devnet") {
@@ -311,7 +311,7 @@ fn pre_004_account_observation_rejects_hostile_rows_without_echoing_values() {
#[test] #[test]
fn pre_004_account_wrong_network_guard_is_pre_io_and_static() { fn pre_004_account_wrong_network_guard_is_pre_io_and_static() {
let backend = network(); let backend = network();
let foreign = match ksp_store_api::RawNetworkId::new("mainnet-beta") { let foreign = match ksp_store_api::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid foreign network rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid foreign network rejected: {error:?}"),
}; };
@@ -343,7 +343,7 @@ fn pre_005_account_acquisition_input_guard_requires_network_and_exact_reference(
std::result::Result::Err(error) => panic!("valid account observation rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid account observation rejected: {error:?}"),
}; };
assert!(super::ensure_acquisition_inputs(&backend, &valid_state, &valid_observation).is_ok()); assert!(super::ensure_acquisition_inputs(&backend, &valid_state, &valid_observation).is_ok());
let foreign = match ksp_store_api::RawNetworkId::new("mainnet-beta") { let foreign = match ksp_store_api::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid foreign network rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid foreign network rejected: {error:?}"),
}; };
@@ -437,7 +437,7 @@ fn pre_005_account_origin_encoding_is_exact_and_static() {
#[test] #[test]
fn pre_006_additional_observation_input_guard_rejects_wrong_network_before_io() { fn pre_006_additional_observation_input_guard_rejects_wrong_network_before_io() {
let backend = network(); let backend = network();
let foreign = match ksp_store_api::RawNetworkId::new("mainnet-beta") { let foreign = match ksp_store_api::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid foreign network rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid foreign network rejected: {error:?}"),
}; };
@@ -532,7 +532,7 @@ fn pre_007_account_cursor_rejects_replay_across_query_context_and_transaction_fa
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid account cursor encoding rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid account cursor encoding rejected: {error:?}"),
}; };
let other_network = page_query("mainnet-beta", Some(pubkey), Some(10), Some(20), ksp_store_api::RawSortDirection::Ascending); let other_network = page_query("mainnet", Some(pubkey), Some(10), Some(20), ksp_store_api::RawSortDirection::Ascending);
let other_pubkey = let other_pubkey =
page_query("devnet", Some(ksp_store_api::Pubkey::new_from_array([6_u8; 32])), Some(10), Some(20), ksp_store_api::RawSortDirection::Ascending); page_query("devnet", Some(ksp_store_api::Pubkey::new_from_array([6_u8; 32])), Some(10), Some(20), ksp_store_api::RawSortDirection::Ascending);
let other_direction = page_query("devnet", Some(pubkey), Some(10), Some(20), ksp_store_api::RawSortDirection::Descending); let other_direction = page_query("devnet", Some(pubkey), Some(10), Some(20), ksp_store_api::RawSortDirection::Descending);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs // file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs
// version: 5 // version: 6
fn network() -> ksp_store_api::RawNetworkId { fn network() -> ksp_store_api::RawNetworkId {
return match ksp_store_api::RawNetworkId::new("devnet") { return match ksp_store_api::RawNetworkId::new("devnet") {
@@ -271,7 +271,7 @@ fn v0_3_8_pre_004_inspection_summary_rejects_physical_retention_shape_residue()
#[test] #[test]
fn pre_004_wrong_network_is_rejected_by_the_private_pre_io_guard() { fn pre_004_wrong_network_is_rejected_by_the_private_pre_io_guard() {
let backend_network = network(); let backend_network = network();
let other_network = match ksp_store_api::RawNetworkId::new("mainnet-beta") { let other_network = match ksp_store_api::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid alternate network rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid alternate network rejected: {error:?}"),
}; };
@@ -330,7 +330,7 @@ fn pre_005_atomic_acquisition_pre_io_guard_requires_backend_network_and_exact_re
let mismatched = observation(other_reference, 4, "publicnode"); let mismatched = observation(other_reference, 4, "publicnode");
let mismatch = super::ensure_acquisition_inputs(&backend_network, &raw_transaction, &mismatched); let mismatch = super::ensure_acquisition_inputs(&backend_network, &raw_transaction, &mismatched);
assert_eq!(mismatch.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::Conflict)); assert_eq!(mismatch.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::Conflict));
let other_network = match ksp_store_api::RawNetworkId::new("mainnet-beta") { let other_network = match ksp_store_api::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid alternate network rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid alternate network rejected: {error:?}"),
}; };
@@ -508,7 +508,7 @@ fn pre_006_cursor_v1_rejects_replay_across_network_direction_and_range() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid cursor encoding rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid cursor encoding rejected: {error:?}"),
}; };
let other_network = page_query("mainnet-beta", Some(10), Some(20), ksp_store_api::RawSortDirection::Ascending, None); let other_network = page_query("mainnet", Some(10), Some(20), ksp_store_api::RawSortDirection::Ascending, None);
let other_direction = page_query("devnet", Some(10), Some(20), ksp_store_api::RawSortDirection::Descending, None); let other_direction = page_query("devnet", Some(10), Some(20), ksp_store_api::RawSortDirection::Descending, None);
let other_range = page_query("devnet", Some(11), Some(20), ksp_store_api::RawSortDirection::Ascending, None); let other_range = page_query("devnet", Some(11), Some(20), ksp_store_api::RawSortDirection::Ascending, None);
for candidate in [&other_network, &other_direction, &other_range] { for candidate in [&other_network, &other_direction, &other_range] {
@@ -640,7 +640,7 @@ fn pre_007_compacted_transitions_are_rejected_by_the_pre_io_guard() {
#[test] #[test]
fn pre_007_retention_transition_pre_io_guard_rejects_wrong_network() { fn pre_007_retention_transition_pre_io_guard_rejects_wrong_network() {
let backend_network = network(); let backend_network = network();
let other_network = match ksp_store_api::RawNetworkId::new("mainnet-beta") { let other_network = match ksp_store_api::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid alternate network rejected: {error:?}"), std::result::Result::Err(error) => panic!("valid alternate network rejected: {error:?}"),
}; };

View File

@@ -0,0 +1,14 @@
# file: crates/ksp-worker-api/Cargo.toml
# version: 1
[package]
name = "ksp-worker-api"
version.workspace = true
edition.workspace = true
repository.workspace = true
[dependencies]
ksp-core-lib = { path = "../ksp-core-lib" }
[lints]
workspace = true

View File

@@ -0,0 +1,69 @@
<!-- file: crates/ksp-worker-api/README.md -->
<!-- version: 2 -->
# ksp-worker-api
`ksp-worker-api` fournit les contrats passifs et runtime-neutral communs aux services continus KSP. Elle décrit un Worker observable ; elle n'est ni un runtime de Worker ni une API métier d'acquisition.
La crate possède l'identité logique d'un Worker, son lifecycle continu, une classification minimale de health/activity, l'intention de stop coopératif et un contrat latest-value fixe pour l'observation. Elle ne possède aucun runtime concret, aucune politique de restart, aucun Job, aucun Transport, aucun Store et aucun contrat Solana.
## Responsabilités
La façade crate-root expose :
- `WorkerId` et `WorkerKindCode`, bornés et validés ;
- `WorkerState`, `WorkerHealth` et `WorkerActivity` ;
- `WorkerLifecycle`, propriétaire des transitions admises ;
- `WorkerStopToken`, cloneable et idempotent ;
- `WorkerSnapshotSequence`, strictement monotone et sans wrap silencieux ;
- `WorkerSnapshot`, forme commune fixe sans payload métier ;
- `WorkerSnapshotSource`, contrat object-safe de lecture courante et attente d'une valeur plus récente ;
- les codes d'erreur Worker et les types `Error`/`Result` communs de Core.
## Lifecycle
Le lifecycle admis reste explicitement borné :
```text
Created -> Starting | Stopped
Starting -> Running | Stopping | Faulted(ErrorCode)
Running -> Stopping | Faulted(ErrorCode)
Stopping -> Stopped | Faulted(ErrorCode)
Stopped -> terminal
Faulted -> terminal
```
`Stopped` et `Faulted` sont terminaux et immuables. Une transition invalide retourne `ERROR_CODE_WORKER_TRANSITION_INVALID` sans modifier l'état source.
## Observation latest-value
`WorkerSnapshotSource` n'impose ni callback, ni queue d'événements, ni runtime async particulier. Un listener lit d'abord `current()`, mémorise la `WorkerSnapshotSequence`, puis appelle `wait_for_change()` s'il doit attendre une valeur plus récente.
Les mises à jour intermédiaires peuvent être coalescées : le contrat porte sur la dernière valeur complète, pas sur la livraison de chaque événement. Le snapshot commun ne contient que l'identité, la séquence, le lifecycle, la health et l'activity.
## Stop
`WorkerStopToken` représente uniquement une intention coopérative partagée. Il ne tue pas une tâche, ne ferme pas un socket et ne décide pas du résultat terminal. Le runtime concret observe cette intention puis pilote `WorkerLifecycle` selon sa politique de shutdown.
## Contrôle runtime et restart
La crate ne possède aucune opération runtime générique `start()`, `stop()`, `restart()`, aucun scheduler, retry/backoff, process manager ou handle d'exécution. `WorkerLifecycle` expose uniquement les transitions d'état détenues par le producer concret ; `WorkerStopToken` exprime uniquement une intention coopérative.
Un lifecycle/source terminal n'est jamais réanimé ni rebinding vers une nouvelle exécution. Démarrage, arrêt effectif, drain, join, recréation et supervision appartiennent au Worker concret, au caller ou à une couche de contrôle supérieure.
## Firewall
La dépendance normale est volontairement minimale :
```text
ksp-worker-api
-> ksp-core-lib
```
La crate ne dépend pas de `ksp-job-api`, Tokio, Futures, serde, Logging, Config, Interface, Transport, Store, Tauri ou d'un SDK provider.
## Documentation
- [`USAGE.md`](USAGE.md) — utilisation durable des contrats Worker ;
- [`../../docs/architecture/003-COMPONENT_CONTRACTS.md`](../../docs/architecture/003-COMPONENT_CONTRACTS.md) — contrats de composants ;
- [`../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md`](../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md) — séparation Worker/Job et ownership d'acquisition.

View File

@@ -0,0 +1,149 @@
<!-- file: crates/ksp-worker-api/USAGE.md -->
<!-- version: 2 -->
# Utilisation de ksp-worker-api
Cette page décrit la façade publique durable de `ksp-worker-api`. Les consumers utilisent uniquement les exports du crate-root.
## Construire une identité Worker
```rust
fn worker_identity() -> ksp_worker_api::Result<(ksp_worker_api::WorkerId, ksp_worker_api::WorkerKindCode)> {
let id = match ksp_worker_api::WorkerId::new("raw-ingest-mainnet-0001") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let kind = match ksp_worker_api::WorkerKindCode::new("raw_transaction_ingest") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok((id, kind));
}
```
`WorkerId` identifie une instance logique observée par un lifecycle/source donné. `WorkerKindCode` identifie une famille de Workers. Les deux sont bornés et utilisent un alphabet sûr. Le `Debug` de `WorkerId` masque sa valeur.
## Piloter un lifecycle passif
Le lifecycle ne démarre aucun runtime. L'exemple suivant représente uniquement les transitions publiées par un producer concret lorsqu'il entre en exécution :
```rust
fn running_lifecycle(id: ksp_worker_api::WorkerId, kind: ksp_worker_api::WorkerKindCode) -> ksp_worker_api::Result<ksp_worker_api::WorkerLifecycle> {
let mut lifecycle = ksp_worker_api::WorkerLifecycle::new(id, kind);
if let std::result::Result::Err(error) = lifecycle.start() {
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) = lifecycle.mark_running() {
return std::result::Result::Err(error);
}
return std::result::Result::Ok(lifecycle);
}
```
Le producer possède l'autorité de transition. Il ne force jamais un état directement. Une transition invalide retourne une erreur stable et conserve l'état courant.
Pour un shutdown coopératif après observation du token :
```rust
if let std::result::Result::Err(error) = lifecycle.mark_stopping() {
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) = lifecycle.mark_stopped() {
return std::result::Result::Err(error);
}
```
Pour un fault terminal :
```rust
const IO_FAULT: ksp_worker_api::ErrorCode = ksp_worker_api::ErrorCode::new("example_worker", "io_fault");
if let std::result::Result::Err(error) = lifecycle.fault(IO_FAULT) {
return std::result::Result::Err(error);
}
```
`Stopped` et `Faulted(ErrorCode)` sont terminaux. Un ancien lifecycle terminal ne représente jamais une nouvelle exécution.
## Distinguer lifecycle, health et activity
`WorkerState` décrit la phase du service. `WorkerHealth` décrit sa qualité opérationnelle. `WorkerActivity` indique seulement `Unknown`, `Idle` ou `Active`.
```rust
let snapshot = ksp_worker_api::WorkerSnapshot::new(
lifecycle.id().clone(),
lifecycle.kind().clone(),
ksp_worker_api::WorkerSnapshotSequence::initial(),
lifecycle.state(),
ksp_worker_api::WorkerHealth::Healthy,
ksp_worker_api::WorkerActivity::Idle,
);
```
Le snapshot commun ne porte ni pourcentage, ni total, ni backlog, ni slot, ni transaction, ni métrique métier. Une API Worker concrète peut exposer séparément ses propres métriques.
## Partager une intention de stop
```rust
let token = ksp_worker_api::WorkerStopToken::new();
let listener = token.clone();
assert!(!listener.is_stop_requested());
assert!(token.request_stop());
assert!(listener.is_stop_requested());
assert!(!token.request_stop());
```
Le premier appel qui change l'intention retourne `true`. Les demandes suivantes sont idempotentes et retournent `false`.
Le token n'est pas une primitive de kill et ne garantit aucun délai de shutdown. Timeout, drain, join et retry appartiennent au runtime/caller.
## Démarrer et arrêter un Worker concret
`ksp-worker-api` n'expose volontairement aucune commande runtime universelle. Une crate concrète peut fournir une surface `start`/`stop` adaptée à son domaine, mais elle utilise les contrats communs pour publier son identité, ses transitions, son état courant et l'intention de stop.
Un Worker concret ne doit donc pas transformer `WorkerLifecycle` en handle d'exécution ni ajouter des paramètres métier au contrat générique. Les paramètres/configurations propres à une famille de Workers restent dans cette famille ou dans sa couche de composition.
## Observer un snapshot latest-value
Un consumer portable peut travailler directement avec le trait object-safe :
```rust
async fn observe(source: &dyn ksp_worker_api::WorkerSnapshotSource) {
let current = source.current();
let observed = current.sequence();
let newer = source.wait_for_change(observed).await;
assert!(newer.sequence().is_after(observed));
}
```
`wait_for_change` retourne la dernière valeur complète disponible après coalescence éventuelle. Un consumer ne doit pas supposer qu'il recevra chaque mise à jour intermédiaire.
Un listener tardif commence par `current()`. Tant que la source existe, son snapshot terminal courant reste lisible.
## Faire avancer une séquence
```rust
let first = ksp_worker_api::WorkerSnapshotSequence::initial();
let second = match first.next() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert!(second.is_after(first));
```
L'épuisement de `u64` est une erreur explicite ; la séquence ne wrappe jamais silencieusement.
## Frontières à respecter
Ne pas ajouter à `ksp-worker-api` :
```text
runtime Tokio/Futures concret
Job lifecycle ou checkpoint/backfill
Transport, Store, Config ou Logging
DTO Solana/provider
restart/retry/scheduler/process manager
payload métier dans WorkerSnapshot
```
Ces responsabilités appartiennent aux Workers concrets et aux couches de composition/contrôle supérieures.

View File

@@ -0,0 +1,12 @@
// file: crates/ksp-worker-api/src/error.rs
// version: 1
/// Error code used when a Worker identifier violates its bounded safe-code contract.
pub const ERROR_CODE_WORKER_ID_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("worker_api", "worker_id_invalid");
/// Error code used when a Worker kind code violates its bounded safe-code contract.
pub const ERROR_CODE_WORKER_KIND_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("worker_api", "worker_kind_invalid");
/// Error code used when a Worker snapshot sequence cannot advance without wrapping.
pub const ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("worker_api", "worker_snapshot_sequence_exhausted");
/// Error code used when a requested Worker lifecycle transition is not allowed.
pub const ERROR_CODE_WORKER_TRANSITION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("worker_api", "worker_transition_invalid");

View File

@@ -0,0 +1,76 @@
// file: crates/ksp-worker-api/src/identity.rs
// version: 2
/// Maximum UTF-8 byte length admitted for one Worker identifier.
pub const MAX_WORKER_ID_BYTES: usize = 128;
/// Maximum UTF-8 byte length admitted for one Worker kind code.
pub const MAX_WORKER_KIND_CODE_BYTES: usize = 128;
/// Bounded caller-supplied identity of one logical Worker instance.
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WorkerId(std::string::String);
impl crate::WorkerId {
/// Creates one non-empty Worker identifier using the KSP safe-code alphabet.
pub fn new(value: impl std::convert::Into<std::string::String>) -> crate::Result<Self> {
let value = value.into();
if !valid_worker_code(value.as_str(), crate::MAX_WORKER_ID_BYTES) {
return std::result::Result::Err(identity_error(crate::ERROR_CODE_WORKER_ID_INVALID, "worker_id"));
}
return std::result::Result::Ok(Self(value));
}
/// Returns the validated Worker identifier.
#[must_use]
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
impl std::fmt::Debug for crate::WorkerId {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str("WorkerId(..)");
}
}
/// Bounded stable code identifying one concrete family of Workers.
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WorkerKindCode(std::string::String);
impl crate::WorkerKindCode {
/// Creates one non-empty Worker kind using the KSP safe-code alphabet.
pub fn new(value: impl std::convert::Into<std::string::String>) -> crate::Result<Self> {
let value = value.into();
if !valid_worker_code(value.as_str(), crate::MAX_WORKER_KIND_CODE_BYTES) {
return std::result::Result::Err(identity_error(crate::ERROR_CODE_WORKER_KIND_INVALID, "worker_kind"));
}
return std::result::Result::Ok(Self(value));
}
/// Returns the validated stable Worker kind code.
#[must_use]
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
impl std::fmt::Debug for crate::WorkerKindCode {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_tuple("WorkerKindCode").field(&self.0).finish();
}
}
fn identity_error(code: crate::ErrorCode, field: &'static str) -> crate::Error {
return crate::Error::new(code, "invalid bounded Worker identity").with_context("field", field);
}
fn valid_worker_code(value: &str, maximum_len: usize) -> bool {
if value.is_empty() || value.len() > maximum_len {
return false;
}
return value.bytes().all(|byte| return byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.' | b':'));
}
#[cfg(test)]
#[path = "../unit_tests/identity.rs"]
mod tests;

View File

@@ -0,0 +1,62 @@
// file: crates/ksp-worker-api/src/lib.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Passive runtime-neutral lifecycle contracts for continuous KSP Workers.
//!
//! This foundation owns bounded Worker identity, explicit continuous lifecycle
//! transitions, cooperative stop intent and a fixed latest-value observation
//! contract. Concrete runtimes, restart policy, Transport, Store, Config, Jobs
//! and domain-specific Worker behavior remain outside this crate.
mod error;
mod identity;
mod lifecycle;
mod snapshot;
mod stop;
/// Error code used when a Worker identifier violates its bounded safe-code contract.
pub use self::error::ERROR_CODE_WORKER_ID_INVALID;
/// Error code used when a Worker kind code violates its bounded safe-code contract.
pub use self::error::ERROR_CODE_WORKER_KIND_INVALID;
/// Error code used when a Worker snapshot sequence cannot advance without wrapping.
pub use self::error::ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED;
/// Error code used when a requested Worker lifecycle transition is not allowed.
pub use self::error::ERROR_CODE_WORKER_TRANSITION_INVALID;
/// Maximum UTF-8 byte length admitted for one Worker identifier.
pub use self::identity::MAX_WORKER_ID_BYTES;
/// Maximum UTF-8 byte length admitted for one Worker kind code.
pub use self::identity::MAX_WORKER_KIND_CODE_BYTES;
/// Bounded caller-supplied identity of one logical Worker instance.
pub use self::identity::WorkerId;
/// Bounded stable code identifying one concrete family of Workers.
pub use self::identity::WorkerKindCode;
/// Minimal generic activity classification for a continuous Worker.
pub use self::lifecycle::WorkerActivity;
/// Operational health classification independent from Worker lifecycle phase.
pub use self::lifecycle::WorkerHealth;
/// Passive owner of one Worker identity and its validated lifecycle state.
pub use self::lifecycle::WorkerLifecycle;
/// Current lifecycle state of one continuous Worker.
pub use self::lifecycle::WorkerState;
/// Fixed common latest-value snapshot exposed by every Worker implementation.
pub use self::snapshot::WorkerSnapshot;
/// Runtime-neutral future returned while observing a latest-value Worker snapshot source.
pub use self::snapshot::WorkerSnapshotFuture;
/// Monotone sequence attached to one latest-value Worker snapshot stream.
pub use self::snapshot::WorkerSnapshotSequence;
/// Runtime-neutral read and change-wait contract for one latest-value Worker snapshot stream.
pub use self::snapshot::WorkerSnapshotSource;
/// Runtime-neutral cloneable token carrying cooperative Worker stop intent.
pub use self::stop::WorkerStopToken;
/// Common KSP error type used by Worker-facing contracts.
pub use ksp_core_lib::Error;
/// Stable structured code identifying a KSP error category and condition.
pub use ksp_core_lib::ErrorCode;
/// Structured contextual field attached to a KSP error.
pub use ksp_core_lib::ErrorContext;
/// Common KSP result alias using [`Error`].
pub use ksp_core_lib::Result;

View File

@@ -0,0 +1,199 @@
// file: crates/ksp-worker-api/src/lifecycle.rs
// version: 2
/// Current lifecycle state of one continuous Worker.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum WorkerState {
/// The Worker exists but has not started initialization.
Created,
/// The Worker is initializing resources before entering steady service.
Starting,
/// The Worker is actively providing its continuous service.
Running,
/// The Worker observed stop intent and is draining or releasing resources.
Stopping,
/// The Worker reached its normal terminal stopped state.
Stopped,
/// The Worker reached a terminal fault classified by one stable KSP error code.
Faulted(crate::ErrorCode),
}
impl crate::WorkerState {
/// Returns the stable safe lifecycle code without rendering Worker data.
#[must_use]
pub const fn code(&self) -> &'static str {
return match self {
Self::Created => "created",
Self::Starting => "starting",
Self::Running => "running",
Self::Stopping => "stopping",
Self::Stopped => "stopped",
Self::Faulted(_) => "faulted",
};
}
/// Returns the terminal fault code when the state is [`Self::Faulted`].
#[must_use]
pub const fn fault_code(&self) -> std::option::Option<crate::ErrorCode> {
return match self {
Self::Faulted(code) => std::option::Option::Some(*code),
_ => std::option::Option::None,
};
}
/// Reports whether no later lifecycle transition is permitted.
#[must_use]
pub const fn is_terminal(&self) -> bool {
return matches!(self, Self::Stopped | Self::Faulted(_));
}
}
/// Operational health classification independent from Worker lifecycle phase.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum WorkerHealth {
/// Health has not yet been established or cannot currently be classified.
Unknown,
/// The Worker is operating within its expected healthy envelope.
Healthy,
/// The Worker is operating with a known degradation while service remains available.
Degraded,
/// The Worker is currently unable to satisfy its expected service health contract.
Unhealthy,
}
impl crate::WorkerHealth {
/// Returns the stable safe code for this health classification.
#[must_use]
pub const fn code(&self) -> &'static str {
return match self {
Self::Unknown => "unknown",
Self::Healthy => "healthy",
Self::Degraded => "degraded",
Self::Unhealthy => "unhealthy",
};
}
}
/// Minimal generic activity classification for a continuous Worker.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum WorkerActivity {
/// Activity has not yet been established or cannot currently be classified.
Unknown,
/// The Worker is alive but not currently processing concrete work.
Idle,
/// The Worker is currently processing concrete work.
Active,
}
impl crate::WorkerActivity {
/// Returns the stable safe code for this activity classification.
#[must_use]
pub const fn code(&self) -> &'static str {
return match self {
Self::Unknown => "unknown",
Self::Idle => "idle",
Self::Active => "active",
};
}
}
/// Passive owner of one Worker identity and its validated lifecycle state.
#[derive(Eq, PartialEq)]
pub struct WorkerLifecycle {
id: crate::WorkerId,
kind: crate::WorkerKindCode,
state: crate::WorkerState,
}
impl crate::WorkerLifecycle {
/// Creates one lifecycle in [`WorkerState::Created`] state.
#[must_use]
pub const fn new(id: crate::WorkerId, kind: crate::WorkerKindCode) -> Self {
return Self { id, kind, state: crate::WorkerState::Created };
}
/// Returns the logical Worker identity.
#[must_use]
pub const fn id(&self) -> &crate::WorkerId {
return &self.id;
}
/// Returns the stable Worker family code.
#[must_use]
pub const fn kind(&self) -> &crate::WorkerKindCode {
return &self.kind;
}
/// Returns the current lifecycle state.
#[must_use]
pub const fn state(&self) -> crate::WorkerState {
return self.state;
}
/// Begins initialization of a newly created Worker.
pub fn start(&mut self) -> crate::Result<()> {
return self.transition(crate::WorkerState::Starting);
}
/// Records that initialization completed and steady service is running.
pub fn mark_running(&mut self) -> crate::Result<()> {
return self.transition(crate::WorkerState::Running);
}
/// Records that a starting or running Worker observed cooperative stop intent.
pub fn mark_stopping(&mut self) -> crate::Result<()> {
return self.transition(crate::WorkerState::Stopping);
}
/// Records normal terminal stop after draining, or before initialization began.
pub fn mark_stopped(&mut self) -> crate::Result<()> {
return self.transition(crate::WorkerState::Stopped);
}
/// Records a terminal fault using only a stable KSP error code.
pub fn fault(&mut self, code: crate::ErrorCode) -> crate::Result<()> {
return self.transition(crate::WorkerState::Faulted(code));
}
fn transition(&mut self, target: crate::WorkerState) -> crate::Result<()> {
if !allowed_transition(self.state, target) {
return std::result::Result::Err(transition_error(self.state, target));
}
self.state = target;
return std::result::Result::Ok(());
}
}
impl std::fmt::Debug for crate::WorkerLifecycle {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("WorkerLifecycle").field("id", &self.id).field("kind", &self.kind).field("state", &self.state).finish();
}
}
fn allowed_transition(source: crate::WorkerState, target: crate::WorkerState) -> bool {
return matches!(
(source, target),
(crate::WorkerState::Created, crate::WorkerState::Starting)
| (crate::WorkerState::Created, crate::WorkerState::Stopped)
| (crate::WorkerState::Starting, crate::WorkerState::Running)
| (crate::WorkerState::Starting, crate::WorkerState::Stopping)
| (crate::WorkerState::Starting, crate::WorkerState::Faulted(_))
| (crate::WorkerState::Running, crate::WorkerState::Stopping)
| (crate::WorkerState::Running, crate::WorkerState::Faulted(_))
| (crate::WorkerState::Stopping, crate::WorkerState::Stopped)
| (crate::WorkerState::Stopping, crate::WorkerState::Faulted(_))
);
}
fn transition_error(source: crate::WorkerState, target: crate::WorkerState) -> crate::Error {
return crate::Error::new(crate::ERROR_CODE_WORKER_TRANSITION_INVALID, "invalid Worker lifecycle transition")
.with_context("source_state", source.code())
.with_context("target_state", target.code());
}
#[cfg(test)]
#[path = "../unit_tests/lifecycle.rs"]
mod tests;

View File

@@ -0,0 +1,143 @@
// file: crates/ksp-worker-api/src/snapshot.rs
// version: 3
/// Monotone sequence attached to one latest-value Worker snapshot stream.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WorkerSnapshotSequence(u64);
impl crate::WorkerSnapshotSequence {
/// Creates the initial sequence position for one Worker snapshot stream.
#[must_use]
pub const fn initial() -> Self {
return Self(0);
}
/// Returns the opaque numeric position carried by this sequence.
#[must_use]
pub const fn value(&self) -> u64 {
return self.0;
}
/// Advances the sequence exactly once or reports exhaustion without wrapping.
pub fn next(&self) -> crate::Result<Self> {
let next = match self.0.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
crate::Error::new(crate::ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED, "Worker snapshot sequence exhausted")
.with_context("sequence", self.0.to_string()),
);
},
};
return std::result::Result::Ok(Self(next));
}
/// Reports whether this sequence is strictly newer than an observed sequence.
#[must_use]
pub const fn is_after(&self, observed: Self) -> bool {
return self.0 > observed.0;
}
#[cfg(test)]
const fn exhausted_for_test() -> Self {
return Self(u64::MAX);
}
}
/// Fixed common latest-value snapshot exposed by every Worker implementation.
#[derive(Clone, Eq, PartialEq)]
pub struct WorkerSnapshot {
id: crate::WorkerId,
kind: crate::WorkerKindCode,
sequence: crate::WorkerSnapshotSequence,
state: crate::WorkerState,
health: crate::WorkerHealth,
activity: crate::WorkerActivity,
}
impl crate::WorkerSnapshot {
/// Creates one immutable common Worker snapshot from already validated values.
#[must_use]
pub const fn new(
id: crate::WorkerId,
kind: crate::WorkerKindCode,
sequence: crate::WorkerSnapshotSequence,
state: crate::WorkerState,
health: crate::WorkerHealth,
activity: crate::WorkerActivity,
) -> Self {
return Self { id, kind, sequence, state, health, activity };
}
/// Returns the logical Worker identity.
#[must_use]
pub const fn id(&self) -> &crate::WorkerId {
return &self.id;
}
/// Returns the stable Worker family code.
#[must_use]
pub const fn kind(&self) -> &crate::WorkerKindCode {
return &self.kind;
}
/// Returns the monotone sequence of this latest value.
#[must_use]
pub const fn sequence(&self) -> crate::WorkerSnapshotSequence {
return self.sequence;
}
/// Returns the lifecycle state represented by this snapshot.
#[must_use]
pub const fn state(&self) -> crate::WorkerState {
return self.state;
}
/// Returns the operational health represented by this snapshot.
#[must_use]
pub const fn health(&self) -> crate::WorkerHealth {
return self.health;
}
/// Returns the generic activity represented by this snapshot.
#[must_use]
pub const fn activity(&self) -> crate::WorkerActivity {
return self.activity;
}
}
impl std::fmt::Debug for crate::WorkerSnapshot {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("WorkerSnapshot")
.field("id", &self.id)
.field("kind", &self.kind)
.field("sequence", &self.sequence)
.field("state", &self.state)
.field("health", &self.health)
.field("activity", &self.activity)
.finish();
}
}
/// Runtime-neutral future returned while observing a latest-value Worker snapshot source.
pub type WorkerSnapshotFuture<'a> = std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = crate::WorkerSnapshot> + std::marker::Send + 'a>>;
/// Runtime-neutral read and change-wait contract for one latest-value Worker snapshot stream.
pub trait WorkerSnapshotSource: std::marker::Send + std::marker::Sync {
/// Returns the complete current common Worker snapshot without replaying prior updates.
#[must_use]
fn current(&self) -> crate::WorkerSnapshot;
/// Waits for a snapshot newer than `observed`, returning the complete current value after coalescing intermediate updates.
fn wait_for_change(&self, observed: crate::WorkerSnapshotSequence) -> crate::WorkerSnapshotFuture<'_>;
}
#[cfg(test)]
fn exhausted_snapshot_sequence() -> crate::WorkerSnapshotSequence {
return crate::WorkerSnapshotSequence::exhausted_for_test();
}
#[cfg(test)]
#[path = "../unit_tests/snapshot.rs"]
mod tests;

View File

@@ -0,0 +1,44 @@
// file: crates/ksp-worker-api/src/stop.rs
// version: 2
/// Runtime-neutral cloneable token carrying cooperative Worker stop intent.
#[derive(Clone)]
pub struct WorkerStopToken {
requested: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
impl crate::WorkerStopToken {
/// Creates a token with no stop request.
#[must_use]
pub fn new() -> Self {
return Self { requested: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)) };
}
/// Requests stop and returns `true` only for the first request shared by all clones.
#[must_use]
pub fn request_stop(&self) -> bool {
return !self.requested.swap(true, std::sync::atomic::Ordering::AcqRel);
}
/// Reports whether stop has been requested through any clone.
#[must_use]
pub fn is_stop_requested(&self) -> bool {
return self.requested.load(std::sync::atomic::Ordering::Acquire);
}
}
impl std::default::Default for crate::WorkerStopToken {
fn default() -> Self {
return Self::new();
}
}
impl std::fmt::Debug for crate::WorkerStopToken {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("WorkerStopToken").field("stop_requested", &self.is_stop_requested()).finish();
}
}
#[cfg(test)]
#[path = "../unit_tests/stop.rs"]
mod tests;

View File

@@ -0,0 +1,128 @@
// file: crates/ksp-worker-api/tests/dependency_boundary.rs
// version: 3
//! Dependency and runtime-neutrality canaries for the Worker API foundation.
#[test]
fn pre_002_manifest_has_exact_core_only_dependency_graph() {
let manifest = include_str!("../Cargo.toml");
assert!(!manifest.contains("[features]"));
assert!(!manifest.contains("[dev-dependencies]"));
assert!(!manifest.contains("[build-dependencies]"));
assert_eq!(manifest.matches("[dependencies]").count(), 1);
let dependencies_tail = manifest.split("[dependencies]").nth(1);
assert!(dependencies_tail.is_some());
let dependencies_tail = match dependencies_tail {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let dependencies = match dependencies_tail.split("[lints]").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert_eq!(manifest_dependency_names(dependencies), std::vec!["ksp-core-lib"]);
return;
}
#[test]
fn pre_002_production_sources_forbid_job_runtime_domain_and_wire_dependencies() {
let sources = [
include_str!("../src/error.rs"),
include_str!("../src/identity.rs"),
include_str!("../src/lib.rs"),
include_str!("../src/lifecycle.rs"),
include_str!("../src/snapshot.rs"),
include_str!("../src/stop.rs"),
];
for source in sources {
for forbidden in [
"ksp_config_lib::",
"ksp_interface_lib::",
"ksp_job_api::",
"ksp_logging_lib::",
"ksp_offchain_transport_lib::",
"ksp_onchain_transport_lib::",
"ksp_store_api::",
"ksp_store_lib::",
"reqwest::",
"serde::",
"serde_json::",
"solana_",
"tauri::",
"tokio::",
"tonic::",
concat!("tracing", "::"),
] {
assert!(!source.contains(forbidden), "forbidden Worker API dependency path detected: {forbidden}");
}
}
return;
}
#[test]
fn pre_002_fix_001_shared_visible_items_use_crate_root_facade_inside_owner_modules() {
let identity = include_str!("../src/identity.rs");
let lifecycle = include_str!("../src/lifecycle.rs");
let snapshot = include_str!("../src/snapshot.rs");
let stop = include_str!("../src/stop.rs");
for expected in [
"impl crate::WorkerId {",
"impl std::fmt::Debug for crate::WorkerId {",
"impl crate::WorkerKindCode {",
"impl std::fmt::Debug for crate::WorkerKindCode {",
] {
assert!(identity.contains(expected), "missing crate-root Worker identity impl target: {expected}");
}
for expected in [
"impl crate::WorkerActivity {",
"impl crate::WorkerHealth {",
"impl crate::WorkerLifecycle {",
"impl crate::WorkerState {",
"impl std::fmt::Debug for crate::WorkerLifecycle {",
] {
assert!(lifecycle.contains(expected), "missing crate-root Worker lifecycle impl target: {expected}");
}
for expected in ["impl crate::WorkerSnapshotSequence {", "impl crate::WorkerSnapshot {", "impl std::fmt::Debug for crate::WorkerSnapshot {"] {
assert!(snapshot.contains(expected), "missing crate-root Worker snapshot reference: {expected}");
}
for expected in [
"impl crate::WorkerStopToken {",
"impl std::default::Default for crate::WorkerStopToken {",
"impl std::fmt::Debug for crate::WorkerStopToken {",
] {
assert!(stop.contains(expected), "missing crate-root Worker stop impl target: {expected}");
}
return;
}
#[test]
fn pre_002_fix_002_private_tuple_constructor_stays_inside_owner_impl() {
let snapshot = include_str!("../src/snapshot.rs");
assert!(snapshot.contains("const fn exhausted_for_test() -> Self {"));
assert!(snapshot.contains("return Self(u64::MAX);"));
assert!(snapshot.contains("return crate::WorkerSnapshotSequence::exhausted_for_test();"));
assert!(!snapshot.contains("return crate::WorkerSnapshotSequence(u64::MAX);"));
return;
}
fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
let mut names = std::vec::Vec::new();
for line in section.lines() {
let content = match line.split('#').next() {
std::option::Option::Some(value) => value.trim(),
std::option::Option::None => continue,
};
if content.is_empty() {
continue;
}
let name = match content.split('=').next() {
std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"),
std::option::Option::None => continue,
};
if !name.is_empty() {
names.push(name);
}
}
names.sort_unstable();
return names;
}

View File

@@ -0,0 +1,52 @@
// file: crates/ksp-worker-api/tests/public_api.rs
// version: 1
//! External-consumer canaries for the Worker API crate-root facade.
#[test]
fn pre_002_identity_lifecycle_stop_and_snapshot_are_consumable_from_crate_root() {
let id = ksp_worker_api::WorkerId::new("worker-public-001");
let kind = ksp_worker_api::WorkerKindCode::new("example_worker");
assert!(id.is_ok());
assert!(kind.is_ok());
let id = match id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let kind = match kind {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let mut lifecycle = ksp_worker_api::WorkerLifecycle::new(id.clone(), kind.clone());
assert_eq!(lifecycle.state(), ksp_worker_api::WorkerState::Created);
assert!(lifecycle.start().is_ok());
assert!(lifecycle.mark_running().is_ok());
let stop = ksp_worker_api::WorkerStopToken::new();
assert!(stop.request_stop());
assert!(lifecycle.mark_stopping().is_ok());
let snapshot = ksp_worker_api::WorkerSnapshot::new(
id,
kind,
ksp_worker_api::WorkerSnapshotSequence::initial(),
lifecycle.state(),
ksp_worker_api::WorkerHealth::Healthy,
ksp_worker_api::WorkerActivity::Idle,
);
assert_eq!(snapshot.state(), ksp_worker_api::WorkerState::Stopping);
assert_eq!(snapshot.health(), ksp_worker_api::WorkerHealth::Healthy);
assert_eq!(snapshot.activity(), ksp_worker_api::WorkerActivity::Idle);
return;
}
#[test]
fn pre_002_public_bounds_and_error_codes_are_exact() {
assert_eq!(ksp_worker_api::MAX_WORKER_ID_BYTES, 128);
assert_eq!(ksp_worker_api::MAX_WORKER_KIND_CODE_BYTES, 128);
assert_eq!(ksp_worker_api::ERROR_CODE_WORKER_ID_INVALID.domain(), "worker_api");
assert_eq!(ksp_worker_api::ERROR_CODE_WORKER_ID_INVALID.code(), "worker_id_invalid");
assert_eq!(ksp_worker_api::ERROR_CODE_WORKER_KIND_INVALID.domain(), "worker_api");
assert_eq!(ksp_worker_api::ERROR_CODE_WORKER_KIND_INVALID.code(), "worker_kind_invalid");
assert_eq!(ksp_worker_api::ERROR_CODE_WORKER_TRANSITION_INVALID.code(), "worker_transition_invalid");
assert_eq!(ksp_worker_api::ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED.code(), "worker_snapshot_sequence_exhausted");
return;
}

View File

@@ -0,0 +1,161 @@
// file: crates/ksp-worker-api/tests/release_completeness.rs
// version: 1
//! Exact frozen-surface and domain-firewall canaries for `ksp-worker-api`.
#[test]
fn pre_003_crate_root_export_inventory_is_exact() {
let crate_root = include_str!("../src/lib.rs");
let mut actual = std::vec::Vec::new();
for line in crate_root.lines() {
let trimmed = line.trim();
if trimmed.starts_with("pub use ") {
actual.push(trimmed);
}
}
actual.sort_unstable();
let mut expected = std::vec![
"pub use self::error::ERROR_CODE_WORKER_ID_INVALID;",
"pub use self::error::ERROR_CODE_WORKER_KIND_INVALID;",
"pub use self::error::ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED;",
"pub use self::error::ERROR_CODE_WORKER_TRANSITION_INVALID;",
"pub use self::identity::MAX_WORKER_ID_BYTES;",
"pub use self::identity::MAX_WORKER_KIND_CODE_BYTES;",
"pub use self::identity::WorkerId;",
"pub use self::identity::WorkerKindCode;",
"pub use self::lifecycle::WorkerActivity;",
"pub use self::lifecycle::WorkerHealth;",
"pub use self::lifecycle::WorkerLifecycle;",
"pub use self::lifecycle::WorkerState;",
"pub use self::snapshot::WorkerSnapshot;",
"pub use self::snapshot::WorkerSnapshotFuture;",
"pub use self::snapshot::WorkerSnapshotSequence;",
"pub use self::snapshot::WorkerSnapshotSource;",
"pub use self::stop::WorkerStopToken;",
"pub use ksp_core_lib::Error;",
"pub use ksp_core_lib::ErrorCode;",
"pub use ksp_core_lib::ErrorContext;",
"pub use ksp_core_lib::Result;",
];
expected.sort_unstable();
assert_eq!(actual, expected);
assert!(!crate_root.contains("pub mod "));
return;
}
#[test]
fn pre_003_production_module_inventory_is_exact() -> std::io::Result<()> {
let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let entries = match std::fs::read_dir(source_root) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut names = std::vec::Vec::new();
for entry in entries {
let entry = match entry {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let file_type = match entry.file_type() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if !file_type.is_file() {
continue;
}
let name = match entry.file_name().into_string() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => continue,
};
if name.ends_with(".rs") {
names.push(name);
}
}
names.sort_unstable();
assert_eq!(names, std::vec!["error.rs", "identity.rs", "lib.rs", "lifecycle.rs", "snapshot.rs", "stop.rs"]);
return std::result::Result::Ok(());
}
#[test]
fn pre_003_fixed_snapshot_shape_contains_only_common_worker_dimensions() {
let snapshot = include_str!("../src/snapshot.rs");
let struct_start = match snapshot.find("pub struct WorkerSnapshot {") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let after_start = &snapshot[struct_start..];
let struct_end = match after_start.find("\n}") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let definition = &after_start[..struct_end];
for required in ["id:", "kind:", "sequence:", "state:", "health:", "activity:"] {
assert!(definition.contains(required), "missing frozen Worker snapshot field: {required}");
}
for forbidden in ["String", "Vec<", "payload", "slot", "signature", "provider", "endpoint", "checkpoint", "backfill", "transaction"] {
assert!(!definition.contains(forbidden), "domain or arbitrary payload leaked into frozen Worker snapshot: {forbidden}");
}
let fields = definition.lines().filter(|line| {
let line = line.trim_start();
return line.starts_with("id:")
|| line.starts_with("kind:")
|| line.starts_with("sequence:")
|| line.starts_with("state:")
|| line.starts_with("health:")
|| line.starts_with("activity:");
});
assert_eq!(fields.count(), 6);
return;
}
#[test]
fn pre_003_surface_remains_runtime_neutral_job_independent_and_domain_free() {
let sources = [
include_str!("../src/error.rs"),
include_str!("../src/identity.rs"),
include_str!("../src/lib.rs"),
include_str!("../src/lifecycle.rs"),
include_str!("../src/snapshot.rs"),
include_str!("../src/stop.rs"),
];
for source in sources {
for forbidden in [
"Backfill",
"RawTransaction",
"WorkerHandle",
"checkpoint",
"endpoint",
"futures::",
"futures_util::",
"ksp_config_lib::",
"ksp_interface_lib::",
"ksp_job_api::",
"ksp_logging_lib::",
"ksp_offchain_transport_lib::",
"ksp_onchain_transport_lib::",
"ksp_store_api::",
"ksp_store_lib::",
"provider",
"reqwest::",
"serde::",
"serde_json::",
"slot",
"solana_",
"tauri::",
"tokio::",
"tonic::",
concat!("tracing", "::"),
] {
assert!(!source.contains(forbidden), "forbidden frozen Worker API concern detected: {forbidden}");
}
}
let snapshot = include_str!("../src/snapshot.rs");
assert!(snapshot.contains("pub trait WorkerSnapshotSource: std::marker::Send + std::marker::Sync"));
assert!(snapshot.contains("std::future::Future"));
assert!(!snapshot.contains("std::sync::mpsc"));
assert!(!snapshot.contains("VecDeque"));
let lifecycle = include_str!("../src/lifecycle.rs");
assert!(lifecycle.contains("#[derive(Eq, PartialEq)]\npub struct WorkerLifecycle"));
assert!(!lifecycle.contains("#[derive(Clone, Eq, PartialEq)]\npub struct WorkerLifecycle"));
return;
}

View File

@@ -0,0 +1,185 @@
// file: crates/ksp-worker-api/tests/security_hardening.rs
// version: 1
//! Adversarial lifecycle, stop and redaction canaries for the frozen Worker API.
const HOSTILE_MARKER: &str = "WORKER-IDENTITY-SECRET-CANARY";
const TEST_FAULT: ksp_worker_api::ErrorCode = ksp_worker_api::ErrorCode::new("worker_test", "fault");
fn running_lifecycle(id_value: &str) -> std::option::Option<ksp_worker_api::WorkerLifecycle> {
let id = match ksp_worker_api::WorkerId::new(id_value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let kind = match ksp_worker_api::WorkerKindCode::new("continuous_worker") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let mut lifecycle = ksp_worker_api::WorkerLifecycle::new(id, kind);
if lifecycle.start().is_err() {
return std::option::Option::None;
}
if lifecycle.mark_running().is_err() {
return std::option::Option::None;
}
return std::option::Option::Some(lifecycle);
}
fn assert_all_mutators_reject(lifecycle: &mut ksp_worker_api::WorkerLifecycle, terminal: ksp_worker_api::WorkerState) {
assert!(lifecycle.start().is_err());
assert!(lifecycle.mark_running().is_err());
assert!(lifecycle.mark_stopping().is_err());
assert!(lifecycle.mark_stopped().is_err());
assert!(lifecycle.fault(TEST_FAULT).is_err());
assert_eq!(lifecycle.state(), terminal);
return;
}
#[test]
fn pre_003_worker_identity_lifecycle_and_snapshot_debug_redact_hostile_identity() {
let lifecycle = running_lifecycle(HOSTILE_MARKER);
assert!(lifecycle.is_some());
let lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let id = lifecycle.id();
assert_eq!(std::format!("{id:?}"), "WorkerId(..)");
let lifecycle_debug = std::format!("{lifecycle:?}");
assert!(lifecycle_debug.contains("WorkerLifecycle"));
assert!(lifecycle_debug.contains("continuous_worker"));
assert!(lifecycle_debug.contains("Running"));
assert!(!lifecycle_debug.contains(HOSTILE_MARKER));
let snapshot = ksp_worker_api::WorkerSnapshot::new(
lifecycle.id().clone(),
lifecycle.kind().clone(),
ksp_worker_api::WorkerSnapshotSequence::initial(),
lifecycle.state(),
ksp_worker_api::WorkerHealth::Healthy,
ksp_worker_api::WorkerActivity::Active,
);
let snapshot_debug = std::format!("{snapshot:?}");
assert!(snapshot_debug.contains("WorkerSnapshot"));
assert!(snapshot_debug.contains("continuous_worker"));
assert!(snapshot_debug.contains("Healthy"));
assert!(snapshot_debug.contains("Active"));
assert!(!snapshot_debug.contains(HOSTILE_MARKER));
return;
}
#[test]
fn pre_003_identity_errors_never_echo_hostile_values() {
let hostile = "WORKER-SECRET/CANARY";
let rejected = ksp_worker_api::WorkerId::new(hostile);
assert!(rejected.is_err());
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), ksp_worker_api::ERROR_CODE_WORKER_ID_INVALID);
assert!(!std::format!("{error}").contains(hostile));
assert!(!std::format!("{error:?}").contains(hostile));
for context in error.context() {
assert!(!context.value().contains(hostile));
}
return;
}
#[test]
fn pre_003_stopped_and_faulted_are_immutable_under_all_public_mutators() {
let id = match ksp_worker_api::WorkerId::new("terminal-created") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let kind = match ksp_worker_api::WorkerKindCode::new("terminal_worker") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let mut stopped = ksp_worker_api::WorkerLifecycle::new(id, kind);
assert!(stopped.mark_stopped().is_ok());
assert_all_mutators_reject(&mut stopped, ksp_worker_api::WorkerState::Stopped);
let faulted = running_lifecycle("terminal-faulted");
assert!(faulted.is_some());
let mut faulted = match faulted {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(faulted.fault(TEST_FAULT).is_ok());
assert_all_mutators_reject(&mut faulted, ksp_worker_api::WorkerState::Faulted(TEST_FAULT));
return;
}
#[test]
fn pre_003_stop_fault_terminal_orders_are_first_valid_terminal_wins() {
let direct_fault = running_lifecycle("race-direct-fault");
assert!(direct_fault.is_some());
let mut direct_fault = match direct_fault {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(direct_fault.fault(TEST_FAULT).is_ok());
assert!(direct_fault.mark_stopping().is_err());
assert_eq!(direct_fault.state(), ksp_worker_api::WorkerState::Faulted(TEST_FAULT));
let stopping_fault = running_lifecycle("race-stopping-fault");
assert!(stopping_fault.is_some());
let mut stopping_fault = match stopping_fault {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(stopping_fault.mark_stopping().is_ok());
assert!(stopping_fault.fault(TEST_FAULT).is_ok());
assert!(stopping_fault.mark_stopped().is_err());
assert_eq!(stopping_fault.state(), ksp_worker_api::WorkerState::Faulted(TEST_FAULT));
let stopping_stopped = running_lifecycle("race-stopping-stopped");
assert!(stopping_stopped.is_some());
let mut stopping_stopped = match stopping_stopped {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(stopping_stopped.mark_stopping().is_ok());
assert!(stopping_stopped.mark_stopped().is_ok());
assert!(stopping_stopped.fault(TEST_FAULT).is_err());
assert_eq!(stopping_stopped.state(), ksp_worker_api::WorkerState::Stopped);
return;
}
#[test]
fn pre_003_stop_token_is_send_sync_cross_thread_and_cannot_reanimate_terminal_lifecycle() {
fn require_send_sync<T: std::marker::Send + std::marker::Sync>() {}
require_send_sync::<ksp_worker_api::WorkerStopToken>();
let lifecycle = running_lifecycle("cross-thread-stop");
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.fault(TEST_FAULT).is_ok());
let token = ksp_worker_api::WorkerStopToken::new();
let worker_token = token.clone();
let thread = std::thread::spawn(move || return worker_token.request_stop());
let first = match thread.join() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(first);
assert!(token.is_stop_requested());
assert!(!token.request_stop());
assert!(lifecycle.mark_stopping().is_err());
assert_eq!(lifecycle.state(), ksp_worker_api::WorkerState::Faulted(TEST_FAULT));
return;
}
#[test]
fn pre_003_public_worker_primitives_keep_promised_send_sync_contracts() {
fn require_send_sync<T: std::marker::Send + std::marker::Sync>() {}
require_send_sync::<ksp_worker_api::WorkerId>();
require_send_sync::<ksp_worker_api::WorkerKindCode>();
require_send_sync::<ksp_worker_api::WorkerState>();
require_send_sync::<ksp_worker_api::WorkerHealth>();
require_send_sync::<ksp_worker_api::WorkerActivity>();
require_send_sync::<ksp_worker_api::WorkerLifecycle>();
require_send_sync::<ksp_worker_api::WorkerSnapshotSequence>();
require_send_sync::<ksp_worker_api::WorkerSnapshot>();
require_send_sync::<ksp_worker_api::WorkerStopToken>();
return;
}

View File

@@ -0,0 +1,219 @@
// file: crates/ksp-worker-api/tests/snapshot_source.rs
// version: 1
//! External std-only latest-value source, object-safety and resynchronization canaries.
#[derive(Clone)]
struct TestSnapshotSource {
state: std::sync::Arc<TestState>,
}
struct TestState {
current: std::sync::Mutex<TestInner>,
}
struct TestInner {
snapshot: ksp_worker_api::WorkerSnapshot,
waiters: std::vec::Vec<std::task::Waker>,
}
impl TestSnapshotSource {
fn new(snapshot: ksp_worker_api::WorkerSnapshot) -> Self {
return Self {
state: std::sync::Arc::new(TestState { current: std::sync::Mutex::new(TestInner { snapshot, waiters: std::vec::Vec::new() }) }),
};
}
fn publish(&self, state: ksp_worker_api::WorkerState, health: ksp_worker_api::WorkerHealth, activity: ksp_worker_api::WorkerActivity) -> bool {
let mut current = match self.state.current.lock() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(poisoned) => poisoned.into_inner(),
};
let sequence = match current.snapshot.sequence().next() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return false,
};
let id = current.snapshot.id().clone();
let kind = current.snapshot.kind().clone();
current.snapshot = ksp_worker_api::WorkerSnapshot::new(id, kind, sequence, state, health, activity);
let waiters = std::mem::take(&mut current.waiters);
std::mem::drop(current);
for waiter in waiters {
waiter.wake();
}
return true;
}
}
struct TestWaitFuture<'a> {
source: &'a TestSnapshotSource,
observed: ksp_worker_api::WorkerSnapshotSequence,
}
impl std::future::Future for TestWaitFuture<'_> {
type Output = ksp_worker_api::WorkerSnapshot;
fn poll(self: std::pin::Pin<&mut Self>, context: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
let mut current = match self.source.state.current.lock() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(poisoned) => poisoned.into_inner(),
};
if current.snapshot.sequence().is_after(self.observed) {
return std::task::Poll::Ready(current.snapshot.clone());
}
if !current.waiters.iter().any(|registered| return registered.will_wake(context.waker())) {
current.waiters.push(context.waker().clone());
}
return std::task::Poll::Pending;
}
}
impl ksp_worker_api::WorkerSnapshotSource for TestSnapshotSource {
fn current(&self) -> ksp_worker_api::WorkerSnapshot {
let current = match self.state.current.lock() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(poisoned) => poisoned.into_inner(),
};
return current.snapshot.clone();
}
fn wait_for_change(&self, observed: ksp_worker_api::WorkerSnapshotSequence) -> ksp_worker_api::WorkerSnapshotFuture<'_> {
return std::boxed::Box::pin(TestWaitFuture { source: self, observed });
}
}
struct WakeProbe {
woken: std::sync::atomic::AtomicBool,
}
impl WakeProbe {
fn new() -> Self {
return Self { woken: std::sync::atomic::AtomicBool::new(false) };
}
fn is_woken(&self) -> bool {
return self.woken.load(std::sync::atomic::Ordering::Acquire);
}
}
impl std::task::Wake for WakeProbe {
fn wake(self: std::sync::Arc<Self>) {
self.woken.store(true, std::sync::atomic::Ordering::Release);
return;
}
}
fn poll_snapshot(future: &mut ksp_worker_api::WorkerSnapshotFuture<'_>, wake: &std::sync::Arc<WakeProbe>) -> std::task::Poll<ksp_worker_api::WorkerSnapshot> {
let waker = std::task::Waker::from(wake.clone());
let mut context = std::task::Context::from_waker(&waker);
return std::future::Future::poll(future.as_mut(), &mut context);
}
fn initial_snapshot() -> std::option::Option<ksp_worker_api::WorkerSnapshot> {
let id = match ksp_worker_api::WorkerId::new("external-source-001") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let kind = match ksp_worker_api::WorkerKindCode::new("external_test_worker") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
return std::option::Option::Some(ksp_worker_api::WorkerSnapshot::new(
id,
kind,
ksp_worker_api::WorkerSnapshotSequence::initial(),
ksp_worker_api::WorkerState::Running,
ksp_worker_api::WorkerHealth::Healthy,
ksp_worker_api::WorkerActivity::Idle,
));
}
#[test]
fn pre_003_snapshot_source_is_object_safe_send_sync_and_externally_implementable() {
fn require_send_sync<T: std::marker::Send + std::marker::Sync>() {}
require_send_sync::<TestSnapshotSource>();
let snapshot = initial_snapshot();
assert!(snapshot.is_some());
let source = match snapshot {
std::option::Option::Some(value) => TestSnapshotSource::new(value),
std::option::Option::None => return,
};
let object: &dyn ksp_worker_api::WorkerSnapshotSource = &source;
let current = object.current();
assert_eq!(current.sequence().value(), 0);
let mut wait = object.wait_for_change(current.sequence());
fn require_send<T: std::marker::Send>(_: &T) {}
require_send(&wait);
let wake = std::sync::Arc::new(WakeProbe::new());
assert!(matches!(poll_snapshot(&mut wait, &wake), std::task::Poll::Pending));
assert!(source.publish(ksp_worker_api::WorkerState::Running, ksp_worker_api::WorkerHealth::Healthy, ksp_worker_api::WorkerActivity::Active));
assert!(wake.is_woken());
let changed = poll_snapshot(&mut wait, &wake);
assert!(matches!(changed, std::task::Poll::Ready(_)));
return;
}
#[test]
fn pre_003_slow_and_independent_listeners_coalesce_to_latest_value() {
let snapshot = initial_snapshot();
assert!(snapshot.is_some());
let source = match snapshot {
std::option::Option::Some(value) => TestSnapshotSource::new(value),
std::option::Option::None => return,
};
let observed = ksp_worker_api::WorkerSnapshotSource::current(&source).sequence();
let mut slow = ksp_worker_api::WorkerSnapshotSource::wait_for_change(&source, observed);
let mut fast = ksp_worker_api::WorkerSnapshotSource::wait_for_change(&source, observed);
let slow_wake = std::sync::Arc::new(WakeProbe::new());
let fast_wake = std::sync::Arc::new(WakeProbe::new());
assert!(matches!(poll_snapshot(&mut slow, &slow_wake), std::task::Poll::Pending));
assert!(matches!(poll_snapshot(&mut fast, &fast_wake), std::task::Poll::Pending));
assert!(source.publish(ksp_worker_api::WorkerState::Running, ksp_worker_api::WorkerHealth::Degraded, ksp_worker_api::WorkerActivity::Active));
assert!(slow_wake.is_woken());
assert!(fast_wake.is_woken());
let fast_value = match poll_snapshot(&mut fast, &fast_wake) {
std::task::Poll::Ready(value) => value,
std::task::Poll::Pending => return,
};
assert_eq!(fast_value.sequence().value(), 1);
assert_eq!(fast_value.health(), ksp_worker_api::WorkerHealth::Degraded);
assert!(source.publish(ksp_worker_api::WorkerState::Running, ksp_worker_api::WorkerHealth::Healthy, ksp_worker_api::WorkerActivity::Idle));
let slow_value = match poll_snapshot(&mut slow, &slow_wake) {
std::task::Poll::Ready(value) => value,
std::task::Poll::Pending => return,
};
assert_eq!(slow_value.sequence().value(), 2);
assert_eq!(slow_value.health(), ksp_worker_api::WorkerHealth::Healthy);
assert_eq!(slow_value.activity(), ksp_worker_api::WorkerActivity::Idle);
return;
}
#[test]
fn pre_003_late_listener_resynchronizes_and_terminal_snapshot_remains_current() {
let snapshot = initial_snapshot();
assert!(snapshot.is_some());
let source = match snapshot {
std::option::Option::Some(value) => TestSnapshotSource::new(value),
std::option::Option::None => return,
};
assert!(source.publish(ksp_worker_api::WorkerState::Running, ksp_worker_api::WorkerHealth::Degraded, ksp_worker_api::WorkerActivity::Active));
assert!(source.publish(ksp_worker_api::WorkerState::Stopping, ksp_worker_api::WorkerHealth::Healthy, ksp_worker_api::WorkerActivity::Idle));
let current = ksp_worker_api::WorkerSnapshotSource::current(&source);
assert_eq!(current.sequence().value(), 2);
assert_eq!(current.state(), ksp_worker_api::WorkerState::Stopping);
let mut wait = ksp_worker_api::WorkerSnapshotSource::wait_for_change(&source, current.sequence());
let wake = std::sync::Arc::new(WakeProbe::new());
assert!(matches!(poll_snapshot(&mut wait, &wake), std::task::Poll::Pending));
assert!(source.publish(ksp_worker_api::WorkerState::Stopped, ksp_worker_api::WorkerHealth::Healthy, ksp_worker_api::WorkerActivity::Idle));
assert!(wake.is_woken());
let terminal = match poll_snapshot(&mut wait, &wake) {
std::task::Poll::Ready(value) => value,
std::task::Poll::Pending => return,
};
assert_eq!(terminal.sequence().value(), 3);
assert_eq!(terminal.state(), ksp_worker_api::WorkerState::Stopped);
assert!(terminal.state().is_terminal());
let retained = ksp_worker_api::WorkerSnapshotSource::current(&source);
assert_eq!(retained, terminal);
return;
}

View File

@@ -0,0 +1,64 @@
// file: crates/ksp-worker-api/unit_tests/identity.rs
// version: 1
#[test]
fn pre_002_identity_accepts_safe_codes_at_exact_bounds() {
let worker_id = crate::WorkerId::new("a".repeat(crate::MAX_WORKER_ID_BYTES));
let kind = crate::WorkerKindCode::new("continuous.raw:example-1");
assert!(worker_id.is_ok());
assert!(kind.is_ok());
let worker_id = match worker_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let kind = match kind {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(worker_id.as_str().len(), crate::MAX_WORKER_ID_BYTES);
assert_eq!(kind.as_str(), "continuous.raw:example-1");
return;
}
#[test]
fn pre_002_identity_rejects_empty_oversized_and_unsafe_values() {
for value in [
std::string::String::new(),
"a".repeat(crate::MAX_WORKER_ID_BYTES + 1),
"unsafe/value".to_string(),
"unsafe\\value".to_string(),
"space value".to_string(),
"unicode-é".to_string(),
] {
let rejected = crate::WorkerId::new(value);
assert!(rejected.is_err());
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => continue,
};
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_ID_INVALID);
assert_eq!(error.context().len(), 1);
assert_eq!(error.context()[0].key(), "field");
assert_eq!(error.context()[0].value(), "worker_id");
}
for value in [std::string::String::new(), "b".repeat(crate::MAX_WORKER_KIND_CODE_BYTES + 1), "worker kind".to_string()] {
let rejected = crate::WorkerKindCode::new(value);
assert!(rejected.is_err());
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => continue,
};
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_KIND_INVALID);
assert_eq!(error.context()[0].value(), "worker_kind");
}
return;
}
#[test]
fn pre_002_private_validator_uses_exact_safe_code_alphabet() {
assert!(super::valid_worker_code("AZaz09_.:-", 10));
for rejected in ["slash/value", "back\\slash", "space value", "line\nbreak", "é"] {
assert!(!super::valid_worker_code(rejected, crate::MAX_WORKER_ID_BYTES));
}
return;
}

View File

@@ -0,0 +1,151 @@
// file: crates/ksp-worker-api/unit_tests/lifecycle.rs
// version: 1
const TEST_FAULT: crate::ErrorCode = crate::ErrorCode::new("worker_test", "fault");
fn new_lifecycle() -> std::option::Option<crate::WorkerLifecycle> {
let id = match crate::WorkerId::new("worker-001") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let kind = match crate::WorkerKindCode::new("continuous_worker") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
return std::option::Option::Some(crate::WorkerLifecycle::new(id, kind));
}
#[test]
fn pre_002_lifecycle_accepts_every_planned_terminal_path() {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.mark_stopped().is_ok());
assert_eq!(lifecycle.state(), crate::WorkerState::Stopped);
assert!(lifecycle.state().is_terminal());
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.start().is_ok());
assert!(lifecycle.mark_running().is_ok());
assert!(lifecycle.mark_stopping().is_ok());
assert!(lifecycle.mark_stopped().is_ok());
assert_eq!(lifecycle.state(), crate::WorkerState::Stopped);
for fault_from_running in [false, true] {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.start().is_ok());
if fault_from_running {
assert!(lifecycle.mark_running().is_ok());
}
assert!(lifecycle.fault(TEST_FAULT).is_ok());
assert_eq!(lifecycle.state(), crate::WorkerState::Faulted(TEST_FAULT));
assert_eq!(lifecycle.state().fault_code(), std::option::Option::Some(TEST_FAULT));
}
return;
}
#[test]
fn pre_002_stopping_allows_normal_stop_or_fault() {
for terminal in [crate::WorkerState::Stopped, crate::WorkerState::Faulted(TEST_FAULT)] {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.start().is_ok());
assert!(lifecycle.mark_running().is_ok());
assert!(lifecycle.mark_stopping().is_ok());
let result = match terminal {
crate::WorkerState::Stopped => lifecycle.mark_stopped(),
crate::WorkerState::Faulted(code) => lifecycle.fault(code),
_ => return,
};
assert!(result.is_ok());
assert_eq!(lifecycle.state(), terminal);
}
return;
}
#[test]
fn pre_002_invalid_transition_preserves_source_state_and_reports_safe_context() {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let rejected = lifecycle.mark_running();
assert!(rejected.is_err());
assert_eq!(lifecycle.state(), crate::WorkerState::Created);
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_TRANSITION_INVALID);
assert_eq!(error.context().len(), 2);
assert_eq!(error.context()[0].key(), "source_state");
assert_eq!(error.context()[0].value(), "created");
assert_eq!(error.context()[1].key(), "target_state");
assert_eq!(error.context()[1].value(), "running");
return;
}
#[test]
fn pre_002_transition_matrix_is_exact() {
let states = [
crate::WorkerState::Created,
crate::WorkerState::Starting,
crate::WorkerState::Running,
crate::WorkerState::Stopping,
crate::WorkerState::Stopped,
crate::WorkerState::Faulted(TEST_FAULT),
];
for source in states {
for target in states {
let expected = matches!(
(source, target),
(crate::WorkerState::Created, crate::WorkerState::Starting)
| (crate::WorkerState::Created, crate::WorkerState::Stopped)
| (crate::WorkerState::Starting, crate::WorkerState::Running)
| (crate::WorkerState::Starting, crate::WorkerState::Stopping)
| (crate::WorkerState::Starting, crate::WorkerState::Faulted(_))
| (crate::WorkerState::Running, crate::WorkerState::Stopping)
| (crate::WorkerState::Running, crate::WorkerState::Faulted(_))
| (crate::WorkerState::Stopping, crate::WorkerState::Stopped)
| (crate::WorkerState::Stopping, crate::WorkerState::Faulted(_))
);
assert_eq!(super::allowed_transition(source, target), expected, "unexpected transition matrix cell: {source:?} -> {target:?}");
}
}
return;
}
#[test]
fn pre_002_health_activity_and_state_codes_are_stable() {
assert_eq!(crate::WorkerState::Created.code(), "created");
assert_eq!(crate::WorkerState::Starting.code(), "starting");
assert_eq!(crate::WorkerState::Running.code(), "running");
assert_eq!(crate::WorkerState::Stopping.code(), "stopping");
assert_eq!(crate::WorkerState::Stopped.code(), "stopped");
assert_eq!(crate::WorkerState::Faulted(TEST_FAULT).code(), "faulted");
assert_eq!(crate::WorkerHealth::Unknown.code(), "unknown");
assert_eq!(crate::WorkerHealth::Healthy.code(), "healthy");
assert_eq!(crate::WorkerHealth::Degraded.code(), "degraded");
assert_eq!(crate::WorkerHealth::Unhealthy.code(), "unhealthy");
assert_eq!(crate::WorkerActivity::Unknown.code(), "unknown");
assert_eq!(crate::WorkerActivity::Idle.code(), "idle");
assert_eq!(crate::WorkerActivity::Active.code(), "active");
return;
}

View File

@@ -0,0 +1,66 @@
// file: crates/ksp-worker-api/unit_tests/snapshot.rs
// version: 1
#[test]
fn pre_002_snapshot_sequence_advances_strictly_and_orders_positions() {
let first = crate::WorkerSnapshotSequence::initial();
assert_eq!(first.value(), 0);
let second = first.next();
assert!(second.is_ok());
let second = match second {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(second.value(), 1);
assert!(second.is_after(first));
assert!(!first.is_after(second));
assert!(!first.is_after(first));
return;
}
#[test]
fn pre_002_snapshot_sequence_exhaustion_is_explicit_and_non_wrapping() {
let exhausted = super::exhausted_snapshot_sequence();
let result = exhausted.next();
assert!(result.is_err());
let error = match result {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED);
assert_eq!(error.context().len(), 1);
assert_eq!(error.context()[0].key(), "sequence");
assert_eq!(error.context()[0].value(), u64::MAX.to_string());
return;
}
#[test]
fn pre_002_snapshot_preserves_exact_common_dimensions() {
let id = crate::WorkerId::new("worker-001");
let kind = crate::WorkerKindCode::new("example_worker");
assert!(id.is_ok());
assert!(kind.is_ok());
let id = match id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let kind = match kind {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let snapshot = crate::WorkerSnapshot::new(
id,
kind,
crate::WorkerSnapshotSequence::initial(),
crate::WorkerState::Running,
crate::WorkerHealth::Healthy,
crate::WorkerActivity::Active,
);
assert_eq!(snapshot.id().as_str(), "worker-001");
assert_eq!(snapshot.kind().as_str(), "example_worker");
assert_eq!(snapshot.sequence().value(), 0);
assert_eq!(snapshot.state(), crate::WorkerState::Running);
assert_eq!(snapshot.health(), crate::WorkerHealth::Healthy);
assert_eq!(snapshot.activity(), crate::WorkerActivity::Active);
return;
}

View File

@@ -0,0 +1,22 @@
// file: crates/ksp-worker-api/unit_tests/stop.rs
// version: 1
#[test]
fn pre_002_stop_token_is_shared_and_idempotent() {
let token = crate::WorkerStopToken::new();
let clone = token.clone();
assert!(!token.is_stop_requested());
assert!(clone.request_stop());
assert!(token.is_stop_requested());
assert!(!token.request_stop());
assert!(!clone.request_stop());
return;
}
#[test]
fn pre_002_default_stop_token_starts_without_stop_request() {
let token = crate::WorkerStopToken::default();
assert!(!token.is_stop_requested());
assert_eq!(std::format!("{token:?}"), "WorkerStopToken { stop_requested: false }");
return;
}

View File

@@ -0,0 +1,95 @@
<!-- file: deltas/0.3.9/pre.001-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.001-fix.001` — forecast souple éditable du plan
## Base requise
```text
khadhroony-solana-project v0.3.8
0.3.9-pre.001 appliquée
workspace.package.version = 0.3.8
```
## Type de livraison
```text
ksp-doc-0.3.9-pre.001-fix.001.zip
```
## Objectif
Corriger uniquement la forme de `docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md` sous `## 17. Prévision souple recalibrée` afin de retrouver le format éditable des plans KSP précédents : une sous-section `### pre.NNN` par prerelease et des correctifs insérables sous la tranche concernée via `#### pre.NNN-fix.MMM`.
Le correctif ne modifie ni larchitecture Worker API, ni le découpage technique retenu, ni les budgets, ni les sorties attendues, ni lordre de clôture de `0.3.9`.
## Modification documentaire
Le tableau de sizing de la section 17 est remplacé par des sous-paragraphes :
```text
### pre.001
#### pre.001-fix.001
### pre.002
...
### pre.009
### rel.001
```
Chaque tranche conserve son budget et son objectif. `pre.001-fix.001` est documenté directement sous `pre.001` afin que les futurs correctifs puissent suivre la même hiérarchie sans transformer le forecast en tableau.
Le header documentaire du plan passe de `version: 1` à `version: 2` parce que ce fichier subit une modification réelle.
## Validation opérateur déjà fournie sur `pre.001`
Après application de `pre.001`, lopérateur a fourni un gate comprenant notamment :
```text
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-app-store-desk
cargo tree --duplicates
```
Résultats communiqués : audits Rust propres, audit Markdown propre avec `321 table(s), 728 file(s)`, `cargo check` PASS, Clippy PASS et toutes les suites `ksp-app-store-desk` exécutées PASS. Cette preuve opérateur complète la livraison précédente ; elle nest pas présentée comme une exécution locale du sandbox dassemblage.
## Version Cargo
Correctif strictement documentaire :
```text
workspace.package.version reste 0.3.8
```
Aucun `Cargo.toml`, code Rust, runtime, build ou configuration nest modifié.
## Fichiers ajoutés
```text
deltas/0.3.9/pre.001-fix.001.md
```
## Fichiers modifiés
```text
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
```
## Fichiers supprimés
```text
aucun
```
## Hors périmètre
```text
aucune modification de docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
aucune modification de deltas/0.3.9/pre.001.md
aucune crate ksp-worker-api créée
aucun audit RAW provider commencé
aucune modification CHANGELOG/ROADMAP
```

View File

@@ -0,0 +1,116 @@
<!-- file: deltas/0.3.9/pre.001-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.001-fix.002` — correction de la synchronisation Cargo
## Base requise
```text
khadhroony-solana-project v0.3.8
0.3.9-pre.001 appliquée
0.3.9-pre.001-fix.001 appliquée
workspace.package.version = 0.3.8
```
## Type de livraison
```text
ksp-general-0.3.9-pre.001-fix.002.zip
```
## Objectif
Corriger lanomalie de versioning découverte après `pre.001-fix.001`. La livraison initiale `pre.001` avait conservé `workspace.package.version = 0.3.8` en suivant lexception explicitement écrite dans le prompt 028. Cette exception est incompatible avec la règle normative `VER-ID-009` de `docs/rules/VERSION_WORKFLOW.md`, qui impose à toute prerelease non-fix de synchroniser la version Cargo, même lorsquelle est uniquement documentaire.
Le présent correctif ne réécrit pas les deltas historiques déjà livrés. Il corrige létat courant et les documents durables qui décrivent cet état.
## Décision de versioning
La livraison est un fix de `pre.001`. Puisquelle modifie `Cargo.toml`, `VER-ID-007` impose la synchronisation de `workspace.package.version` avec lidentifiant technique du fix et `VER-ID-010` impose la représentation SemVer pointée :
```text
identifiant livraison = 0.3.9-pre.001-fix.002
version Cargo = 0.3.9-pre.1.fix.2
```
Le passage direct à `0.3.9-pre.2` nest pas retenu dans ce fix : `VER-ID-006` réserve un nouveau numéro de prerelease à une nouvelle tranche planifiée. `0.3.9-pre.2` reste donc réservé à `pre.002`, qui porte la création des contrats `ksp-worker-api`.
## Fichiers modifiés
```text
Cargo.toml
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Modifications :
```text
Cargo.toml header : 475 -> 476
workspace.package.version : 0.3.8 -> 0.3.9-pre.1.fix.2
plan header : 2 -> 3
validation header : 1 -> 2
```
Le plan reçoit `#### pre.001-fix.002` et sa section de versioning est corrigée. La validation distingue désormais létat historique erroné de `pre.001` de létat courant corrigé.
## Fichiers ajoutés
```text
deltas/0.3.9/pre.001-fix.002.md
```
## Fichiers supprimés
```text
aucun
```
## Historique préservé
Les fichiers suivants ne sont volontairement pas modifiés :
```text
deltas/0.3.9/pre.001.md
deltas/0.3.9/pre.001-fix.001.md
prompts/028-V0_3_9_START_PROMPT.md
```
Les deux premiers décrivent les livraisons réellement produites. Le prompt reste lentrée historique de la session ; le présent delta trace explicitement que son exception de versioning était en conflit avec `VER-ID-009`.
## Validation opérateur disponible avant ce fix
Lopérateur a exécuté après `pre.001-fix.001` :
```text
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
```
Résultat fourni : audits Rust propres et audit Markdown propre avec `320 table(s), 729 file(s)`.
## Validations dassemblage du présent fix
Exécuté sur larbre assemblé après application du correctif :
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
Markdown table audit: clean (320 table(s), 730 file(s))
```
Le binaire `cargo` nest pas disponible dans lenvironnement dassemblage ; aucun `cargo fmt/check/clippy/test/tree` du présent fix nest donc déclaré PASS localement. Le changement Cargo est limité au header documentaire du manifeste et à `workspace.package.version`.
## Hors périmètre
```text
aucune création de ksp-worker-api
aucun changement fonctionnel Worker API
aucun audit RAW provider commencé
aucune modification CHANGELOG/ROADMAP
aucun changement Transport/Config/Store
```

187
deltas/0.3.9/pre.001.md Normal file
View File

@@ -0,0 +1,187 @@
<!-- file: deltas/0.3.9/pre.001.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.001` — audit, architecture et sizing Worker API
## Base requise
```text
archive stable fournie : khadhroony-solana-project-v0.3.8.zip
workspace.package.version = 0.3.8
delta stable = deltas/0.3.8/rel.001.md
prompt = prompts/028-V0_3_9_START_PROMPT.md
```
## Type de livraison
```text
ksp-doc-0.3.9-pre.001.zip
```
La livraison est strictement documentaire et contient uniquement les deux nouveaux documents `docs/` et le présent delta.
## Objectif
Ouvrir `0.3.9` par le gate documentaire imposé : vérification complète des archives/règles, audit de `ksp-job-api` comme référence de propriétés, décision de larchitecture générique `ksp-worker-api`, threat map, tests et sizing, **sans créer la crate Worker ni commencer laudit fonctionnel RAW provider**.
## Vérification des archives
```text
KSP SHA-256 = b3a34a3dfd56fac1eef3d23f13631ff001338b612834488038cf259112c63624
KSP entries = 1809
KSP unzip -t = PASS
kbot3 SHA-256 = ee47643b9f8b582ee8db97b2381ec107e45aef8c009fee44757531514615d318
kbot3 entries = 2501
kbot3 unzip -t = PASS
```
Aucune entrée absolue/traversal/symlink et aucun `target`, `node_modules`, `.git`, `Cargo.lock` ou `.env` livré na été détecté.
La source de code reste exclusivement KSP `v0.3.8`. kbot3 na pas été utilisé pour concevoir Worker API ; son futur usage est réservé à la référence **fonctionnelle historique** lors de laudit RAW après freeze.
## Audits baseline exécutés avant modification
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
Markdown table audit: clean (316 table(s), 725 file(s))
```
Le binaire `cargo` est absent de lenvironnement dassemblage. Les commandes Cargo de baseline et `cargo tree -p ksp-job-api` sont donc **non exécutées et non PASS** localement. Le manifest de `ksp-job-api` a néanmoins été audité statiquement : une seule dépendance normale `ksp-core-lib`, aucune feature/dev/build dependency.
## Audits post-modification
Après ajout du plan, de la validation et du présent delta :
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
Markdown table audit: clean (321 table(s), 728 file(s))
```
Le diff exact contre les bytes du ZIP stable contient uniquement les trois fichiers ajoutés de cette livraison ; aucun fichier de la base nest modifié ou supprimé. Le cache Python créé par laudit local est un artefact ignoré et est supprimé avant packaging.
## Décisions Worker API
Surface V1 prévue :
```text
WorkerId / WorkerKindCode, bornés à 128 octets
WorkerState = Created / Starting / Running / Stopping / Stopped / Faulted(ErrorCode)
WorkerHealth = Unknown / Healthy / Degraded / Unhealthy
WorkerActivity = Unknown / Idle / Active
WorkerLifecycle producteur-owned, terminal immutable
WorkerStopToken coopératif partagé/idempotent
WorkerSnapshotSequence monotone checked
WorkerSnapshot fixe sans payload arbitraire
WorkerSnapshotSource latest-value, Send + Sync et object-safe
Core Error/ErrorCode/ErrorContext/Result
```
Dependency map exact décidé :
```text
ksp-worker-api -> ksp-core-lib uniquement
```
Explicitement exclus : Job API, Interface, Config, Logging, Transport, Store, Tokio, futures, serde, Tauri, provider SDK et toute notion RawTransaction/slot/provider/endpoint/replay/backfill.
Le snapshot commun est volontairement fixe plutôt que générique : le control plane obtient un état uniforme et aucun payload/string libre ne peut entrer dans lAPI Worker. Les métriques métier restent au worker concret.
Restart/retry/process control ne font pas partie de Worker API V1. Un lifecycle/source terminal ne redevient jamais actif et nest jamais rebinding vers une nouvelle instance ; la recréation appartient au caller/futur control plane.
## Recalibrage de la trajectoire
Le noyau API reste en deux tranches de code :
```text
pre.002 contrats
pre.003 hardening + external implementation + freeze fonctionnelle
```
Laudit RAW initialement regroupé en une tranche est scindé afin de respecter le budget KSP :
```text
pre.004 Solana standard + inventaire KSP
pre.005 Helius/Yellowstone/providers + kbot3 fonctionnel historique
pre.006 synthèse multi-source + handoff 0.3.10/0.3.12
pre.007 gate technique final
pre.008 réconciliation documentaire
pre.009 préparation publication
rel.001 publication stable
```
Lowner prévu du document daudit final est `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`, créé seulement après freeze de Worker API.
## Fichiers ajoutés
```text
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
deltas/0.3.9/pre.001.md
```
## Fichiers modifiés
```text
aucun
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Aucun code/build/runtime/config nest modifié. Conformément à lautorisation explicite du prompt 028 pour ce premier gate doc-only :
```text
workspace.package.version reste 0.3.8
```
## Non inclus
```text
aucune crate ksp-worker-api créée
aucun Rust modifié
aucun Cargo.toml modifié
aucun worker RawTransaction
aucune modification Transport/Config/Store
aucun endpoint/profil Helius
aucun nouveau secret
aucun audit provider courant déclaré réalisé
aucune analyse fonctionnelle kbot3 encore réalisée
aucune modification CHANGELOG/ROADMAP
```
## Validations non exécutées
```text
cargo fmt/check/clippy/test/tree : cargo absent du sandbox
sources externes RAW : réservées après freeze Worker API
smoke réseau/provider : hors pre.001
```
## Questions différées
```text
WorkerHandle/start/join générique
registry/factory/restart generation
reconfiguration live
remote protocol/IPC
serialization commune
metrics métier universelles
```
Elles ne sont pas nécessaires au contrat générique V1 et ne doivent pas élargir `pre.002` sans preuve.

View File

@@ -0,0 +1,114 @@
<!-- file: deltas/0.3.9/pre.002-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.002-fix.001` — conformité crate-root de `ksp-worker-api`
## Base requise
```text
0.3.9-pre.002
workspace.package.version = 0.3.9-pre.2
```
Le gate opérateur de `pre.002` est vert : format et audits propres, `cargo check --workspace` PASS, Clippy workspace `--all-targets --all-features -- -D warnings` PASS, `cargo test -p ksp-worker-api` PASS avec 17 tests, et les deux `cargo tree` ciblés confirment la frontière Core-only.
## Défaut corrigé
La nouvelle crate respectait les réexports crate-root dans ses signatures inter-types, mais les modules propriétaires implémentaient encore les types partagés par nom local nu, par exemple :
```rust
impl WorkerId {
```
`RUST-IMPORT-009` impose qu'un élément `pub` ou `pub(crate)` partagé soit appelé via `crate::Item`, y compris depuis son module de déclaration lorsque le contrat est crate-wide. La forme canonique est donc :
```rust
impl crate::WorkerId {
```
Le même défaut existait pour les autres targets d'`impl` de `identity.rs`, `lifecycle.rs`, `snapshot.rs` et `stop.rs`.
## Corrections Rust
Les targets sont normalisées sans modifier la surface ni le comportement :
```text
identity.rs
WorkerId
Debug for WorkerId
WorkerKindCode
Debug for WorkerKindCode
lifecycle.rs
WorkerState
WorkerHealth
WorkerActivity
WorkerLifecycle
Debug for WorkerLifecycle
snapshot.rs
WorkerSnapshotSequence
WorkerSnapshot
Debug for WorkerSnapshot
stop.rs
WorkerStopToken
Default for WorkerStopToken
Debug for WorkerStopToken
```
Le helper `exhausted_snapshot_sequence()` construit aussi explicitement `crate::WorkerSnapshotSequence(u64::MAX)`.
Une troisième canarie `dependency_boundary` vérifie les références crate-root attendues dans les quatre modules propriétaires afin d'éviter une régression locale lors de `pre.003`.
## Version
Ce correctif modifie du code Rust :
```text
workspace.package.version = 0.3.9-pre.2.fix.1
```
Le header du `Cargo.toml` racine passe de `477` à `478`.
## Hors scope
Aucun changement de lifecycle, identity bounds, snapshot shape, stop semantics, ErrorCode, dépendance, feature ou API publique. Aucun travail `pre.003` n'est anticipé.
L'auditeur Python global n'est pas étendu dans ce fix : une généralisation immédiate ferait remonter la dette historique de crates antérieures et élargirait artificiellement cette correction locale. La canarie Worker ferme le défaut nouveau sans masquer cette distinction.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-api/src/identity.rs
crates/ksp-worker-api/src/lifecycle.rs
crates/ksp-worker-api/src/snapshot.rs
crates/ksp-worker-api/src/stop.rs
crates/ksp-worker-api/tests/dependency_boundary.rs
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
## Fichier ajouté
```text
deltas/0.3.9/pre.002-fix.001.md
```
## Fichiers supprimés
Aucun.
## Gate opérateur demandé
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-api
cargo tree -p ksp-worker-api --edges normal
cargo tree -p ksp-worker-api -e features
```

View File

@@ -0,0 +1,96 @@
<!-- file: deltas/0.3.9/pre.002-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.002-fix.002` — constructeur privé de `WorkerSnapshotSequence`
## Base requise
```text
0.3.9-pre.002-fix.001
workspace.package.version = 0.3.9-pre.2.fix.1
```
Le gate opérateur de `pre.002-fix.001` confirme les audits Rust/Markdown et `cargo check --workspace`, mais Clippy `--all-targets --all-features -- -D warnings` et `cargo test -p ksp-worker-api` échouent sur `E0423` dans `snapshot.rs`. Les deux `cargo tree` ciblés restent conformes à la frontière Core-only.
## Cause exacte
`WorkerSnapshotSequence` est un tuple struct public dont le champ `u64` reste volontairement privé. Le type est réexporté au crate-root, mais ce re-export ne rend pas le constructeur tuple visible :
```rust
return crate::WorkerSnapshotSequence(u64::MAX);
```
échoue donc lors de la compilation des targets de test.
Le défaut ne remet pas en cause `RUST-IMPORT-009` pour les targets d'`impl` : `impl crate::WorkerSnapshotSequence` reste la forme correcte. Il faut seulement confiner l'accès au champ privé dans le module propriétaire.
## Correctif
L'impl propriétaire reçoit un constructeur associé privé et uniquement compilé pour les tests :
```rust
#[cfg(test)]
const fn exhausted_for_test() -> Self {
return Self(u64::MAX);
}
```
Le helper privé conserve la référence crate-root au type partagé :
```rust
return crate::WorkerSnapshotSequence::exhausted_for_test();
```
Ainsi :
- aucun champ ne devient `pub` ou `pub(crate)` ;
- aucun constructeur arbitraire n'entre dans l'API publique ;
- les targets d'`impl` restent sous `crate::Type` ;
- l'initialisation du champ privé reste confinée dans l'impl situé dans le module propriétaire ;
- le helper d'overflow reste strictement `#[cfg(test)]`.
La canarie `dependency_boundary` de `pre.002-fix.001` cesse d'exiger le constructeur tuple invalide et une nouvelle canarie vérifie explicitement ce pont privé.
## Version
Ce fix modifie du Rust :
```text
workspace.package.version = 0.3.9-pre.2.fix.2
delivery = 0.3.9-pre.002-fix.002
```
Le header du `Cargo.toml` racine passe de `478` à `479`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-api/src/snapshot.rs
crates/ksp-worker-api/tests/dependency_boundary.rs
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
## Fichier ajouté
```text
deltas/0.3.9/pre.002-fix.002.md
```
## Hors scope
Aucun changement de surface publique, lifecycle, stop semantics, snapshot shape, dépendance, feature, runtime ou travail `pre.003`.
## Gate opérateur demandé
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-api
cargo tree -p ksp-worker-api --edges normal
cargo tree -p ksp-worker-api -e features
```

202
deltas/0.3.9/pre.002.md Normal file
View File

@@ -0,0 +1,202 @@
<!-- file: deltas/0.3.9/pre.002.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.002` — noyau `ksp-worker-api`
## Base requise
```text
0.3.9-pre.001-fix.002
workspace.package.version = 0.3.9-pre.1.fix.2
```
Le gate opérateur de la base a exécuté `cargo clean`, format, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace `--all-targets --all-features -- -D warnings`, tests workspace `--all-targets --all-features`, les cinq builds Tauri `deb,rpm` et `cargo tree --duplicates`. Les commandes Cargo ont terminé sans échec signalé ; les tests live/smoke explicitement opt-in sont restés `ignored` comme prévu.
## Objectif
Créer uniquement le noyau passif et runtime-neutral de `ksp-worker-api` décidé en `pre.001`, sans commencer le worker RAW Transaction ni anticiper le hardening de `pre.003`.
## Version
Cette tranche est une nouvelle prerelease non-fix :
```text
workspace.package.version = 0.3.9-pre.2
```
Le `Cargo.toml` racine passe du header `476` au header `477` et ajoute `crates/ksp-worker-api` aux membres du workspace.
## Surface ajoutée
```text
WorkerId
WorkerKindCode
MAX_WORKER_ID_BYTES = 128
MAX_WORKER_KIND_CODE_BYTES = 128
WorkerState
WorkerHealth
WorkerActivity
WorkerLifecycle
WorkerStopToken
WorkerSnapshotSequence
WorkerSnapshot
WorkerSnapshotFuture<'a>
WorkerSnapshotSource
ERROR_CODE_WORKER_ID_INVALID
ERROR_CODE_WORKER_KIND_INVALID
ERROR_CODE_WORKER_TRANSITION_INVALID
ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED
Error / ErrorCode / ErrorContext / Result
```
Tous les exports sont accessibles depuis le crate-root ; les modules restent privés.
## Lifecycle matérialisé
```text
Created -> Starting
Created -> Stopped
Starting -> Running
Starting -> Stopping
Starting -> Faulted(code)
Running -> Stopping
Running -> Faulted(code)
Stopping -> Stopped
Stopping -> Faulted(code)
```
`Stopped` et `Faulted(ErrorCode)` sont terminaux. `Faulted` conserve uniquement un `ErrorCode` KSP statique. Une transition invalide retourne `ERROR_CODE_WORKER_TRANSITION_INVALID` avec les seuls codes source/cible et laisse l'état inchangé.
`WorkerHealth` (`Unknown`, `Healthy`, `Degraded`, `Unhealthy`) et `WorkerActivity` (`Unknown`, `Idle`, `Active`) restent orthogonaux au lifecycle ; aucune completion, progression, ETA, slot, provider ou métrique métier n'entre dans cette API.
## Snapshot et stop
`WorkerSnapshot` est fixe et contient exactement les dimensions communes décidées : identité, kind, séquence, lifecycle, health et activity. Aucun payload arbitraire ou champ diagnostique libre n'est admis.
`WorkerSnapshotSequence` part de zéro, avance par `checked_add(1)`, expose `is_after` et retourne une erreur explicite en cas d'épuisement.
`WorkerSnapshotSource` expose `current()` et `wait_for_change(...)` via un future `std` boxed ; il n'impose aucun runtime concret. Les preuves object-safety, implémentation externe et listeners lents restent dans `pre.003`.
`WorkerStopToken` partage une intention atomique cloneable ; seule la première demande retourne `true`. Le token ne modifie pas directement le lifecycle et ne possède ni join, ni timeout, ni thread/task.
## Firewalls
Dépendance normale exacte :
```text
ksp-worker-api -> ksp-core-lib
```
Absents du manifest et des sources de production :
```text
features / dev-dependencies / build-dependencies
ksp-job-api
ksp-interface-lib
ksp-config-lib
ksp-logging-lib
ksp-onchain-transport-lib
ksp-store-api / ksp-store-lib
tokio / futures / serde / tracing / Tauri
Solana / provider / RawTransaction / backfill / checkpoint
```
## Tests ajoutés
Treize tests unitaires :
- identité : admission aux bornes, rejets hostiles, alphabet exact ;
- lifecycle : chemins terminaux, arrêt/fault depuis Stopping, conservation sur transition invalide, matrice exacte, codes state/health/activity ;
- stop : partage/idempotence et état par défaut ;
- snapshot : séquence monotone, exhaustion explicite et projection exacte des six dimensions communes.
Quatre canaries d'intégration :
- deux canaries dependency boundary : manifest Core-only et absence de chemins runtime/domain interdits dans les sources de production ;
- deux canaries public API : consommation crate-root du lifecycle/stop/snapshot et stabilité des bornes/error codes.
Le hardening adversarial complet, l'object-safety, l'implémentation externe std-only, les listeners lents/indépendants, le late-listener et les inventaires exacts restent réservés à `pre.003`.
## Fichiers ajoutés
```text
crates/ksp-worker-api/Cargo.toml
crates/ksp-worker-api/src/error.rs
crates/ksp-worker-api/src/identity.rs
crates/ksp-worker-api/src/lib.rs
crates/ksp-worker-api/src/lifecycle.rs
crates/ksp-worker-api/src/snapshot.rs
crates/ksp-worker-api/src/stop.rs
crates/ksp-worker-api/unit_tests/identity.rs
crates/ksp-worker-api/unit_tests/lifecycle.rs
crates/ksp-worker-api/unit_tests/snapshot.rs
crates/ksp-worker-api/unit_tests/stop.rs
crates/ksp-worker-api/tests/dependency_boundary.rs
crates/ksp-worker-api/tests/public_api.rs
deltas/0.3.9/pre.002.md
```
## Fichiers modifiés
```text
Cargo.toml
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
## Fichiers supprimés
Aucun.
## Documentation volontairement différée
`crates/ksp-worker-api/README.md` et `USAGE.md` ne sont pas créés dans cette tranche de code. Le plan réserve leur réconciliation version-neutral à `pre.008`, après freeze fonctionnelle et hardening de la surface publique.
## Validations exécutées dans l'environnement d'assemblage
```text
python3 scripts/audit_rust_workspace_rules.py
-> General Rust rule audit: clean
-> Rust export completeness audit: 0 candidate(s)
-> KSP workspace Rust rule audit: clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
-> Markdown table audit: clean (320 table(s), 731 file(s))
```
Contrôles statiques complémentaires :
```text
workspace member ksp-worker-api présent
workspace.package.version = 0.3.9-pre.2
manifest Worker API = Core-only, aucune feature/dev/build dependency
6 sources de production prévues présentes
4 fichiers unit_tests présents
2 suites d'intégration présentes
aucun pub mod dans src/lib.rs
aucun runtime concret ajouté
```
L'environnement d'assemblage ne fournit ni `cargo`, ni `rustc`, ni `rustfmt`. Aucun gate Cargo de `pre.002` n'est donc déclaré PASS localement.
## Gate opérateur demandé
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-api
cargo tree -p ksp-worker-api --edges normal
cargo tree -p ksp-worker-api -e features
```
## Questions ouvertes
Aucune question ne bloque `pre.003` après un gate opérateur vert. Les questions volontairement différées (`WorkerHandle`, registry/factory, restart generation, remote protocol, serialization, metrics communes) restent hors scope conformément au plan `pre.001`.

145
deltas/0.3.9/pre.003.md Normal file
View File

@@ -0,0 +1,145 @@
<!-- file: deltas/0.3.9/pre.003.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.003` — hardening et freeze fonctionnelle de `ksp-worker-api`
## Base requise
```text
0.3.9-pre.002-fix.002
workspace.package.version = 0.3.9-pre.2.fix.2
```
Le gate opérateur de cette base est fermé : audits Rust/Markdown propres, `cargo check --workspace` PASS, Clippy `--all-targets --all-features -- -D warnings` PASS, `cargo test -p ksp-worker-api` PASS avec 13 tests unitaires, 4 tests dependency-boundary et 2 tests public API, puis les deux `cargo tree` ciblés confirment la frontière `ksp-worker-api -> ksp-core-lib`.
## Objet
Fermer le contrat Worker générique avant l'audit RAW Transaction. La production API matérialisée en `pre.002` n'est pas élargie : la tranche ajoute les preuves adversariales, external-consumer et release-completeness prévues par le plan, ainsi que les documents durables d'une bibliothèque fonctionnellement complète.
## Hardening lifecycle et redaction
`tests/security_hardening.rs` couvre :
```text
Stopped et Faulted immuables sous tous les mutateurs publics
Running -> Faulted puis stop lifecycle rejeté
Running -> Stopping -> Faulted puis Stopped rejeté
Running -> Stopping -> Stopped puis Faulted rejeté
WorkerId hostile redacted dans Debug
WorkerLifecycle et WorkerSnapshot sans fuite de l'identité hostile
valeur d'identité invalide jamais recopiée dans Error/Debug/context
WorkerStopToken Send + Sync, partagé et visible cross-thread
stop demandé après terminal sans réanimation du lifecycle
Send + Sync des primitives publiques promises
```
Le premier état terminal validement enregistré reste donc autoritaire ; le token de stop transporte seulement une intention coopérative.
## Source latest-value externe std-only
`tests/snapshot_source.rs` implémente `WorkerSnapshotSource` depuis le contexte d'un vrai test d'intégration, sans dépendance de test supplémentaire et avec uniquement `std` :
```text
Arc
Mutex
Waker
Future
Pin
AtomicBool
```
Cette implémentation prouve par compilation l'object-safety via `&dyn WorkerSnapshotSource`, le contrat `Send + Sync` et le future `Send`. Les scénarios couvrent :
```text
deux listeners indépendants enregistrés sur la même séquence
listener rapide reçoit sequence 1
listener lent saute directement à sequence 2 après coalescing
late listener commence par current() puis attend sequence > observed
publication terminale réveille le listener
snapshot Stopped terminal reste lisible par current()
```
Aucune queue d'événements ni runtime async concret n'entre dans `ksp-worker-api`.
## Freeze de surface
`tests/release_completeness.rs` verrouille :
```text
six modules de production exacts : error, identity, lib, lifecycle, snapshot, stop
inventaire exact des vingt-et-un reexports crate-root
aucun pub mod
WorkerSnapshot limité à id/kind/sequence/state/health/activity
aucun payload/String libre dans le snapshot commun
aucun WorkerHandle générique
aucun Job/checkpoint/backfill/RawTransaction/provider/endpoint/slot
aucun Config/Interface/Logging/Transport/Store
aucun Tokio/Futures crate/serde/Tauri/Solana/provider SDK
```
La dependency-boundary existante reste inchangée et continue d'imposer `ksp-core-lib` comme unique dépendance normale.
## Documentation durable
La crate reçoit :
```text
crates/ksp-worker-api/README.md
crates/ksp-worker-api/USAGE.md
```
Le README décrit responsabilités, lifecycle, latest-value, stop, restart ownership et firewall. `USAGE.md` reste version-neutral et fournit les exemples de consommation crate-root pour identity, lifecycle, health/activity, stop, snapshot, `WorkerSnapshotSource` et sequence. La réconciliation documentaire finale de `pre.008` pourra uniquement ajuster ces documents aux preuves finales sans les transformer en journal de release.
## Version
```text
workspace.package.version = 0.3.9-pre.3
delivery = 0.3.9-pre.003
```
Le header du `Cargo.toml` racine passe de `479` à `480`.
## Fichiers modifiés
```text
Cargo.toml
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
## Fichiers ajoutés
```text
crates/ksp-worker-api/README.md
crates/ksp-worker-api/USAGE.md
crates/ksp-worker-api/tests/release_completeness.rs
crates/ksp-worker-api/tests/security_hardening.rs
crates/ksp-worker-api/tests/snapshot_source.rs
deltas/0.3.9/pre.003.md
```
## Audits d'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (320 table(s), 736 file(s))
```
Cargo/rustc/rustfmt ne sont pas disponibles dans l'environnement d'assemblage ; aucun nouveau gate Rust n'est déclaré PASS ici.
## Gate opérateur demandé
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-api
cargo tree -p ksp-worker-api --edges normal
cargo tree -p ksp-worker-api -e features
```
La freeze fonctionnelle de Worker API devient effective uniquement après fermeture de ce gate. `pre.004` peut alors commencer l'audit RAW standard/KSP sans rouvrir l'API pour des besoins Solana-specific.

60
deltas/0.3.9/pre.004.md Normal file
View File

@@ -0,0 +1,60 @@
<!-- file: deltas/0.3.9/pre.004.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.004`
## 1. Objet
Fermer l'audit RAW A après freeze effective de `ksp-worker-api` : Solana standard HTTP/WS, inventaire KSP Transport/Store/Config, taxonomie de capabilities et premiers gaps préparatoires pour `0.3.10`/`0.3.12`.
Aucun runtime, DTO Transport, Store model, Config profile ou Worker API n'est modifié.
## 2. Base et version
Base opérateur : `0.3.9-pre.3`, dont le gate Worker est PASS.
```text
workspace.package.version = 0.3.9-pre.4
```
## 3. Décisions
```text
getSignaturesForAddress + getTransaction -> history/catch-up/repair adressé
getBlocks/getBlocksWithLimit + getBlock -> catch-up/gap repair global sous gap provenance
logsSubscribe -> live discovery + hydration
signatureSubscribe -> confirmation ciblée, pas discovery
blockSubscribe -> direct full sous capability explicite, méthode instable
slot/ledger methods -> auxiliaires de continuité
reconnect WS -> jamais assimilé à replay
mainnet-beta -> identité réseau durable canonique
```
## 4. Gaps ouverts, non implémentés
- `get_block_observed` ou équivalent nécessaire si la voie bloc multi-endpoint est admise en V1 ;
- extraction/canonicalisation transaction-par-transaction depuis un bloc à définir ;
- canonicalizer RAW v1 actuellement owned par Backfill à rendre réutilisable sans dépendance Worker -> Job ni duplication ;
- gap repair explicite requis après reconnexion/overflow WS ;
- Config future à exprimer par capabilities/rôles et non enum de protocole ;
- cohérence `mainnet` profile alias / `mainnet-beta` durable à préserver.
## 5. Fichiers
| Fichier | Action | Rôle |
|----------------------------------------------------------------|--------|---------------------------------------|
| Cargo.toml | MOD | workspace `0.3.9-pre.4` |
| docs/000-README.md | MOD | indexe larchitecture RAW |
| docs/architecture/000-README.md | MOD | ajoute le document 011 |
| docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md | ADD | audit standard + taxonomie + gaps |
| docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md | MOD | freeze pre.003 + réalisation pre.004 |
| docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md | MOD | preuves opérateur + checklist pre.004 |
| deltas/0.3.9/pre.004.md | ADD | présent delta |
## 6. Sources externes
Documentation RPC Solana officielle consultée le 4 septembre 2026. Les liens exacts sont conservés dans `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`. Les providers, quotas, Helius, Yellowstone et kbot3 restent réservés à `pre.005`.
## 7. Gates
Les audits Python sont exécutés après assemblage. Les gates Cargo de cette tranche restent à exécuter par l'opérateur ; le changement Rust se limite au metadata de version workspace.

71
deltas/0.3.9/pre.005.md Normal file
View File

@@ -0,0 +1,71 @@
<!-- file: deltas/0.3.9/pre.005.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.005`
## 1. Objet
Poursuivre l'audit RAW post-freeze avec Helius, Yellowstone, providers alternatifs et la référence fonctionnelle historique kbot3. Cette tranche ferme les preuves provider/tier/replay nécessaires avant la synthèse multi-source `pre.006`.
Aucun runtime, DTO, endpoint, secret, profil Config, Store model ou Worker API n'est modifié.
## 2. Base et version
Base : `0.3.9-pre.4`, dont les audits opérateur Rust/Markdown sont PASS.
```text
workspace.package.version = 0.3.9-pre.5
```
## 3. Décisions fermées
```text
Helius Standard WSS -> admis ; provider-managed continuity, repair KSP conservé
Helius transactionSubscribe -> live_direct_full spécialisé déjà supporté par Transport
Helius LaserStream gRPC -> Yellowstone-compatible, replay from_slot 24 h prouvé
Yellowstone transactions -> live_direct_full générique prioritaire
Yellowstone status/meta -> signaux incomplets/continuité, jamais RAW seuls
PublicNode Yellowstone -> live prouvé, profondeur replay non prouvée
OrbitFlare Yellowstone -> live prouvé, profondeur replay non prouvée
OrbitFlare archive HTTP -> historique complet depuis genesis annoncé
kbot3 -> référence fonctionnelle uniquement, aucune source de code/config
```
## 4. Gaps ouverts
- convergence RAW commune entre `transactionSubscribe`, Yellowstone transaction et autres sources full ;
- Config Yellowstone multi-provider par capabilities, sans SDK provider parallèle ;
- replay gRPC activé uniquement si support/rétention provider réellement observés ;
- repair externe conservé pour WSS lorsque la plage manquée n'est pas adressable ;
- réutilisation unique de `KSP_SECRET_HELIUS_API_KEY` dans la future composition Helius ;
- stratégies historiques provider-specific réservées à la synthèse `0.3.12`.
## 5. Fichiers
| Fichier | Action | Rôle |
|----------------------------------------------------------------|--------|------------------------------------------|
| Cargo.toml | MOD | workspace `0.3.9-pre.5` |
| docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md | MOD | audit providers/Yellowstone/kbot3 + gaps |
| docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md | MOD | marque pre.005 réalisé |
| docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md | MOD | preuves/checklist pre.005 |
| deltas/0.3.9/pre.005.md | ADD | présent delta |
## 6. Sources
| Famille | Preuves consultées |
|----------------------|------------------------------------------------------------------------------------|
| Helius | LaserStream WSS/gRPC, pricing, rate limits, gTFA, Preconfirmations, Parsed Streams |
| Yellowstone upstream | proto, README, changelog |
| PublicNode | page Solana officielle |
| OrbitFlare | Yellowstone docs, pricing, archive, gRPC product |
| kbot3 fourni | référence fonctionnelle locale uniquement |
Toutes les sources externes sont datées du 4 septembre 2026 dans l'owner architecture. Les URLs qui y figurent sont des liens documentaires ; aucune URL d'endpoint provider n'est transférée dans Config.
## 7. kbot3
L'archive historique a uniquement servi à inventorier les comportements `BackfillSource`, discovery/hydration, reprise, session WS, fallback et provenance. Aucun code, type, DTO, URL d'endpoint, secret, configuration, dependency ou convention de version n'est repris.
## 8. Gates
Les audits Python sont exécutés après assemblage. Les gates Cargo de cette tranche restent opérateur-owned ; le changement Rust se limite au metadata de version workspace.

View File

@@ -0,0 +1,135 @@
<!-- file: deltas/0.3.9/pre.006-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.006-fix.001`
## 1. Objet
Corriger la synthèse architecturale RAW de `pre.006` sans modifier le runtime.
Le document `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` était fonctionnellement riche mais conservait la forme de deux audits chronologiques A/B auxquels une synthèse avait été ajoutée. Cette structure masquait aussi la séparation stricte attendue entre les deux producteurs de `RawTransaction`.
Le correctif réécrit intégralement l'owner comme une synthèse unique centrée sur Store / `RawTransaction`.
## 2. Architecture corrigée
Le modèle durable devient explicitement :
```text
sources/protocoles/providers
-> capabilities d'acquisition
-> Job Backfill OU Worker Ingest selon l'intention
-> normalisation RAW source-neutral
-> Store
-> RawTransaction + observations/provenance
```
Les deux producteurs sont indépendants :
```text
Job Backfill
= historique demandé
= paramètres métier explicites
= campagne bornée et terminable
Worker Raw Transaction Ingest
= acquisition continue depuis start
= aucun paramètre métier de campagne au start
= stop pour terminer normalement
```
Aucun edge, appel, délégation, checkpoint partagé ou coordination lifecycle `Job <-> Worker` n'est admis.
## 3. Protocoles et capabilities
Le correctif supprime toute lecture implicite :
```text
HTTP = Backfill
WS/gRPC = Worker
```
La règle correcte est :
```text
HTTP / WS / gRPC / archive / EARLY
= moyens d'acquisition
= utilisables par le producer dont l'intention correspond
```
Exemples :
- HTTP `getTransaction` sert au Worker pour hydrater un signal live et au Job pour hydrater une signature historique ;
- HTTP `getBlock` peut servir au Worker en live polling/repair de continuité et au Job pour une plage historique ;
- Yellowstone `from_slot` peut réparer la continuité d'un Worker depuis son frontier actif ou alimenter un Job de replay historique borné ;
- une API archive/provider history appartient au Job lorsqu'elle répond à une campagne historique.
## 4. Frontière de continuité Worker
Le Worker peut réparer uniquement les pertes de continuité liées à son acquisition active :
```text
frontier live
-> gap détecté
-> replay/hydration/block recovery
-> frontier restauré
-> live continue
```
Il ne reçoit jamais une requête arbitraire « remonte avant telle date/slot/signature ». Cette responsabilité appartient au Job Backfill.
## 5. Normalisation commune
`ksp-raw-transaction-lib` reste retenu comme lower-layer source-neutral commun afin d'éviter la duplication du canonicalizer RAW v1.
Cette dépendance inférieure ne crée aucune relation fonctionnelle entre Job et Worker :
```text
Job ------> raw common ------> Store
Worker ---> raw common ------> Store
aucun edge Job <-> Worker
```
## 6. Données d'audit conservées
La réécriture conserve et réorganise :
- la matrice exhaustive des méthodes standard HTTP/WS/Yellowstone ;
- l'applicabilité Worker / Job par méthode ;
- les providers et infrastructures connus ;
- les réseaux ;
- les prix/tiers datés au 4 septembre 2026 ;
- les statuts `PROUVÉ`, `TESTABLE`, `NON PROUVÉ`, `BLOQUÉ TIER`, `À REVALIDER` ;
- les sources EARLY/shreds ;
- la stratégie de preuve Mainnet/Devnet/Testnet/local ;
- les gaps Transport/Config ;
- les handoffs `0.3.10` Worker et `0.3.12` Backfill.
## 7. Version
Correctif strictement documentaire. Conformément aux règles KSP applicables aux fixes documentaires :
```text
workspace.package.version = 0.3.9-pre.6
```
Aucun changement `Cargo.toml` n'est effectué.
## 8. Fichiers
Modifiés :
```text
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Ajouté :
```text
deltas/0.3.9/pre.006-fix.001.md
```
Aucun fichier Rust, Config, Store, Transport, endpoint ou secret n'est modifié.

View File

@@ -0,0 +1,51 @@
<!-- file: deltas/0.3.9/pre.006-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.006-fix.002`
## 1. Objet
Corriger la direction de l'identité réseau Mainnet dans la synthèse RAW sans modifier le runtime.
La cible canonique KSP devient `mainnet`. `mainnet-beta` est conservé uniquement comme alias de compatibilité/historique ou comme libellé externe lorsqu'un provider/API l'emploie réellement.
## 2. Motivation
Le précédent état documentaire conservait `mainnet-beta` comme identité Store canonique principalement parce que cette valeur était déjà présente dans l'état N1 RAW. Cette contrainte n'est pas pertinente : tant que les Jobs et Workers d'acquisition RAW ne sont pas finalisés, les données Mainnet actuelles restent des données de test et la base peut être droppée/recréée si une normalisation est nécessaire.
La compatibilité avec des données jetables ne doit donc pas imposer la direction de l'alias durable.
## 3. Décision
```text
identité KSP canonique = mainnet
alias legacy/externe = mainnet-beta
```
Cette décision concerne l'identité logique KSP. Elle ne réécrit pas artificiellement les noms publiés par les providers : une documentation externe qui nomme son cluster `mainnet-beta` reste décrite avec son libellé natif.
Le changement technique correspondant dans Config/Store et les éventuelles migrations de données ne sont pas introduits en `0.3.9` ; ils restent un handoff des releases d'acquisition suivantes.
## 4. Fichiers modifiés
```text
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Nouveau fichier :
```text
deltas/0.3.9/pre.006-fix.002.md
```
## 5. Versioning
Correctif strictement documentaire :
```text
workspace.package.version = 0.3.9-pre.6
```
`Cargo.toml` n'est pas modifié.

View File

@@ -0,0 +1,52 @@
<!-- file: deltas/0.3.9/pre.006-fix.003.md -->
<!-- version: 1 -->
# Delta v0.3.9-pre.006-fix.003 — canonicalisation technique `mainnet`
## 1. Objet
Ce correctif matérialise la décision documentaire de `pre.006-fix.002` : l'identité logique KSP du réseau de production Solana devient `mainnet` dans les configurations runtime, exemples, fixtures et tests concernés. `mainnet-beta` reste un alias legacy/externe ou un libellé historique/provider, pas une seconde identité persistée KSP.
## 2. Changements runtime/configuration
- `std.store` et son exemple utilisent désormais `network = mainnet` pour le profil Mainnet ;
- `std.transport`, son exemple et les fixtures utilisent `cluster = mainnet` pour les endpoints Mainnet ;
- les endpoints publics Solana engagés passent à `https://api.mainnet.solana.com` et `wss://api.mainnet.solana.com` ;
- `.env.example` suit ces endpoints ;
- aucune migration des données N1 RAW n'est ajoutée : la base de test peut être droppée/recréée si elle contient encore l'identité `mainnet-beta`.
## 3. Tests et documentation durable
Les canaries Config, Store API/PostgreSQL, Job Backfill, Backfill Desk et Store Desk concernées utilisent `mainnet`. Les guides Config/Transport et les owners d'architecture sont synchronisés. Les occurrences historiques de `mainnet-beta` dans les anciens plans/deltas restent volontairement intactes.
Le gate opérateur fourni le 5 septembre 2026 couvre :
```text
Rust rule audits PASS
Markdown audit PASS (332 tables, 742 files)
cargo check --workspace PASS
clippy all-targets/all-features PASS (-D warnings)
cargo test workspace all targets PASS (live/diagnostic smokes ignored by policy)
5 x Tauri deb/rpm bundles PASS
cargo tree --duplicates executed
```
Les smokes live restent opt-in. La documentation Solana courante confirme les nouveaux endpoints publics Mainnet HTTP/WSS ; aucune preuve live supplémentaire n'est exigée pour accepter ce correctif.
## 4. Version
La livraison est `0.3.9-pre.006-fix.003`. Comme elle modifie de la configuration exécutable, `workspace.package.version` devient :
```text
0.3.9-pre.6.fix.3
```
Cette forme respecte `VER-ID-007` et `VER-ID-010`. La forme locale intermédiaire `0.3.9-pre.6-fix.3` utilisée pour le premier gate n'est pas retenue comme version Cargo canonique.
## 5. Hors périmètre
- aucune migration SQL ;
- aucun alias automatique `mainnet-beta -> mainnet` dans Store ;
- aucun changement de logique Worker/Job ;
- aucun ajout de provider ;
- aucun changement des libellés externes lorsqu'un provider les publie réellement sous `mainnet-beta`.

114
deltas/0.3.9/pre.006.md Normal file
View File

@@ -0,0 +1,114 @@
<!-- file: deltas/0.3.9/pre.006.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.006`
## 1. Objet
Fermer l'audit RAW Transaction de `0.3.9` par une synthèse **exhaustive des possibilités d'acquisition**, sans limiter l'architecture aux providers/tiers actuellement accessibles à l'opérateur. La tranche distingue possibilité connue, support KSP et preuve live, classe les sources par réseau/coût/temporalité/complétude/replay et produit le handoff exact vers le Worker live `0.3.10` et l'extension Backfill `0.3.12`.
Aucun runtime d'acquisition n'est modifié dans cette tranche.
## 2. Baseline
Entrée : `0.3.9-pre.005` appliquée après la freeze Worker API `pre.003` et les audits RAW standard/providers `pre.004`/`pre.005`.
Dernier gate opérateur explicitement fourni avant cette tranche pour l'état documentaire `pre.004` :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (327 table(s), 738 file(s))
```
`pre.005` a ensuite été poursuivi sur demande de l'opérateur ; aucun gate Cargo `pre.005` n'est inventé par ce delta.
## 3. Version
```text
workspace.package.version = 0.3.9-pre.6
```
Cette livraison est une prerelease non-fix ; la synchronisation Cargo suit `VER-ID-009` même si la tranche reste documentaire côté runtime.
## 4. Synthèse fermée
`docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` ajoute :
- une légende d'admission/support/preuve ;
- la matrice exhaustive des familles HTTP, WS, Yellowstone, archive, self-hosted et pre-execution ;
- une matrice providers/réseaux/prix/tier/proof status datée au 4 septembre 2026 ;
- Helius, PublicNode, OrbitFlare, QuickNode, Alchemy, Chainstack, Shyft, Triton One, dRPC, Ankr, GetBlock et les providers Yellowstone génériques ;
- les feeds EARLY : Helius Shred Delivery, OrbitFlare Jetstream, Shyft RabbitStream, Triton Deshred/Shreds, bloXroute, DoubleZero et Jito sunset ;
- la stratégie réseau Mainnet/Devnet/Testnet/local ;
- le modèle Worker V1 par capabilities ;
- la déduplication de contenu séparée des observations de provenance ;
- l'échelle de gap repair replay -> redondance -> block scan -> hydration -> archive ;
- les adaptations Transport/Config requises pour `0.3.10` ;
- l'ownership commun de canonicalisation via `ksp-raw-transaction-lib` ;
- la matrice des stratégies Backfill destinées à `0.3.12` ;
- un ordre d'implémentation qui ne supprime aucune capability admise faute de compte payant.
## 5. Décisions d'architecture
```text
NON PROUVÉ != REJETÉ
PAYANT/BLOQUÉ != NON SUPPORTÉ
IMPLÉMENTÉ != PROUVÉ LIVE
```
`ksp-worker-raw-transaction-ingest-lib` sera multi-source par rôles/capabilities et pourra combiner sources directes full, discovery+hydration, replay, gap repair et signaux EARLY.
La canonicalisation RAW v1 commune ne reste pas dans `ksp-job-backfill-lib` et n'est pas copiée dans le Worker. `0.3.10` doit extraire la logique source-neutral dans `ksp-raw-transaction-lib`, avec golden bytes/hash inchangés.
## 6. Preuves et comptes disponibles
La synthèse sépare le support de la preuve afin de permettre :
```text
Mainnet standard HTTP/WS : smokes multi-provider gratuits quand accessibles
Mainnet Yellowstone : PublicNode disponible actuellement
Devnet : Solana public + providers gratuits, dont OrbitFlare gRPC
Testnet : Solana public + PublicNode
branches payantes : tests déterministes + smokes live ignored jusqu'à accès
local/custom : fixtures/Agave/Yellowstone auto-hébergé si nécessaire
```
Aucune clé, URL d'endpoint runtime ou donnée de compte n'est ajoutée au dépôt par cette tranche.
## 7. Fichiers
Modifiés :
```text
Cargo.toml
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Ajouté :
```text
deltas/0.3.9/pre.006.md
```
Aucun fichier Rust, Config, schema, endpoint provider, secret ou Store n'est modifié.
## 8. Validation d'assemblage
Assemblage local :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (343 table(s), 740 file(s))
```
Le sandbox d'assemblage ne fournit pas Cargo ; aucun `cargo check`, Clippy ou test Rust `pre.006` n'est déclaré PASS localement. Les gates Cargo opérateur restent séparés et ne sont jamais inventés.
## 9. Handoff
Après `pre.006`, l'audit RAW est fonctionnellement fermé. `pre.007` doit être un **gate technique final** sans nouvelle fonctionnalité ; `pre.008` réconcilie les documents durables ; `pre.009` prépare le prompt `0.3.10` et la publication.

View File

@@ -0,0 +1,78 @@
<!-- file: deltas/0.3.9/pre.007-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.007-fix.001`
## 1. Objet
Corriger l'unique échec du premier gate technique `pre.007` : une canary historique Yellowstone de `ksp-onchain-transport-lib` inspectait textuellement des contraintes de versions appartenant au `Cargo.toml` racine. Le correctif remet la responsabilité au bon niveau sans changer le runtime, les APIs Transport ou la contrainte de dépendance Yellowstone.
## 2. Baseline
Entrée : `0.3.9-pre.7`.
Le gate opérateur du 5 septembre 2026 a établi :
```text
cargo fmt --all -- --check PASS
Rust audits PASS
Markdown audit PASS (332 tables / 744 files)
cargo check --workspace PASS
clippy all-targets/all-features -D warnings PASS
cargo test workspace all targets/features FAIL: 1 historical Transport canary
ksp-worker-api PASS (28 tests + doc-tests)
Worker dependency trees PASS / Core-only
cargo tree --duplicates executed
```
L'échec exact était `release_v0_2_9_pre_002_materializes_minimal_yellowstone_engine_without_provider_or_ws_coupling`, sur une assertion littérale du manifeste racine pour `yellowstone-grpc-proto = ^12.6`. Le même gate a ensuite observé une résolution Cargo compatible `yellowstone-grpc-proto 12.7.0`.
## 3. Correction
`crates/ksp-onchain-transport-lib/tests/release_completeness.rs` cesse, dans les deux canaries historiques Yellowstone concernées, de lire le `Cargo.toml` racine et de vérifier textuellement les contraintes `tonic`, `yellowstone-grpc-proto`, `http` et `tonic-prost`.
Cette inspection globale n'appartient pas au Transport : `DEP-CARGO-007` réserve les canaries de politique workspace à la surface de gouvernance/fondation. Les tests Transport conservent les preuves propres à leur domaine :
- dépendances consommées via `.workspace = true` ;
- features runtime/dev attendues ;
- absence de `yellowstone-grpc-client` ;
- absence de dépendance `ksp-config-lib` ;
- présence des surfaces Yellowstone KSP attendues ;
- absence de réexport public de `tonic` ou `yellowstone_grpc_proto`.
La contrainte racine reste :
```text
yellowstone-grpc-proto = { version = "^12.6", default-features = false }
```
Elle accepte déjà les versions `12.x` compatibles selon la sémantique caret Cargo ; aucun pin exact vers `12.6.0` et aucun bump artificiel vers `^12.7` n'est requis par ce défaut.
## 4. Version
Le correctif modifie un test Rust consommé par le build/gate. Conformément à `VER-ID-007` et `VER-ID-010` :
```text
workspace.package.version = 0.3.9-pre.7.fix.1
```
## 5. Fichiers
Modifiés :
```text
Cargo.toml
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Ajouté :
```text
deltas/0.3.9/pre.007-fix.001.md
```
## 6. Validations d'assemblage
À exécuter avant livraison : audits Rust/Markdown, contrôle du diff et intégrité de l'archive. Cargo/rustfmt ne sont pas disponibles dans l'environnement d'assemblage ; le gate opérateur doit être rejoué sur `0.3.9-pre.7.fix.1`.

View File

@@ -0,0 +1,75 @@
<!-- file: deltas/0.3.9/pre.007-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.007-fix.002`
## 1. Objet
Adopter explicitement les baselines workspace `jsonschema ^0.53` et `yellowstone-grpc-proto ^12.7`, puis adapter les deux fixtures serveur Yellowstone au RPC `SubscribeGossip` ajouté au service `Geyser` par le proto 12.7. Le correctif ne crée aucune fonctionnalité Gossip dans l'API ou le runtime KSP.
## 2. Baseline et échec observé
Entrée : `0.3.9-pre.7.fix.1`.
Après le choix opérateur des nouvelles contraintes, le gate du 5 septembre 2026 établit :
```text
Rust / Markdown audits PASS
cargo check --workspace PASS
jsonschema résolu 0.53.0
yellowstone-grpc-proto résolu 12.7.0
Transport release_completeness ciblé PASS 43/43
clippy all-targets/all-features FAIL E0046
workspace tests all-targets/all-features FAIL E0046
ksp-worker-api PASS 28 + doc-tests
```
L'échec `E0046` ne concerne pas la surface cliente/runtime KSP : les deux implémentations de test `FixtureGeyser` ne satisfont plus le trait serveur généré, qui exige désormais `SubscribeGossipStream` et `subscribe_gossip`.
## 3. Correction
Les deux fixtures :
```text
crates/ksp-onchain-transport-lib/unit_tests/grpc_unary.rs
crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
```
déclarent le nouveau stream Gossip et répondent `tonic::Status::unimplemented("gossip is outside KSP 0.2.9")`. Cette réponse est volontaire : `pre.007` est un couloir de gate et n'ouvre aucune nouvelle capability Transport. Le fixture de version Yellowstone est également aligné de `12.6` vers `12.7`.
Le manifeste workspace devient :
```text
jsonschema = { version = "^0.53", default-features = false }
yellowstone-grpc-proto = { version = "^12.7", default-features = false }
```
## 4. Version
Le correctif modifie dépendances et tests :
```text
workspace.package.version = 0.3.9-pre.7.fix.2
```
## 5. Fichiers
Modifiés :
```text
Cargo.toml
crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
crates/ksp-onchain-transport-lib/unit_tests/grpc_unary.rs
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Ajouté :
```text
deltas/0.3.9/pre.007-fix.002.md
```
## 6. Revalidation requise
Après application, rejouer au minimum les audits, Clippy all-targets/all-features, le workspace test all-targets/all-features, le gate Worker API et les arbres Cargo. Les smokes live restent opt-in selon leur politique existante.

76
deltas/0.3.9/pre.007.md Normal file
View File

@@ -0,0 +1,76 @@
<!-- file: deltas/0.3.9/pre.007.md -->
<!-- version: 1 -->
# Delta `0.3.9-pre.007`
## 1. Objet
Ouvrir le gate technique final de `0.3.9` sans ajouter de fonctionnalité. La tranche fige l'état à tester sous l'identifiant prerelease `0.3.9-pre.7`, enregistre le clean gate complet réussi sur `pre.006-fix.003` comme précondition et exige une preuve finale sur cette version exacte avant la réconciliation documentaire `pre.008`.
## 2. Baseline
Entrée : `0.3.9-pre.6.fix.3`.
Le clean gate opérateur fourni le 5 septembre 2026 sur cette baseline est PASS :
```text
cargo clean PASS
Rust audits PASS
Markdown audit PASS (332 tables / 743 files)
cargo check --workspace PASS
clippy all-targets/all-features -D warnings PASS
cargo test workspace all targets/all features PASS
5 x Tauri deb/rpm bundles PASS
cargo tree --duplicates executed
```
Les smokes live/diagnostiques restent opt-in/ignored conformément aux politiques existantes.
## 3. Version
Cette livraison est une prerelease non-fix. Conformément à `VER-ID-009` :
```text
workspace.package.version = 0.3.9-pre.7
```
## 4. Changements
- aucun changement de code métier ou d'API ;
- aucun changement Worker/Job/Transport/Store/Config hors version workspace ;
- aucun nouvel endpoint, provider, secret, schema ou migration ;
- plan et validation synchronisés pour distinguer le clean gate `fix.003` déjà PASS du gate final `pre.007` encore à exécuter ;
- aucun document durable n'est réconcilié ici : cette responsabilité reste à `pre.008`.
## 5. Gate requis
```bash
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
cargo test -p ksp-worker-api
cargo tree -p ksp-worker-api --edges normal
cargo tree -p ksp-worker-api -e features
cargo tree --duplicates
```
Aucune case `pre.007` ne doit être fermée avant retour de cette preuve opérateur sur `0.3.9-pre.7`.
## 6. Fichiers
Modifiés :
```text
Cargo.toml
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Ajouté :
```text
deltas/0.3.9/pre.007.md
```

42
deltas/0.3.9/pre.008.md Normal file
View File

@@ -0,0 +1,42 @@
<!-- file: deltas/0.3.9/pre.008.md -->
<!-- version: 1 -->
# Delta 0.3.9-pre.008 — réconciliation documentaire finale
## Objet
Fermer la réconciliation documentaire de `0.3.9` après le gate technique PASS de `0.3.9-pre.7.fix.2`, sans rouvrir le runtime.
## Version
```text
workspace.package.version = 0.3.9-pre.8
```
## Modifications
- `README.md` : fondation Worker API et séparation durable Worker Ingest / Job Backfill.
- `crates/ksp-worker-api/README.md` : surface finale version-neutral et distinction lifecycle/runtime.
- `crates/ksp-worker-api/USAGE.md` : exemples durables et absence volontaire de commande runtime universelle.
- `docs/000-README.md` : index du plan/validation `0.3.9` et références durables.
- `docs/architecture/000-README.md` : descriptions `009`/`011` réconciliées.
- `docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md` : Worker API réelle, producteurs RAW indépendants et lower-layer `ksp-raw-transaction-lib` cible.
- `docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md` : fermeture technique `pre.007` et lane `pre.008`.
- `docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md` : preuves opérateur finales et checklist documentaire.
`docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` a été relu sans modification : sa synthèse Store-centrique et ses handoffs indépendants Worker/Backfill restent autoritaires.
## Hors périmètre
Aucun code Rust, test, Config, Store, Transport, Job runtime, Worker concret, CHANGELOG, ROADMAP ou prompt `0.3.10` n'est modifié.
## Validation attendue
```bash
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
```
La tranche est documentaire hors synchronisation mécanique de version Cargo ; aucun replay du gate workspace complet n'est requis sauf défaut détecté par ces contrôles.

36
deltas/0.3.9/pre.009.md Normal file
View File

@@ -0,0 +1,36 @@
<!-- file: deltas/0.3.9/pre.009.md -->
<!-- version: 1 -->
# Delta 0.3.9-pre.009 — préparation de publication
## Objet
Préparer la publication stable de `0.3.9` après fermeture technique `pre.007-fix.002` et réconciliation documentaire `pre.008`, sans aucun rattrapage fonctionnel ou documentaire hors couloir de publication.
## Version
```text
workspace.package.version = 0.3.9-pre.9
```
## Modifications
- `prompts/029-V0_3_10_START_PROMPT.md` : contrat opératoire autonome pour `0.3.10`, centré sur `ksp-raw-transaction-lib` puis `ksp-worker-raw-transaction-ingest-lib` live multi-source, avec `pre.001` audit/sizing obligatoire avant codage lourd ;
- `CHANGELOG.md` : synthèse finale de `0.3.9`, Worker API, audit RAW, séparation Worker/Backfill, canonicalisation `mainnet`, upgrades `jsonschema`/Yellowstone et gates opérateur ;
- `ROADMAP.md` : `0.3.9` fermé, handoff `0.3.10` aligné sur la lower-layer RAW commune et le rôle strictement live du Worker, TODO audit RAW/réseau clôturés ;
- `Cargo.toml` : synchronisation mécanique de prerelease vers `0.3.9-pre.9`.
## Hors périmètre
Aucun README, USAGE, architecture, plan, validation, code Rust/TypeScript, test, schema, Config, endpoint, dépendance, runtime ou backend n'est modifié dans cette tranche.
## Validation attendue
```bash
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
```
Si ce gate est vert, la candidate est prête pour `rel.001`, qui doit rester une publication mécanique stable sans rattrapage.

172
deltas/0.3.9/rel.001.md Normal file
View File

@@ -0,0 +1,172 @@
<!-- file: deltas/0.3.9/rel.001.md -->
<!-- version: 1 -->
# Delta `0.3.9-rel.001` — publication stable
## Base requise
Base directe attendue :
```text
0.3.9-pre.009
workspace.package.version = 0.3.9-pre.9
```
Le gate minimal opérateur de `pre.009`, exécuté le **5 septembre 2026**, est propre :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (332 table(s), 749 file(s))
cargo check --workspace: PASS
```
Les couloirs de fermeture antérieurs sont déjà fermés :
- `pre.007`, corrigée par `pre.007-fix.001` puis `pre.007-fix.002` : gate technique final, passage explicite à `jsonschema ^0.53` et `yellowstone-grpc-proto ^12.7`, adaptation des fixtures serveur au RPC `SubscribeGossip`, puis workspace `--all-targets --all-features`, Worker API et graphes Cargo validés ;
- `pre.008` : réconciliation documentaire finale des surfaces Worker/architecture/indexes/plan/validation ;
- `pre.009` : préparation minimale de publication avec `CHANGELOG.md`, `ROADMAP.md` et `prompts/029-V0_3_10_START_PROMPT.md`.
## Objet
Publier mécaniquement la version stable :
```text
0.3.9
```
Conformément à `VER-LIFECYCLE-012`, cette tranche ne corrige ni code, ni test, ni documentation durable, ni architecture, ni configuration, ni dépendance, ni prompt.
Tout défaut nouveau renvoie vers une prerelease appropriée ; `rel.001` n'est pas une tranche de rattrapage.
## Modification
### `Cargo.toml`
Le header est incrémenté parce que le fichier est réellement modifié :
```text
488 -> 489
```
La version workspace devient :
```text
0.3.9-pre.9 -> 0.3.9
```
Aucune autre ligne du `Cargo.toml` racine n'est modifiée.
## Fichiers ajoutés
```text
deltas/0.3.9/rel.001.md
```
## Fichiers modifiés
```text
Cargo.toml
```
## Fichiers supprimés
```text
aucun
```
## Surfaces explicitement inchangées
```text
CHANGELOG.md
ROADMAP.md
README.md
RULES.md
prompts/029-V0_3_10_START_PROMPT.md
crates/**
docs/**
config/**
```
La surface préparée en `pre.009` reste byte-identique hors mécanique Cargo et ajout du présent delta.
## Surface stable publiée
`0.3.9` stabilise d'abord `ksp-worker-api` comme API générique, courte et runtime-neutral pour services continus :
- identité et kind bornés ;
- lifecycle explicite et états terminaux immuables ;
- health/activity sûrs ;
- stop token partagé et idempotent ;
- séquence/snapshot latest-value ;
- `WorkerSnapshotSource` object-safe, `Send + Sync`, consommable sans runtime imposé ;
- dépendance runtime exclusivement vers `ksp-core-lib` ;
- aucune dépendance Job, Solana, Transport, Store, Config ou Tauri ;
- aucun `start/stop/restart/reconfigure` universel imposé par l'API générique.
Après freeze de cette API, la release publie également l'audit d'acquisition `RawTransaction` consolidé dans `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` :
- Store / `RawTransaction` est le centre du modèle RAW ;
- Job Backfill historique paramétré et Worker Raw Transaction Ingest live sont deux producteurs indépendants du même Store ;
- HTTP, WebSocket, Yellowstone gRPC, provider extensions, blocs/slots, discovery + hydration, replay, archives et sources EARLY sont classés par capability et non par rôle ;
- possibilités documentées, support architectural, implémentation KSP et preuve live sont distingués explicitement ;
- `mainnet` devient l'identité KSP canonique, `mainnet-beta` restant un alias legacy/externe lorsque nécessaire à une frontière provider ;
- la normalisation RAW v1 commune doit être extraite vers `ksp-raw-transaction-lib`, sans edge Job ↔ Worker ;
- le Worker `0.3.10` est live multi-source dès V1 et ne devient pas un moteur de campagne historique ;
- les extensions Backfill multi-source/multi-stratégie restent réservées à `0.3.12`.
La base technique publiée inclut également les baselines compatibles retenues pendant le gate final :
```text
jsonschema = ^0.53
yellowstone-grpc-proto = ^12.7
```
L'ajout upstream `SubscribeGossip` de Yellowstone 12.7 est pris en compte uniquement dans les fixtures serveur de test de cette release ; aucune surface Gossip de production n'est ouverte par `0.3.9`.
## Suite préparée
La session suivante part exclusivement de la base stable :
```text
v0.3.9
```
et exécute :
```text
prompts/029-V0_3_10_START_PROMPT.md
```
pour ouvrir :
```text
0.3.10 — normalisation RAW commune + Worker RawTransaction live multi-source
```
`0.3.10-pre.001` doit commencer par l'audit/sizing et le plan détaillé imposés par ce prompt avant toute implémentation lourde.
## Gate demandé
La tranche est purement mécanique. Aucun nouveau smoke provider, build Tauri ou graphe de dépendances complet n'est nécessaire : aucune dépendance, feature, source runtime ou configuration n'est modifiée par `rel.001`.
```bash
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.9
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-api
cargo tree -p ksp-worker-api --edges normal
cargo tree -p ksp-worker-api -e features
```
Après gate propre :
```text
commit : v0.3.9-rel.001
tag : v0.3.9
```
Aucun tag de prerelease ou `rel.001` n'est requis.

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/000-README.md --> <!-- file: docs/architecture/000-README.md -->
<!-- version: 10 --> <!-- version: 12 -->
# Architecture KSP # Architecture KSP
@@ -25,7 +25,8 @@ Ils ne remplacent ni `ROADMAP.md`, ni les plans de version, ni les deltas.
6. [`006-WIRE_AND_PROGRAM.md`](006-WIRE_AND_PROGRAM.md) — propriété des contrats wire, politique de dépendances codecs/interfaces, API Program ouverte, preparation d'exécution et extensibilité externe ; 6. [`006-WIRE_AND_PROGRAM.md`](006-WIRE_AND_PROGRAM.md) — propriété des contrats wire, politique de dépendances codecs/interfaces, API Program ouverte, preparation d'exécution et extensibilité externe ;
7. [`007-EXECUTION_AND_POLICY.md`](007-EXECUTION_AND_POLICY.md) — policy multi-checkpoints, orchestration transactionnelle, wallet/transport, retry, approval externe et résultat d'exécution ; 7. [`007-EXECUTION_AND_POLICY.md`](007-EXECUTION_AND_POLICY.md) — policy multi-checkpoints, orchestration transactionnelle, wallet/transport, retry, approval externe et résultat d'exécution ;
8. [`008-DATA_MATERIALIZATION_AND_STORE.md`](008-DATA_MATERIALIZATION_AND_STORE.md) — niveaux durables D1D4, Materialization, Store PostgreSQL de référence, provenance, idempotence, replay et notifications de données persistées ; 8. [`008-DATA_MATERIALIZATION_AND_STORE.md`](008-DATA_MATERIALIZATION_AND_STORE.md) — niveaux durables D1D4, Materialization, Store PostgreSQL de référence, provenance, idempotence, replay et notifications de données persistées ;
9. [`009-ACQUISITION_WORKERS_AND_JOBS.md`](009-ACQUISITION_WORKERS_AND_JOBS.md) — pipelines spécialisés, workers live, jobs de backfill/replay, backlog, claim/lease, reprise, concurrence et mécanisme de notification de référence ; 9. [`009-ACQUISITION_WORKERS_AND_JOBS.md`](009-ACQUISITION_WORKERS_AND_JOBS.md) — séparation durable pipelines/workers/jobs, Worker API générique, producteurs RAW indépendants, reprise, concurrence et frontières de composition ;
10. [`010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md`](010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md) — apps spécialisées, workers autonomes et need-driven, control plane, scenarios réutilisables, demos desktop et frontières IPC/orchestration. 10. [`010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md`](010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md) — apps spécialisées, workers autonomes et need-driven, control plane, scenarios réutilisables, demos desktop et frontières IPC/orchestration.
11. [`011-RAW_TRANSACTION_ACQUISITION.md`](011-RAW_TRANSACTION_ACQUISITION.md) — synthèse Store-centrique des sources/capabilities `RawTransaction`, matrices providers/réseaux/preuves et handoffs indépendants Worker live / Backfill historique.
`004-COMPONENT_INVENTORY.md` et `005-DEPENDENCY_GRAPH.md` sont maintenus ensemble : une évolution du graphe qui change le propriétaire d'une responsabilité doit corriger l'inventaire au lieu de laisser deux descriptions contradictoires. `004-COMPONENT_INVENTORY.md` et `005-DEPENDENCY_GRAPH.md` sont maintenus ensemble : une évolution du graphe qui change le propriétaire d'une responsabilité doit corriger l'inventaire au lieu de laisser deux descriptions contradictoires.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md --> <!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md -->
<!-- version: 12 --> <!-- version: 14 -->
# Acquisition, workers, jobs et pipelines spécialisés # Acquisition, workers, jobs et pipelines spécialisés
@@ -55,9 +55,7 @@ persistence D1 RAW
notification after commit notification after commit
``` ```
Une crate spécialisée `ksp-pipeline-raw-ingestion-lib` peut être introduite lorsque la réutilisation worker + job le justifie réellement. 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.
Elle ne choisit pas le provider réseau et ne pilote pas le range historique.
### `ksp-job-backfill-lib` ### `ksp-job-backfill-lib`
@@ -136,15 +134,15 @@ Une stratégie peut donc être :
- **alternative** : une source choisie à la place d'une autre ; - **alternative** : une source choisie à la place d'une autre ;
- **complémentaire** : une source découvre une signature/slot et une autre hydrate la transaction complète ; - **complémentaire** : une source découvre une signature/slot et une autre hydrate la transaction complète ;
- **redondante** : plusieurs providers/transports observent la même transaction et produisent des observations distinctes ; - **redondante** : plusieurs providers/transports observent la même transaction et produisent des observations distinctes ;
- **spécialisée** : une source live, une source de catch-up/gap repair et une source historique peuvent coexister avec des responsabilités différentes. - **spécialisée** : une source live, une voie d'hydration et une voie de continuité/gap repair du run peuvent coexister avec des responsabilités différentes.
Le worker ne doit pas être réduit à un enum superficiel `Http | WebSocket | Grpc`. La configuration/runtime doit exprimer les **capacités et rôles d'acquisition réellement nécessaires** : discovery, hydration, direct full transaction, live, replay/catch-up, gap repair, filtre, finality/commitment, reprise et limites. Le worker ne doit pas être réduit à un enum superficiel `Http | WebSocket | Grpc`. La configuration/runtime doit exprimer les **capacités et rôles d'acquisition réellement nécessaires** : discovery, hydration, direct full transaction, live, replay/catch-up, gap repair, filtre, finality/commitment, reprise et limites.
#### Audit obligatoire avant `0.3.10` #### Résultat de l'audit `0.3.9`
La fin de `0.3.9`, après fermeture fonctionnelle de `ksp-worker-api`, produit un audit exhaustif servant d'entrée architecturale à `0.3.10`. Cet audit ne doit pas déformer Worker API pour le premier consumer : un besoin découvert n'est remonté dans `ksp-worker-api` que s'il est réellement générique à des services continus non Solana. L'audit exhaustif est synthétisé dans [`011-RAW_TRANSACTION_ACQUISITION.md`](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 futur Worker concret porte ses propres capabilities de sources.
L'audit doit au minimum comparer : Les familles admises par la synthèse couvrent notamment :
```text ```text
HTTP getSignaturesForAddress + getTransaction HTTP getSignaturesForAddress + getTransaction
@@ -161,7 +159,7 @@ replay/from_slot/catch-up lorsqu'une implémentation/provider le permet
combinaisons multi-provider et multi-transport combinaisons multi-provider et multi-transport
``` ```
La liste n'est pas une promesse d'implémentation. Chaque voie est évaluée avant admission et peut être rejetée, réservée au backfill, réservée au live ou nécessiter une adaptation Transport/Config. 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 : Pour chaque voie, l'audit couvre au minimum :
@@ -204,20 +202,11 @@ L'archive kbot3 doit être relue uniquement comme **référence fonctionnelle**
#### `mainnet` et `mainnet-beta` #### `mainnet` et `mainnet-beta`
La terminologie réseau est un audit explicite avant toute modification. Les sources Solana actuelles utilisent de plus en plus `mainnet` alors que plusieurs surfaces/outils historiques conservent `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.
KSP ne doit jamais créer deux identités persistées pour le même cluster par simple renommage. L'audit doit donc inventorier : 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 N1 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.
```text 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.
RawNetworkId et valeurs persistées Store
Config profile ids / cluster labels
Transport descriptors et provider metadata
CLI/external aliases réellement acceptés
compatibilité des checkpoints/fingerprints existants
migration ou canonicalisation éventuellement nécessaire
```
Aucun renommage de données persistées ou de profil n'est effectué en `0.3.8`. `0.3.10` ne l'implémente que si l'audit `0.3.9` établit une stratégie de compatibilité sûre.
#### Idempotence et multi-source #### Idempotence et multi-source
@@ -352,26 +341,24 @@ Un satellite protocolaire reste avec son groupe : Meteora vaults avec Meteora, P
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. 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.
Concepts candidats : Contrats communs actuels :
```text ```text
WorkerId WorkerId
WorkerDescriptor WorkerKindCode
WorkerState WorkerState
WorkerHealth WorkerHealth
WorkerCapabilities WorkerActivity
WorkerLifecycle
WorkerStopToken
WorkerSnapshotSequence
WorkerSnapshot
WorkerSnapshotSource
``` ```
Opérations minimales candidates : 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.
```text 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.
start
stop
status
health
```
Une capability comme `reconfigure` n'est pas imposée à tous les workers.
## Job API ## Job API
@@ -467,6 +454,8 @@ Les événements utiles comprennent notamment :
### RAW backfill ### RAW backfill
État actuel avant extraction de la normalisation commune :
```text ```text
ksp-job-backfill-lib ksp-job-backfill-lib
-> ksp-job-api -> ksp-job-api
@@ -475,15 +464,26 @@ ksp-job-backfill-lib
-> ksp-onchain-transport-lib -> ksp-onchain-transport-lib
-> ksp-store-lib # façade Store ; default-features=false côté Job -> ksp-store-lib # façade Store ; default-features=false côté Job
-> futures-util/tokio # runtime privé de Backfill -> futures-util/tokio # runtime privé de Backfill
-> serde_json/sha2 # RAW v1 canonique + digest -> serde_json/sha2 # RAW v1 canonique + digest actuellement locaux
``` ```
Cible après matérialisation de la lower-layer commune :
```text
ksp-job-backfill-lib
-> ksp-raw-transaction-lib
-> ksp-store-lib
```
Le Job conserve seul ses scopes, campagnes, checkpoints et lifecycle.
### RAW worker ### RAW worker
```text ```text
ksp-worker-raw-transaction-ingest-lib ksp-worker-raw-transaction-ingest-lib
-> ksp-worker-api -> ksp-worker-api
-> ksp-onchain-transport-lib -> ksp-onchain-transport-lib
-> ksp-raw-transaction-lib
-> ksp-interface-lib # seulement si un fait passif partagé aide réellement la composition live -> ksp-interface-lib # seulement si un fait passif partagé aide réellement la composition live
-> ksp-store-lib # façade Store ; aucun backend physique direct -> ksp-store-lib # façade Store ; aucun backend physique direct
-> ksp-logging-lib -> ksp-logging-lib
@@ -495,6 +495,8 @@ composition supérieure / future Desk
-> ksp-store-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.
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. 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.
### CORE replay/worker ### CORE replay/worker
@@ -525,9 +527,7 @@ selon les capacités réellement introduites.
## Questions laissées ouvertes ## Questions laissées ouvertes
- nom final de la crate pipeline RAW si la réutilisation worker + backfill justifie réellement une crate dédiée ; - 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 ;
- taxonomie exacte des stratégies/source capabilities de `ksp-worker-raw-transaction-ingest-lib`, à décider par l'audit de fin `0.3.9` ;
- stratégie sûre de canonicalisation/aliasing `mainnet` / `mainnet-beta`, si un changement KSP est réellement nécessaire ;
- modèle de claim/lease PostgreSQL pour les futurs processors continus ; - modèle de claim/lease PostgreSQL pour les futurs processors continus ;
- taille de batch et stratégie backpressure des workers de processing ; - taille de batch et stratégie backpressure des workers de processing ;
- découpage des workers DECODE/SPECIALIZED par groupe lorsque les premiers groupes existent ; - découpage des workers DECODE/SPECIALIZED par groupe lorsque les premiers groupes existent ;

View File

@@ -0,0 +1,886 @@
<!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md -->
<!-- version: 6 -->
# Acquisition et alimentation `RawTransaction`
## 1. Rôle du document
Ce document est l'owner durable de l'architecture d'alimentation des `RawTransaction` de KSP.
Il ne suit pas l'ordre chronologique des audits `pre.004` / `pre.005`. Il synthétise directement le modèle cible :
```text
sources/protocoles/providers
|
| capacités d'acquisition
v
+-----------------------------+ +---------------------------------------+
| ksp-job-backfill-lib | | ksp-worker-raw-transaction-ingest-lib |
| historique paramétré | | acquisition continue start/stop |
+-----------------------------+ +---------------------------------------+
| |
| producteurs indépendants |
+------------------+-------------------+
v
normalisation RAW commune
|
v
Store
|
RawTransaction + observations
```
Le **Store et `RawTransaction` sont le centre**. Le Job Backfill et le Worker Ingest sont deux producteurs indépendants qui convergent vers le même contrat durable. Ils ne se pilotent pas mutuellement, ne se délèguent pas du travail et ne dépendent pas l'un de l'autre.
Les protocoles ne sont pas affectés à un producteur par nature : HTTP, WS, gRPC, archive ou shred peuvent servir au Job, au Worker ou aux deux si leur sémantique correspond au rôle concerné.
Les prix, tiers, quotas et disponibilités provider sont des données d'audit datées. Ils ne deviennent jamais des constantes métier KSP.
## 2. Centre du modèle : Store et `RawTransaction`
### 2.1 Vérité RAW durable
L'identité canonique reste :
```text
RawTransaction identity = (network, signature)
```
Le contenu canonique doit être indépendant de la source d'acquisition :
```text
même identité + même contenu canonique
-> idempotence
même identité + contenu canonique divergent
-> conflit explicite
-> jamais first-provider-wins silencieux
```
La provenance d'acquisition n'est pas l'identité de la transaction. Elle appartient aux observations associées.
### 2.2 Observations et provenance
Une même transaction peut être acquise plusieurs fois :
```text
PublicNode Yellowstone
Helius WSS
HTTP getTransaction
blockSubscribe
archive provider
...
```
Ces acquisitions peuvent converger vers un seul `RawTransaction` et plusieurs `RawTransactionObservation` distinctes lorsque leurs clés d'observation sont différentes et utiles.
La provenance sûre peut conserver notamment :
```text
provider
protocole
méthode
origin logique
endpoint logique
commitment
filter id
capture session id
timestamps
hash/taille du payload source
```
Aucune URL secrète, clé API ou payload sensible ne doit traverser ce contrat durable.
### 2.3 Complétude avant persistence
Une source n'est autorisée à produire directement un `RawTransaction` que si elle permet de reconstruire le payload canonique complet attendu par Store.
Un signal incomplet reste un matériau de discovery/hydration :
```text
signature seule
logs
transaction status
transaction body sans execution meta
shreds
slot/block meta
```
Il doit être complété avant la persistence RAW canonique.
## 3. Deux producteurs indépendants
### 3.1 Job Backfill : historique paramétré et borné
`ksp-job-backfill-lib` a pour rôle de **récupérer un historique demandé**.
Une campagne reçoit des paramètres métier explicites, par exemple :
```text
signature ou liste de signatures
program_id / address / compte ciblé
slot ou plage de slots
borne temporelle
before / after / until
limit / page bounds
commitment
stratégie ou politique de sélection admissible
```
Le Job choisit ensuite, selon Config et les capabilities réellement disponibles, une stratégie en une ou plusieurs étapes :
```text
discovery -> hydration -> normalisation -> Store
stream replay borné -> normalisation -> Store
archive -> extraction -> normalisation -> Store
```
Le Job est :
```text
paramétré
borné
terminable
checkpointable lorsque la stratégie le nécessite
orienté historique / catch-up demandé
```
Il peut utiliser **HTTP, WS, Yellowstone gRPC, replay provider ou archive** si ces capacités permettent de satisfaire la requête historique. Il ne doit pas être modélisé comme « un Worker arrêté après N éléments ».
### 3.2 Worker Raw Transaction Ingest : acquisition continue start/stop
`ksp-worker-raw-transaction-ingest-lib` a pour rôle de **remplir continuellement Store à partir du moment où il est démarré**.
Son contrôle métier V1 est volontairement court :
```text
start
stop
snapshot / notifications
```
Le caller ne lui fournit pas une signature, un `program_id`, une plage historique, une limite de campagne ou une requête de backfill. Les endpoints, réseaux, sources activées, capabilities et secrets proviennent de Config/composition, pas d'un payload métier de `start`.
À partir de son démarrage, le Worker :
```text
acquiert les nouvelles transactions disponibles
hydrate les signaux live incomplets si nécessaire
normalise puis persiste RawTransaction + observations
publie ses notifications indépendamment de leurs lecteurs
continue jusqu'à stop ou fault
```
Le Worker peut utiliser **WS, Yellowstone gRPC, HTTP, block polling, provider streams ou sources EARLY** si ces capacités servent l'acquisition live.
Il ne lance pas de campagne historique arbitraire.
### 3.3 Continuité Worker ≠ Backfill
Le Worker peut utiliser un replay ou HTTP pour **réparer une perte de continuité apparue pendant son acquisition active** :
```text
stream actif
-> gap détecté
-> replay from_slot / HTTP hydration / block recovery
-> frontier live restauré
-> reprise du flux
```
Cette réparation reste liée au frontier du Worker et ne transforme pas le Worker en moteur historique.
Inversement :
```text
« récupère les transactions du programme X depuis le slot Y »
« récupère les 100 000 dernières signatures de cette adresse »
« rejoue cette plage d'archive »
```
sont des campagnes Job Backfill.
### 3.4 Absence de relation Job ↔ Worker
Le modèle interdit les dépendances fonctionnelles suivantes :
```text
Worker -> Job Backfill
Job Backfill -> Worker
Worker délègue un gap au Job
Job démarre un Worker temporaire
coordination obligatoire entre leurs lifecycles
checkpoint partagé entre Job et Worker
```
Ils peuvent être actifs séparément ou simultanément. S'ils observent la même transaction, **Store et les invariants RAW** fournissent l'idempotence et la détection de conflit ; il ne s'agit pas d'une collaboration entre les deux producteurs.
## 4. Capabilities d'acquisition orthogonales aux producteurs
La configuration et l'architecture ne doivent pas réduire les sources à un enum fermé `HTTP | WS | gRPC`.
Les capabilities utiles sont plutôt :
```text
live_direct_transaction
live_direct_block
live_transaction_discovery
live_early_transaction
transaction_hydration
block_hydration
history_discovery
bounded_replay
continuity_boundary
continuity_repair
archive_history
```
Une source concrète peut fournir plusieurs capabilities. Une capability peut être consommée par le Job, le Worker ou les deux selon l'intention.
## 5. Matrice des méthodes et de leur applicabilité
| Famille / méthode | Contenu obtenu | RAW complet | Usage Worker live | Usage Job Backfill | Remarque |
|-----------------------------------------------------------------|---------------------------------------|------------------------------|------------------------------------------------------------|---------------------------------------------|------------------------------------------------------|
| HTTP `getTransaction(signature)` | transaction depuis signature connue | oui si disponible | oui, hydration d'un signal live | oui, hydration historique | dépend de la rétention du RPC |
| HTTP `getSignaturesForAddress` + `getTransaction` | discovery adressée puis transaction | oui après hydration | non comme campagne de scan | oui, stratégie historique principale | naturellement paramétré par adresse/programme |
| HTTP `getBlocks` / `getBlocksWithLimit` | slots confirmés | non | oui pour suivre/recoller le frontier courant si nécessaire | oui pour énumérer une plage historique | discovery par slots |
| HTTP `getBlock(slot)` full | bloc et transactions | oui | oui pour live polling ou repair de continuité | oui pour historique par bloc | nécessite provenance observed en pool multi-endpoint |
| HTTP `getSlot` / `getFirstAvailableBlock` / `minimumLedgerSlot` | bornes de ledger | non | oui, continuité | oui, admission d'une campagne | aucune transaction directe |
| WS `logsSubscribe` | signature + logs | non | oui, discovery live + hydration | non pour historique pur | `mentions` standard limité à un pubkey |
| WS `signatureSubscribe` | statut d'une signature connue | non | oui, confirmation ciblée interne | possible pour une requête ciblée en attente | one-shot |
| WS `blockSubscribe` full | bloc + transactions | oui | oui | non sans mécanisme de replay historique | méthode standard instable |
| Helius `transactionSubscribe` full | transaction + meta | oui | oui | non comme source historique | extension provider |
| Yellowstone `transactions` | transaction exécutée + meta | oui | oui | oui si replay borné demandé/disponible | filters server-side |
| Yellowstone `blocks` avec transactions | bloc + transactions | oui | oui | oui si replay borné demandé/disponible | utile au live et au backfill |
| Yellowstone `transactions_status` | signature/status/error | non | oui + hydration | oui si replay/filtre borné | discovery/statut uniquement |
| Yellowstone slots / `blocks_meta` | continuité de slots | non | oui | auxiliaire | aucune transaction full |
| Yellowstone `from_slot` / replay | reprise d'un stream depuis slot | dépend du stream | oui uniquement pour continuité du Worker | oui pour campagne historique bornée | profondeur provider-specific |
| API provider historique adressée | historique signatures ou transactions | provider-dependent | non comme campagne historique | oui | exemple Helius `getTransactionsForAddress` |
| RPC archive standard | méthodes HTTP sur ledger ancien | oui selon méthode | non pour recherche historique | oui | provider ou self-host |
| Old Faithful / `yellowstone-faithful` | historique par RPC/index | oui pour données disponibles | non | oui | archive spécialisée |
| transaction body pré-exécution | signature/corps sans meta finale | non | oui, EARLY + hydration | non principal | faible latence |
| shreds / deshred | fragments ou transaction reconstruite | non sans meta d'exécution | oui, EARLY + hydration | non principal | adapter spécialisé |
| Agave RPC auto-hébergé | méthodes standard | selon méthode | oui | oui | mêmes rôles que RPC standard |
| Agave + Yellowstone auto-hébergé | streams Yellowstone | selon stream | oui | oui avec rétention/replay opérateur | capability opérateur |
Cette matrice est intentionnellement **usage-first**. HTTP n'est pas « Backfill » et gRPC n'est pas « Worker » : leur rôle dépend de l'opération effectuée.
## 6. Stratégies Worker live
### 6.1 Acquisition directe
Sources capables de produire directement un matériau transactionnel complet :
```text
Yellowstone transactions
Yellowstone blocks avec transactions
WS blockSubscribe full
Helius transactionSubscribe full
provider stream compatible full transaction
```
Chemin logique :
```text
source live full
-> adaptation source-neutral
-> canonicalisation RAW
-> Store
-> notification Worker
```
### 6.2 Discovery live + hydration
Sources rapides mais incomplètes :
```text
logsSubscribe
transactions_status
signature/status feed
transaction body pré-exécution
shreds/deshred
```
Chemin logique :
```text
signal live
-> identité/signature
-> getTransaction ou autre hydration admissible
-> canonicalisation RAW
-> Store
```
HTTP est donc une capability normale du Worker lorsqu'il sert l'hydration live.
### 6.3 Suivi live par blocs HTTP
Une implémentation Worker peut aussi suivre le réseau sans subscription push :
```text
getSlot / borne courante
-> nouveaux slots depuis le démarrage
-> getBlock full
-> extraction transaction par transaction
-> Store
```
Cette stratégie est du **live polling** tant qu'elle suit le frontier depuis le démarrage ; elle devient historique si on lui demande une plage ancienne, auquel cas le rôle revient au Job Backfill.
### 6.4 Réparation de continuité
Le Worker doit distinguer reconnexion et continuité :
```text
reconnect réussi != absence de gap
```
Pour un gap survenu pendant son run, il peut utiliser dans cet ordre de préférence selon les capabilities :
```text
1. replay adressable du stream depuis le frontier connu
2. source live redondante ayant couvert la plage
3. HTTP getBlock/getTransaction pour les références/slots manquants
4. reprise live une fois le frontier réconcilié
```
Aucune étape ne reçoit une requête historique arbitraire du caller.
### 6.5 Notifications
Le Worker persiste d'abord la vérité RAW durable puis publie une projection/notification adaptée à son API concrète. Les notifications sont indépendantes de leurs lecteurs :
```text
aucun consumer obligatoire
aucune callback consumer-owned
aucune queue non bornée imposée par ksp-worker-api
```
## 7. Stratégies Job Backfill
### 7.1 Discovery adressée
Vertical slice déjà prouvé :
```text
request avec address/program_id/borne
-> getSignaturesForAddress
-> getTransaction observed
-> canonicalisation
-> Store
```
### 7.2 Signatures explicites
```text
request avec signatures
-> getTransaction observed
-> canonicalisation
-> Store
```
### 7.3 Historique par blocs
```text
request avec plage slot/temps/limite
-> getBlocks/getBlocksWithLimit
-> getBlock observed
-> extraction transaction par transaction
-> Store
```
### 7.4 Replay Yellowstone borné
Si un provider sert réellement `from_slot`/replay :
```text
request historique bornée
-> transactions ou blocks depuis from_slot
-> arrêt lorsque la borne de campagne est atteinte
-> Store
```
Le fait d'utiliser un stream gRPC ne change pas la nature Job : la campagne reste paramétrée, bornée et terminable.
### 7.5 APIs historiques provider
Exemples admis :
```text
Helius getTransactionsForAddress
provider archive via RPC standard
provider persistent history stream
```
Ces stratégies restent des adapters spécialisés et ne remplacent pas les primitives standard.
### 7.6 Archives
Pour les historiques hors fenêtre RPC/replay :
```text
Old Faithful / yellowstone-faithful
archive provider
CAR / Filecoin / S3 / Bigtable ou autre substrat futur
```
Les substrats directs restent une extension plus lointaine ; l'adapter doit toujours converger vers le même matériau source-neutral et le même format RAW canonique.
## 8. Taxonomie de preuve
Le support architectural et la preuve live restent séparés :
```text
possibilité connue = protocole/provider/infrastructure capable en principe
support KSP = adapter/capability implémenté ou planifié
preuve KSP = comportement réellement exercé sur un endpoint accessible
```
Codes :
| Code | Sens |
|---------------|-----------------------------------------------------------------------|
| `ADMIS` | capability à représenter dans le socle KSP |
| `SPÉCIALISÉ` | adapter provider/source autorisé sans contaminer le contrat générique |
| `EARLY` | signal incomplet précoce ; hydration/confirmation obligatoire |
| `SUNSET` | voie historique conservée seulement pour traçabilité/migration |
| `EXISTANT` | surface KSP déjà présente |
| `PLANIFIÉ` | adaptation à implémenter dans la release indiquée |
| `PROUVÉ` | propriété exercée ou directement démontrée pour le périmètre indiqué |
| `TESTABLE` | accès disponible mais smoke KSP encore à produire |
| `NON PROUVÉ` | capability admise sans preuve live actuelle |
| `BLOQUÉ TIER` | branche admise mais accès live indisponible avec les comptes actuels |
| `À REVALIDER` | documentation provider insuffisante ou contradictoire |
Règles :
```text
NON PROUVÉ != REJETÉ
PAYANT/BLOQUÉ != NON SUPPORTÉ
IMPLÉMENTÉ != PROUVÉ LIVE
PROUVÉ CHEZ UN PROVIDER != GARANTI CHEZ TOUS LES PROVIDERS
```
## 9. Matrice providers, réseaux, coût et preuve
Audit externe daté du **4 septembre 2026**.
| Provider / infrastructure | Réseaux utiles documentés | HTTP / WSS standard | Yellowstone / stream full | Historique / replay | Prix / tier utile au 04-09-2026 | Accès KSP actuel | Preuve / décision KSP |
|---------------------------------|-----------------------------------------|-----------------------------|-------------------------------------------|-----------------------------------------------------|-------------------------------------------------------------------------------|--------------------------------|------------------------------------------------------------------------|
| Solana public RPC | Mainnet-beta, Devnet, Testnet | oui | non | ledger public borné | gratuit, fortement rate-limité | oui | standard `PROUVÉ/TESTABLE`, baseline seulement |
| provider RPC standard générique | selon provider | oui si méthodes exposées | éventuel | selon provider | variable | selon compte | `ADMIS` par capabilities, jamais par nom |
| Agave auto-hébergé | Mainnet, Devnet, Testnet, local, custom | oui | Yellowstone si plugin | rétention opérateur | coût infrastructure | non actuellement | `ADMIS`, environnement KSP `NON PROUVÉ` |
| Helius | Mainnet + Devnet | Free+ | Devnet Developer+, Mainnet Business+ | LaserStream 24 h, history API | Free $0, Developer $49, Business $499, Professional $999 | HTTP/WSS gratuit | standard `TESTABLE`, gRPC Mainnet `BLOQUÉ TIER`, replay 24 h documenté |
| PublicNode | Mainnet + Testnet | gratuit | Yellowstone gRPC gratuit | archive sur demande, profondeur replay inconnue | endpoint public gratuit | Mainnet gRPC + RPC disponibles | gRPC Mainnet déjà exercé, profondeur replay `NON PROUVÉE` |
| OrbitFlare | Mainnet + Devnet | oui | Devnet Free/Developer, Mainnet add-on/Pro | archival data, profondeur gRPC inconnue | Free $0, Developer $49, Growth $399, Scale $799, Pro $999, Mainnet gRPC +$500 | Devnet gratuit possible | Devnet `TESTABLE`, Mainnet payant, replay `NON PROUVÉ` |
| QuickNode | Mainnet-beta, Testnet, Devnet | oui | Scale/Business ou add-on | `fromSlot` jusqu'à 3000 slots, archive selon réseau | Scale $499, Business $999 | pas de tier gRPC actuel | `BLOQUÉ TIER`, replay provider documenté |
| Alchemy | Mainnet + Devnet | oui | Yellowstone PAYG/Enterprise | replay documenté mais contradictoire | $75/TB gRPC, PAYG/Enterprise | standard gratuit possible | gRPC `BLOQUÉ/À REVALIDER` |
| Chainstack | Mainnet + Devnet RPC, gRPC Mainnet | Free Developer + payant | add-on Yellowstone Growth+ | archive Growth+, replay gRPC inconnu | Developer $0, Growth $49, gRPC $49/2, $149/7, $449/25 streams | standard gratuit possible | standard `TESTABLE`, gRPC `BLOQUÉ TIER`, replay `NON PROUVÉ` |
| Shyft | Mainnet + Devnet RPC | Free RPC | Build/Grow/Accelerate Yellowstone | replay jusqu'à 150 slots | Free $0, Build $199, Grow $349, Accelerate $649 | RPC gratuit possible | standard `TESTABLE`, gRPC `BLOQUÉ TIER`, 150 slots documentés |
| Triton One | Solana, réseau par endpoint | oui / Whirligig | Dragon's Mouth, Riptide, Fumarole | Fumarole persistant, Old Faithful | dépôt PAYG $125, streaming $0.08/GB, RPC $0.08/GB + $10/M calls | pas de compte actuel | `BLOQUÉ TIER`, architecture `ADMIS` |
| dRPC | Solana, réseau gRPC à qualifier | HTTP/WSS | Yellowstone Premium/Advanced | profondeur replay inconnue | Premium $399, Advanced $599 | standard éventuellement | gRPC `BLOQUÉ TIER`, replay `NON PROUVÉ` |
| Ankr | Mainnet + Devnet | Freemium/Premium HTTP + WSS | Yellowstone Solana non prouvé | ledger rolling, archive générique | $0.00005 par request/subscription/notification Solana | freemium possible | standard `TESTABLE`, ne pas inférer gRPC Solana |
| GetBlock | Mainnet-beta + Devnet | Free+ HTTP/WSS | Yellowstone dedicated/add-on | archive selon plan | Free $0, Starter $49, Advanced $199, Pro $499, Enterprise $999 | standard gratuit possible | standard `TESTABLE`, gRPC payant `NON PROUVÉ` |
| autre Yellowstone-compatible | provider/network-dependent | variable | oui si protocole compatible | provider-dependent | inconnu | non | `ADMIS` via descriptors/capabilities |
### 9.1 Alchemy : replay à revalider
Les pages Alchemy courantes ne sont pas cohérentes entre elles : certaines mentionnent environ **6000 slots**, tandis que la page dédiée Historical Replay annonce `from_slot` dans environ **432 000 slots / ~48 h**.
KSP ne doit donc pas encoder une constante Alchemy. La capability replay doit être qualifiée par documentation courante + test au moment de l'activation.
## 10. Sources ultra-low-latency et pre-execution
Ces voies sont conservées dans le socle de possibilités pour le Worker, mais elles ne deviennent jamais une vérité RAW complète tant que les métadonnées d'exécution nécessaires ne sont pas disponibles.
| Source | Transport | Réseau / accès | Contenu utile | Meta d'exécution | Prix / accès daté | Usage KSP |
|----------------------------------|---------------------|----------------------------|--------------------------------------|------------------|--------------------------------------------|-------------------------------------|
| Helius Shred Delivery | UDP shreds | Mainnet, beta/qualified | shreds bruts | non | Professional+, prix non figé | `EARLY`, deshred + hydration |
| Helius preprocessed transactions | gRPC | Helius Shred Delivery | transaction prétraitée | non | Professional+, 20 crédits/MB | `EARLY`, body/signature + hydration |
| OrbitFlare Jetstream | gRPC basé shreds | provider-dependent | transaction faible latence | non | depuis environ $500/mo selon offre | `EARLY` + hydration |
| Shyft RabbitStream | gRPC depuis shreds | provider-dependent, payant | transactions extraites des shreds | non/à confirmer | Build+ $199+ | `EARLY` + hydration |
| Triton Deshred | extension gRPC | shared/dedicated Triton | transaction reconstruite | non | offre streaming PAYG | `EARLY` + hydration |
| Triton Shred Streaming | shred stream | Triton | shreds | non | $1500/mo/IP/datacenter | phase avancée |
| bloXroute Transaction Streamer | gRPC | Solana | signature + bytes transaction + slot | non | $500/mo | `EARLY`, body + hydration |
| bloXroute Shreds | UDP/gateway | Solana | shreds | non | $500/mo | phase avancée |
| DoubleZero Edge | UDP multicast | feed Solana shreds | shreds bruts | non | $450/$900/$1500 par machine/mo selon metro | deshred + hydration |
| Jito ShredStream | shreds/local decode | Solana | shreds -> transactions | non | sunset | `SUNSET` le 5 septembre 2026 |
| Turbine/shred feed auto-opéré | UDP/local | cluster opéré | shreds bruts | non | coût infrastructure | possibilité générique future |
Jito prévoit l'arrêt complet de ShredStream le **5 septembre 2026** et recommande DoubleZero Edge. La ligne Jito reste uniquement pour exhaustivité historique/migration.
## 11. Réseaux et stratégie de preuve
Le support de stratégie doit rester network-neutral ; les smokes utilisent pragmatiquement les accès disponibles.
| Réseau KSP | Preuves prioritaires disponibles | Branches complémentaires | Règle |
|--------------|-------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|----------------------------------------------------------------|
| `mainnet` | HTTP/WS standard multi-provider, PublicNode Yellowstone gRPC, Helius HTTP/WSS | Helius gRPC payant, QuickNode, Alchemy, Chainstack, Shyft, Triton, dRPC, GetBlock | identité KSP canonique ; `mainnet-beta` = alias legacy/externe |
| `devnet` | Solana public HTTP/WS, Helius standard, OrbitFlare Yellowstone gratuit | Helius LaserStream Developer+, Alchemy, self-host | terrain privilégié pour protocole sans coût Mainnet |
| `testnet` | Solana public HTTP/WS, PublicNode RPC/WS/Yellowstone | self-host et autres providers | ne pas extrapoler disponibilité provider |
| local/custom | fixtures, Agave local, Yellowstone local si nécessaire | serveurs contrôlés | prouver gaps, replay, backpressure et erreurs |
Plan de preuve :
```text
1. tests déterministes pour chaque adapter/capability implémenté
2. smokes live opt-in sur combinaisons gratuites/accessibles
3. smokes live ignored pour branches payantes jusqu'à obtention du tier
```
Une branche peut donc être **implémentée mais non prouvée live**, à condition que la documentation le dise explicitement.
## 12. Inventaire KSP disponible
### 12.1 Transport standard HTTP
KSP possède déjà :
```text
get_signatures_for_address
get_transaction / get_transaction_observed
get_blocks / get_blocks_with_limit
get_block
get_slot
get_first_available_block
minimum_ledger_slot
```
Gap principal : `getBlock` n'a pas encore d'équivalent `get_block_observed`. Pour un pool multi-endpoint, la provenance provider/endpoint exacte doit être conservée avant de persister une observation.
### 12.2 Transport WebSocket
KSP possède déjà :
```text
logsSubscribe
signatureSubscribe
blockSubscribe
slotSubscribe
slotsUpdatesSubscribe
Helius transactionSubscribe
```
Le runtime WS possède des queues bornées et une logique de reconnect/resubscribe. Un reconnect ne constitue toutefois pas un replay adressable.
### 12.3 Yellowstone gRPC
Le moteur KSP expose déjà les familles nécessaires :
```text
transactions
transactions_status
blocks
blocks_meta
slots
from_slot
SubscribeReplayInfo / snapshot de continuité associé
```
Il faut réutiliser ce moteur générique pour les providers compatibles plutôt que créer un client gRPC par provider.
### 12.4 Store
Les primitives de convergence existent déjà autour de :
```text
RawTransactionReference
RawTransaction
RawTransactionObservation
RawAcquisitionProvenance
RawTransactionWrite::persist_raw_transaction_acquisition
RawTransactionObservationWrite::record_raw_transaction_observation
```
Aucun backend physique ne doit entrer dans le Worker ou le Job.
### 12.5 Config
Config reste seul propriétaire :
```text
réseau
endpoints
provider/protocole
secrets
activation des sources
capabilities déclarées
priorités/composition
```
Les prix/tiers d'audit ne doivent pas devenir une politique runtime.
`mainnet` est l'identité réseau durable KSP. `mainnet-beta` reste uniquement un alias de compatibilité/historique ou un libellé externe lorsqu'un provider/API l'emploie. Depuis `0.3.9-pre.006-fix.003`, les profils Config/Store/Transport Mainnet engagés utilisent `mainnet` comme identité logique, et les exemples/tests associés ont été normalisés. Les endpoints publics Solana engagés suivent également la nomenclature Mainnet courante (`https://api.mainnet.solana.com` et `wss://api.mainnet.solana.com`). Aucune migration de données N1 n'est exigée : les données RAW Mainnet encore expérimentales peuvent être droppées/recréées si elles portent l'ancienne identité.
## 13. Normalisation RAW commune sans couplage Job/Worker
La canonicalisation RAW v1 actuellement prouvée dans `ksp-job-backfill-lib::conversion` ne doit ni y rester enfermée ni être copiée dans le Worker.
Le handoff retient une petite crate source-neutral commune, à matérialiser pendant `0.3.10` :
```text
ksp-raw-transaction-lib
```
Elle n'organise aucune collaboration entre Job et Worker. Elle constitue une dépendance inférieure commune, au même titre qu'un contrat Store partagé.
Responsabilités prévues :
```text
format id/version RAW
matériau source-neutral transaction complète
canonical payload bytes
content hash
construction RawTransaction
construction/projection d'observation depuis métadonnées sûres
validation réseau/signature/slot/meta/version/index
```
Responsabilités interdites :
```text
runtime
HTTP/WS/gRPC
Config
scheduler
Job lifecycle
Worker lifecycle
backend Store physique
```
Graphe conceptuel :
```text
ksp-job-backfill-lib --------------------> ksp-raw-transaction-lib ----> Store façade
ksp-worker-raw-transaction-ingest-lib ---> ksp-raw-transaction-lib ----> Store façade
ksp-job-backfill-lib --------------------> ksp-onchain-transport-lib
ksp-worker-raw-transaction-ingest-lib ---> ksp-onchain-transport-lib
aucun edge Job <-> Worker
```
La migration doit préserver exactement les golden bytes/hash RAW v1 déjà prouvés. Aucun RAW v2 n'est justifié.
## 14. Handoff `0.3.10` : Worker live
### 14.1 Contrat fonctionnel
`ksp-worker-raw-transaction-ingest-lib` doit :
```text
être un consumer de ksp-worker-api
être démarré/arrêté sans requête historique métier
ouvrir les sources activées par Config
acquérir à partir du démarrage
supporter plusieurs sources simultanées
normaliser et persister RawTransaction + observations
publier snapshots/notifications concrètes Worker
réparer seulement ses propres pertes de continuité live
```
Il ne doit pas :
```text
recevoir signature/program_id/plage/limit de campagne au start
chercher arbitrairement avant son frontier live
instancier ou appeler ksp-job-backfill-lib
attendre un Job pour continuer
hardcoder un provider unique
```
### 14.2 Sources Worker V1 à représenter
À implémenter autant que possible, même si certaines preuves live restent bloquées par tier :
```text
Yellowstone transactions
Yellowstone blocks
Yellowstone status + hydration
WS logsSubscribe + HTTP getTransaction
WS blockSubscribe full
Helius transactionSubscribe full
HTTP live block polling
HTTP hydration
replay Yellowstone pour continuité du run
sources EARLY via adapter extensible
```
### 14.3 Gaps Transport Worker
| ID | Adaptation | Motif |
|--------|-----------------------------------------------------------------------------------|------------------------------------------------------|
| `TR-B` | `get_block_observed` symétrique de `get_transaction_observed` | provenance exacte en pool HTTP |
| `TR-C` | projection source-neutral des transactions full WS/Yellowstone | éviter plusieurs canonicalizers filaires |
| `TR-D` | métadonnées sûres d'acquisition live au moment de la conversion | observation uniforme |
| `TR-E` | conserver/exploiter `from_slot`, replay info et snapshots de continuité existants | ne pas créer un second moteur Yellowstone |
| `TR-F` | adapters EARLY uniquement quand leur protocole est réellement implémenté | réserver la capability sans tout coder immédiatement |
### 14.4 Config Worker
La composition doit exprimer des routes/capabilities de sources, pas des requêtes métier de campagne :
```text
source id
network
endpoint ref
capabilities
priority
enabled
settings techniques nécessaires
```
Elle peut activer plusieurs providers/endpoints simultanément. Les secrets restent Config-owned, avec réutilisation unique de `KSP_SECRET_HELIUS_API_KEY` pour les surfaces Helius concernées.
## 15. Handoff `0.3.12` : Job Backfill multi-stratégie
Le Job doit conserver son vertical slice existant puis ajouter des stratégies historiques choisies selon requête + capabilities + configuration.
| Stratégie | Entrée de campagne typique | Acquisition | Admission |
|----------------------------------------|-----------------------------------|----------------------------|------------------|
| GSFA + getTransaction | address/program_id + bornes/limit | discovery + hydration HTTP | déjà existante |
| signatures explicites + getTransaction | signatures | hydration HTTP | déjà existante |
| scan blocs | slots/temps/limit | getBlocks + getBlock | oui |
| Yellowstone transactions replay | from slot/plage/filtre | stream full borné | oui |
| Yellowstone blocks replay | plage/filtre | stream bloc borné | oui |
| Helius getTransactionsForAddress | address + filtres provider | history API | oui spécialisé |
| provider archive via RPC | plage historique | RPC standard archive | oui |
| Old Faithful | plage historique | RPC/index archive | oui expérimental |
| substrat archive direct | dataset + plage | adapter spécifique | futur |
Le Job peut donc utiliser gRPC ou WS/replay si cela répond à une campagne historique. Sa nature reste Job parce que l'entrée est paramétrée, la campagne bornée et la terminaison attendue.
## 16. Priorité de réalisation sans réduire le socle
L'exhaustivité de l'architecture ne signifie pas que chaque intégration vendor-specific doit être codée dans la première prerelease.
Ordre conseillé :
```text
0.3.10 P0 : normalisation RAW commune
0.3.10 P0 : observed getBlock + adapters source-neutral
0.3.10 P0 : Yellowstone transactions/blocks live
0.3.10 P0 : WS logs + hydration, blockSubscribe, Helius transactionSubscribe
0.3.10 P0 : HTTP live block polling/hydration
0.3.10 P1 : multi-source concurrency, dedup, provenance, continuity repair
0.3.10 P1 : smokes gratuits Mainnet/Devnet/Testnet
0.3.10 P2 : EARLY adapters accessibles
0.3.12 P0 : block scan historique
0.3.12 P0 : replay Yellowstone borné
0.3.12 P1 : provider history/archive
0.3.12 P1 : Old Faithful
plus tard : substrats directs et sources vendor-specific sans accès actuel
```
Une branche `ADMIS` reste dans le modèle même si son smoke live est impossible aujourd'hui.
## 17. Référence fonctionnelle historique kbot3
L'archive kbot3 fournie par l'opérateur a été auditée uniquement comme référence fonctionnelle historique.
Elle confirme notamment les besoins :
```text
discovery adressée
hydration getTransaction
pagination/reprise bornée
frontier/checkpoint pour campagne historique
retries/concurrency bornés
session WS persistante
reconnect/resubscribe
provenance d'acquisition
```
Elle confirme aussi deux limites que KSP ne doit pas reproduire :
```text
retry d'une route choisie != provenance multi-source complète
reconnect WS != replay historique
```
Aucun code, DTO, Config, endpoint, secret, dépendance ou convention de version kbot3 n'est repris.
## 18. Sources externes auditées
Sources consultées le **4 septembre 2026**. Cette liste consolide les anciens audits A/B dans un seul registre documentaire.
### 18.1 Solana standard
| Sujet | URL |
|-------------------------|-------------------------------------------------------------|
| clusters/public RPC | https://solana.com/docs/references/clusters |
| getSignaturesForAddress | https://solana.com/docs/rpc/http/getsignaturesforaddress |
| getTransaction | https://solana.com/docs/rpc/http/gettransaction |
| getBlocks | https://solana.com/docs/rpc/http/getblocks |
| getBlocksWithLimit | https://solana.com/docs/rpc/http/getblockswithlimit |
| getBlock | https://solana.com/docs/rpc/http/getblock |
| getSlot | https://solana.com/docs/rpc/http/getslot |
| getFirstAvailableBlock | https://solana.com/docs/rpc/http/getfirstavailableblock |
| minimumLedgerSlot | https://solana.com/docs/rpc/http/minimumledgerslot |
| logsSubscribe | https://solana.com/docs/rpc/websocket/logssubscribe |
| signatureSubscribe | https://solana.com/docs/rpc/websocket/signaturesubscribe |
| blockSubscribe | https://solana.com/docs/rpc/websocket/blocksubscribe |
| slotSubscribe | https://solana.com/docs/rpc/websocket/slotsubscribe |
| slotsUpdatesSubscribe | https://solana.com/docs/rpc/websocket/slotsupdatessubscribe |
### 18.2 Yellowstone et providers
| Source | URL |
|----------------------------------|---------------------------------------------------------------------------------------------------|
| Yellowstone proto | https://github.com/rpcpool/yellowstone-grpc/blob/master/yellowstone-grpc-proto/proto/geyser.proto |
| Yellowstone README | https://github.com/rpcpool/yellowstone-grpc/blob/master/README.md |
| Yellowstone changelog | https://github.com/rpcpool/yellowstone-grpc/blob/master/CHANGELOG.md |
| Helius data streaming | https://www.helius.dev/docs/data-streaming |
| Helius LaserStream | https://www.helius.dev/blog/introducing-laserstream |
| Helius WebSockets | https://www.helius.dev/blog/laserstream-websockets |
| Helius enhanced WebSockets | https://www.helius.dev/blog/introducing-next-generation-enhanced-websockets |
| Helius getTransactionsForAddress | https://www.helius.dev/blog/introducing-gettransactionsforaddress |
| Helius preprocessed transactions | https://www.helius.dev/docs/shred-delivery/preprocessed-transactions |
| PublicNode Solana | https://solana.publicnode.com/ |
| OrbitFlare Yellowstone | https://docs.orbitflare.com/data-streaming/yellowstone |
| OrbitFlare RPC | https://orbitflare.com/products/rpc-nodes |
| OrbitFlare gRPC | https://orbitflare.com/products/solana-grpc |
| QuickNode Yellowstone | https://www.quicknode.com/guides/solana-development/tooling/solana-grpc/solana-grpc |
| QuickNode gRPC plans | https://www.quicknode.com/blog/solana-grpc-is-now-included-with-scale-and-business-plans |
| Alchemy Solana gRPC | https://www.alchemy.com/solana-grpc |
| Alchemy Yellowstone quickstart | https://www.alchemy.com/docs/reference/yellowstone-grpc-quickstart |
| Alchemy historical replay | https://www.alchemy.com/docs/reference/yellowstone-grpc-historical-replay |
| Alchemy compute/bandwidth | https://www.alchemy.com/docs/reference/compute-unit-costs |
| Chainstack Yellowstone | https://chainstack.com/yellowstone-grpc-more-streams-same-price/ |
| Shyft Yellowstone | https://shyft.to/solana-yellowstone-grpc |
| Shyft pricing | https://shyft.to/solana-rpc-grpc-pricing |
| Shyft RabbitStream | https://shyft.to/solana-shreds-rabbitstream |
| Triton pricing | https://triton.one/pricing |
| Triton Yellowstone | https://blog.triton.one/complete-guide-to-solana-streaming-and-yellowstone-grpc/ |
| Triton Fumarole | https://blog.triton.one/introducing-yellowstone-fumarole/ |
| Triton Deshred | https://blog.triton.one/deshred-transactions-the-fastest-path-to-solana-data/ |
| dRPC Yellowstone | https://drpc.org/docs/solana-yellowstone-geyser-grpc |
| Ankr Solana | https://www.ankr.com/docs/rpc-service/chains/chains-list/s-t/ |
| Ankr pricing | https://www.ankr.com/docs/rpc-service/pricing/ |
| GetBlock pricing | https://getblock.io/pricing-new/ |
| bloXroute pricing | https://bloxroute.com/pricing/ |
| Jito ShredStream | https://docs.jito.wtf/lowlatencytxnfeed/ |
| DoubleZero Edge | https://docs.malbeclabs.com/Edge%20Subscriber%20Connection/ |
| Old Faithful | https://github.com/rpcpool/yellowstone-faithful |
Les URLs sont documentaires. Aucun endpoint runtime, token ou secret de compte n'est ajouté au dépôt par cet audit.
## 19. Décisions fermées après `pre.006`
```text
centre du modèle = Store / RawTransaction
producers = Job Backfill et Worker Ingest indépendants
Job Backfill = historique paramétré, borné, terminable
Worker Ingest = acquisition continue start/stop, sans requête métier historique
protocol ownership = aucun ; HTTP/WS/gRPC/archive sont des capabilities réutilisables
Worker continuity repair = seulement gaps liés à son acquisition live, pas campagne historique
Store convergence = identité (network, signature), idempotence + conflit canonique explicite
observations = plusieurs acquisitions/provenances possibles pour un même RAW
support vs preuve = axes séparés ; NON PROUVÉ n'implique pas REJETÉ
provider model = capabilities/configuration, jamais enum provider fermé
network identity = mainnet canonique ; mainnet-beta alias legacy/externe
RAW canonicalisation = lower-layer commune, sans edge Job <-> Worker
0.3.10 = Worker live multi-source + adaptations communes nécessaires
0.3.12 = Job Backfill multi-stratégie historique
```
`0.3.9` n'implémente aucune nouvelle stratégie d'acquisition. Il ferme l'architecture et l'inventaire nécessaires aux releases suivantes.

View File

@@ -0,0 +1,690 @@
<!-- file: docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md -->
<!-- version: 17 -->
# Plan v0.3.9 — Worker API générique + audit RAW Transaction
## 1. But de la version
La version `0.3.9` a deux responsabilités successives et volontairement séparées :
```text
1. stabiliser ksp-worker-api comme lifecycle API générique de services continus ;
2. seulement après sa freeze fonctionnelle, auditer exhaustivement les voies dacquisition RawTransaction afin de préparer 0.3.10.
```
Aucun besoin Solana, provider, Store, endpoint, replay ou backfill ne doit influencer le contrat générique de `ksp-worker-api` avant sa freeze.
## 2. Base autoritaire et vérification douverture
Base fournie et vérifiée le 4 septembre 2026 :
```text
archive KSP : khadhroony-solana-project-v0.3.8.zip
workspace.package.version : 0.3.8
delta stable : deltas/0.3.8/rel.001.md
prompt : prompts/028-V0_3_9_START_PROMPT.md
```
Preuves darchive :
```text
KSP SHA-256 = b3a34a3dfd56fac1eef3d23f13631ff001338b612834488038cf259112c63624
KSP entries = 1809
KSP raw bytes = 19667131
KSP unzip -t = PASS
kbot3 SHA-256 = ee47643b9f8b582ee8db97b2381ec107e45aef8c009fee44757531514615d318
kbot3 entries = 2501
kbot3 raw bytes = 37467702
kbot3 unzip -t = PASS
```
Les deux ZIP ont été contrôlés sans entrée absolue, traversal, lien symbolique, `target/`, `node_modules/`, `.git/`, `Cargo.lock` ou `.env` livré.
Le ZIP stable ne contient volontairement pas les métadonnées Git ; le tag `v0.3.8` nest donc pas réinspectable depuis `.git`. La base est néanmoins admissible selon le prompt car lopérateur la fournie explicitement comme archive stable, sa version Cargo vaut `0.3.8` et son `rel.001` exige puis décrit le tag stable `v0.3.8`.
### 2.1 Membres structurants confirmés
```text
ksp-app-store-desk
ksp-job-api
ksp-job-backfill-lib
ksp-onchain-transport-lib
ksp-config-lib
ksp-store-api
ksp-store-lib
ksp-store-postgres-lib
```
Le workspace stable comporte 18 membres et ne contient pas encore `ksp-worker-api`.
### 2.2 Baseline locale avant modification
Exécuté sur larchive KSP inchangée :
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
Markdown table audit: clean (316 table(s), 725 file(s))
```
Le binaire `cargo` nest pas installé dans lenvironnement dassemblage. Les commandes Cargo de baseline, dont `cargo fmt --all -- --check`, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo tree -p ksp-job-api`, sont donc **non exécutées ici et non déclarées PASS**. La preuve stable du delta `0.3.8-rel.001` rappelle quun gate opérateur complet avait été fermé en amont ; elle ne remplace pas une exécution locale de cette session.
## 3. Sources internes lues avant décision
Les règles et architectures demandées par le prompt ont été auditées avant la présente planification :
```text
RULES.md
ROADMAP.md
CHANGELOG.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/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/009-ACQUISITION_WORKERS_AND_JOBS.md
docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md
ksp-job-api manifest/README/USAGE/src/tests
docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md
docs/validation/023-V0_3_6_JOB_API_BACKFILL.md
ksp-job-backfill-lib README/USAGE/src/tests
ksp-app-backfill-desk README/USAGE
docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md
docs/validation/024-V0_3_7_BACKFILL_DESK.md
```
Les headers/version des fichiers normatifs et architecturaux exigés sont présents et cohérents. Aucun document stable antérieur nest modifié dans `pre.001`.
## 4. Frontière Worker / Job décidée
Règle sémantique de base :
```text
Job = traitement borné/terminable, fin normale et outcome attendus.
Worker = service continu, Running peut durer indéfiniment, arrêt normal sans notion de completion.
```
`ksp-job-api` est une référence de **propriétés déjà prouvées**, pas un parent nominal de Worker et pas une dépendance.
| Concept Job audité | Propriété générique | Décision Worker API | Motif |
|---------------------------|-----------------------------------------|---------------------------------|--------------------------------------------------------|
| JobId borné | oui | WorkerId propre | même propriété, identité sémantiquement distincte |
| JobKindCode borné | oui | WorkerKindCode propre | famille stable utile au contrôle sans dépendre de Job |
| Created | oui | WorkerState::Created | état passif avant démarrage |
| Running | oui | WorkerState::Running | un Worker peut y rester indéfiniment |
| Cancelling | non nominalement | WorkerState::Stopping | un service sarrête, il ne termine pas un Job annulé |
| Completed + JobCompletion | non | interdit | un Worker continu na pas de completion normale |
| Cancelled | non | WorkerState::Stopped | arrêt normal distinct dune annulation de traitement |
| Failed | partiellement | WorkerState::Faulted(ErrorCode) | fault terminal avec diagnostic statique sûr |
| Cancellation token | oui conceptuellement | WorkerStopToken | intention coopérative partagée et idempotente |
| Notification générique S | latest-value oui, payload générique non | WorkerSnapshot fixe | évite payload arbitraire et simplifie le control plane |
| Sequence monotone | oui | WorkerSnapshotSequence | resynchronisation sans queue |
| SnapshotSource | oui | WorkerSnapshotSource | lecture current + wait_for_change runtime-neutral |
| Terminal immuable | oui | retenu | ancien handle/source ne peut pas être réactivé |
| Checkpoint / resume | non | interdit | spécifique aux Jobs/backfills |
| Outcome / résultat | non | interdit | spécifique à un traitement terminable |
| Core-only | oui | retenu | API passive et dependency firewall minimal |
Conséquence : une duplication limitée de petites primitives (`WorkerId`, séquence, token stop) est intentionnelle. Elle maintient deux vocabulaires de lifecycle indépendants et évite quune future évolution Job force Worker ou inversement.
## 5. Surface publique Worker API prévue
`pre.002` doit ouvrir uniquement le noyau suivant, sous réserve dun défaut réel découvert lors de sa matérialisation :
```text
WorkerId
WorkerKindCode
MAX_WORKER_ID_BYTES = 128
MAX_WORKER_KIND_CODE_BYTES = 128
WorkerState
WorkerHealth
WorkerActivity
WorkerLifecycle
WorkerStopToken
WorkerSnapshotSequence
WorkerSnapshot
WorkerSnapshotFuture<'a>
WorkerSnapshotSource
ERROR_CODE_WORKER_ID_INVALID
ERROR_CODE_WORKER_KIND_INVALID
ERROR_CODE_WORKER_TRANSITION_INVALID
ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED
reexports Core : Error, ErrorCode, ErrorContext, Result
```
Modules de production prévus :
```text
src/lib.rs
src/error.rs
src/identity.rs
src/lifecycle.rs
src/snapshot.rs
src/stop.rs
```
La façade reste crate-root ; aucun `pub mod`.
### 5.1 Identité
`WorkerId` et `WorkerKindCode` utilisent un contrat borné analogue dans ses propriétés à Job mais avec leurs propres types :
```text
non vide
maximum 128 octets
alphabet ASCII sûr : A-Z a-z 0-9 _ - . :
aucun slash, whitespace ou Unicode libre
WorkerId Debug = redacted
WorkerKindCode Debug = valeur validée sûre
```
`WorkerId` identifie linstance logique observée par un lifecycle/source donné. La V1 ne définit aucune notion de génération/restart et ne rebinde jamais un ancien handle/source terminal vers une nouvelle exécution.
## 6. Lifecycle exact retenu
États :
```text
Created
Starting
Running
Stopping
Stopped
Faulted(ErrorCode)
```
| État source | Transitions autorisées | Transitions interdites notables |
|---------------|----------------------------------|-----------------------------------|
| Created | Starting, Stopped | Running direct, Stopping, Faulted |
| Starting | Running, Stopping, Faulted(code) | Stopped direct, Created |
| Running | Stopping, Faulted(code) | Starting, Stopped direct |
| Stopping | Stopped, Faulted(code) | Running, Starting |
| Stopped | aucune | toutes |
| Faulted(code) | aucune | toutes |
Décisions :
- `Created -> Stopped` représente un arrêt demandé avant démarrage effectif ;
- `Starting` et `Stopping` sont nécessaires parce que start/shutdown peuvent avoir une durée non nulle ;
- `Running` na aucune notion de completion ;
- `Faulted(ErrorCode)` ne conserve quun identifiant derreur KSP statique, jamais un message arbitraire ni un `Box<dyn Error>` ;
- `Stopped` et `Faulted` sont terminaux et immuables ;
- `WorkerLifecycle` est producteur-owned et non cloneable ; les clones éventuels concernent le token de stop et la source de snapshot, pas lautorité de transition ;
- une transition invalide retourne une erreur stable et laisse létat inchangé ;
- les appels lifecycle répétés ne sont pas silencieusement idempotents : lidempotence appartient à lintention de stop, pas à la state machine.
### 6.1 Race stop / fault
Le modèle ne crée pas de mutation concurrente de `WorkerLifecycle`. Le runtime concret possède une seule autorité de transition et arbitre les événements. Les ordres légaux sont explicitement testables :
```text
Running -> Faulted => terminal Faulted, stop lifecycle ultérieur rejeté
Running -> Stopping -> Faulted => terminal Faulted, Stopped ultérieur rejeté
Running -> Stopping -> Stopped => terminal Stopped, Faulted ultérieur rejeté
```
Le premier état terminal **validement enregistré** gagne. Un `WorkerStopToken` demandé après terminal peut conserver lintention atomique mais ne réanime ni ne transforme létat terminal.
## 7. Health et activity
`health` est distinct du lifecycle. Il décrit la qualité opérationnelle observée, pas la phase du service :
```text
WorkerHealth::Unknown
WorkerHealth::Healthy
WorkerHealth::Degraded
WorkerHealth::Unhealthy
```
`activity` est volontairement plus faible quun progrès de Job :
```text
WorkerActivity::Unknown
WorkerActivity::Idle
WorkerActivity::Active
```
Aucun `current/total`, pourcentage, ETA, compteur métier, slot, signature, backlog ou débit nentre dans Worker API. Un worker concret peut publier ses métriques métier via sa propre API/snapshot sans les injecter dans le snapshot commun.
Le lifecycle reste autoritaire pour savoir si le service est terminal ; health/activity ne doivent jamais être interprétés comme une seconde state machine.
## 8. Snapshot latest-value décidé
Contrairement à `JobNotification<S>`, Worker V1 retient un snapshot **fixe et non générique** afin que le control plane puisse lire la même forme pour tous les workers et afin dempêcher un payload arbitraire dans le contrat commun.
`WorkerSnapshot` contient uniquement :
```text
WorkerId
WorkerKindCode
WorkerSnapshotSequence
WorkerState
WorkerHealth
WorkerActivity
```
Aucune `String` diagnostique libre, aucun provider, endpoint, payload, transaction, slot, Store reference ou secret.
### 8.1 Séquence
`WorkerSnapshotSequence` :
```text
initial = 0
next = checked_add(1)
aucun wrap silencieux
is_after(observed) pour la resynchronisation
```
Toute modification publiée du snapshot commun avance la séquence exactement une fois. Le détail de stockage/wakeup reste au runtime concret.
### 8.2 Source
Contrat prévu :
```text
trait WorkerSnapshotSource: Send + Sync
current() -> WorkerSnapshot
wait_for_change(observed: WorkerSnapshotSequence) -> WorkerSnapshotFuture<'_>
```
Le trait doit être object-safe pour permettre `&dyn WorkerSnapshotSource` ou une composition équivalente sans imposer Tokio.
Sémantique latest-value :
```text
pas de queue dévénements
pas de callback producteur
les updates intermédiaires peuvent être coalescées
un listener lent reçoit le snapshot courant le plus récent
plusieurs listeners restent indépendants
un listener tardif commence par current()
le snapshot terminal reste lisible tant que la source existe
```
Un appel `wait_for_change` nest attendu quaprès inspection de `current()` et ne doit pas produire de boucle artificielle sur une séquence terminale déjà observée.
## 9. Stop coopératif
`WorkerStopToken` est une primitive runtime-neutral cloneable fondée sur une intention atomique :
```text
request_stop() -> true uniquement pour la première demande partagée
is_stop_requested() -> bool
```
La demande est bornée au sens API : opération atomique locale, non bloquante, sans attente réseau/process/thread. Elle **ne garantit pas** que le worker concret sarrête dans un délai donné. Timeout, drain, join, retry et politique de shutdown appartiennent au runtime/caller.
Le token ne modifie pas directement `WorkerLifecycle`; le producteur observe lintention puis publie `Stopping`/terminal selon sa politique concrète.
## 10. Restart et supervision
La V1 ne contient pas :
```text
restart()
restart policy
retry/backoff policy
scheduler
process manager
remote protocol
registry global
WorkerHandle générique possédant le runtime
```
Restart/recreate appartient au caller ou au futur `ksp-worker-control-lib`. Un lifecycle/source terminal ne redevient jamais Running et nest jamais rebinding vers une nouvelle instance. Cette règle ferme la race « old handle observes a restarted worker » sans introduire prématurément une génération globale.
Un futur control plane peut composer les primitives `WorkerId`/kind/snapshot/stop avec ses propres factories et règles de remplacement.
## 11. Dépendances exactes
Cible décidée :
```text
ksp-worker-api -> ksp-core-lib
```
| Dépendance | Statut prévu | Justification |
|-------------------------------|--------------|---------------------------------------------------------------|
| ksp-core-lib | REQUISE | Error, ErrorCode, ErrorContext et Result communs |
| ksp-job-api | INTERDITE | lifecycle Job distinct ; réutilisation seulement conceptuelle |
| ksp-interface-lib | INTERDITE | aucun événement wire/domain requis |
| ksp-config-lib | INTERDITE | Config/secrets hors API passive |
| ksp-logging-lib | INTERDITE | aucun comportement runtime à instrumenter |
| ksp-onchain-transport-lib | INTERDITE | aucun transport dans le contrat Worker |
| ksp-store-api / ksp-store-lib | INTERDITES | aucune persistence dans le lifecycle commun |
| tokio / futures | INTERDITES | std::future suffit au contrat dattente abstrait |
| serde | INTERDITE | aucun wire/serialization commun imposé |
| Tauri / provider SDK | INTERDITS | contrôle UI/provider hors API |
Le manifeste prévu ne comporte ni feature, ni dev-dependency, ni build-dependency tant quun besoin réel nest pas démontré. Les tests doivent fonctionner avec `std` + dépendance normale Core.
## 12. Error codes et Debug
Domaine prévu :
```text
worker_api
```
Codes prévus :
```text
worker_id_invalid
worker_kind_invalid
worker_transition_invalid
worker_snapshot_sequence_exhausted
```
Les erreurs didentité ne recopient pas la valeur hostile dans message/context ; elles indiquent uniquement le champ fautif. Les erreurs de transition exposent seulement les codes détat source/cible. `Faulted(ErrorCode)` expose un code statique et non une chaîne externe.
Debug :
```text
WorkerId -> valeur masquée
WorkerKindCode -> code sûr borné autorisé
WorkerLifecycle -> id masqué, kind/state sûrs
WorkerStopToken -> bool stop_requested uniquement
WorkerSnapshot -> id masqué, autres champs statiques/bornés
```
## 13. Threat map
| Risque | Mesure décidée | Preuve prévue |
|-------------------------------------|------------------------------------------------------------------------|-----------------------------------|
| queue de notifications non bornée | latest-value unique, aucun backlog événementiel dans lAPI | canarie source + audit production |
| listener lent bloque producteur | coalescing ; listener se resynchronise sur current | listeners lents/indépendants |
| snapshot périmé | sequence monotone attachée à toute valeur publiée | late listener + is_after |
| wrap sequence | checked_add et erreur explicite | test exhaustion |
| stop vs fault | lifecycle producteur unique ; premier terminal valide gagne | matrice et ordres adversariaux |
| stop répété | WorkerStopToken atomique idempotent | multi-clone / cross-thread |
| restart réanime ancien handle | terminal immutable ; nouvelle instance/source, aucun rebind | canarie terminal + documentation |
| fuite identité | alphabet borné ; Debug WorkerId redacted | hostile marker |
| payload/secret dans snapshot commun | snapshot fixe sans String/payload générique | inventaire de champs + Debug |
| erreur externe dans état | Faulted ne retient quun ErrorCode statique | surface publique exacte |
| confusion health/lifecycle | dimensions séparées ; lifecycle terminal reste autoritaire | tests indépendance |
| fausse progression finie | aucun percent/total générique ; activité Unknown/Idle/Active seulement | inventaire API |
| contamination Solana/Store/Config | Core-only + canaries lexicales/dependencies | dependency_boundary |
| runtime implicite | aucun spawn/thread/channel Tokio possédé par API | manifest/source firewall |
## 14. Tests planifiés
### 14.1 Unit tests
```text
identity exact bounds : 0, 1, 128, 129 octets
alphabet hostile : whitespace, slash, backslash, Unicode, control chars
lifecycle transition matrix exhaustive
invalid transition leaves state unchanged
Stopped/Faulted immutable sous tous les mutateurs
Faulted conserve uniquement ErrorCode
stop token first-wins/idempotent/shared
stop token Send + Sync / cross-thread
snapshot sequence initial/next/is_after/exhaustion
health/activity stable safe codes si helpers exposés
Debug hostile marker absent
```
### 14.2 Integration/public API
```text
construction exclusivement depuis crate root
ErrorCode constants exacts
bounds publics exacts
WorkerSnapshotSource implémentable depuis une crate externe
WorkerSnapshotSource object-safe
source custom std-only sans Tokio
slow + independent listeners resync vers latest value
late listener current puis wait
terminal current reste disponible
Send/Sync des primitives promises
```
### 14.3 Dependency/release completeness
```text
manifest dependency exacte = ksp-core-lib uniquement
aucune feature/dev/build dependency
aucun tokio/futures/serde/tracing/Tauri
aucun ksp-job-api/Config/Interface/Transport/Store
aucun RawTransaction/slot/provider/endpoint/backfill/checkpoint
modules de production exacts
exports crate-root exacts
aucun pub mod
```
Les tests ne créent aucun runtime KSP fictif. Une implémentation de test peut utiliser `std::future::ready`, `Arc` et primitives `std` uniquement.
## 15. Questions explicitement différées
Les points suivants ne bloquent pas Worker API V1 et ne doivent pas être ouverts en `pre.002` par anticipation :
```text
WorkerHandle générique de start/join
registry/factory commune
reconfiguration live
generation/restart identity globale
remote proxy/protocol/IPC
serialization du snapshot commun
metrics/rates/counters métier universels
control-plane persistence
process ownership
```
Ils ne deviennent candidats que lorsquau moins plusieurs workers ou le futur control plane prouvent un besoin transverse.
## 16. Audit RAW Transaction : position et owner documentaire
Aucune analyse fonctionnelle de kbot3 ni réaudit provider courant nest effectué dans `pre.001`, conformément au séquencement du prompt.
Après freeze fonctionnelle de `ksp-worker-api` en `pre.003`, laudit RAW est scindé pour respecter le budget de tranche. Son owner durable prévu est :
```text
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
```
Après la synthèse `pre.006-fix.001`, ce document doit être lu comme une **architecture unique centrée sur Store / `RawTransaction`**, et non comme la juxtaposition chronologique des audits A/B. Il sépare :
```text
centre durable : Store / RawTransaction / observations / provenance
producteur 1 : Job Backfill historique, paramétré, borné et terminable
producteur 2 : Worker Ingest continu, start/stop, sans requête métier historique
capabilities : HTTP / WS / gRPC / archive / EARLY utilisables selon l'intention
preuve datée : providers, réseaux, tiers, prix, replay et accès réellement prouvés
handoffs : Worker 0.3.10 et Backfill 0.3.12, sans edge Job <-> Worker
```
Les protocoles n'appartiennent à aucun producteur. Une capability gRPC/WS peut servir au Job lorsqu'elle fournit un historique/replay borné ; HTTP peut servir au Worker pour hydration, live block polling ou repair de sa continuité active. Lindex architecture ne sera synchronisé quau moment où ce document sera réellement créé, puis réconcilié dans le couloir documentaire final.
kbot3 ne sera alors utilisé que pour inventorier des comportements historiques. Aucun code, DTO, Config, URL, dependency ou version nen sera repris.
## 17. Prévision souple recalibrée
Le `pre.004` unique imaginé par le prompt est scindé en trois tranches daudit/synthèse : un audit Solana/KSP, un audit provider/kbot3, puis une synthèse. Un audit externe exhaustif Solana + Helius + Yellowstone + providers + KSP + mainnet naming dans une seule tranche dépasserait vraisemblablement le budget normatif de 15-20 minutes.
Le forecast reste volontairement souple. Une tranche peut être scindée, fusionnée ou réordonnée par delta si la réalité technique lexige, à condition de préserver les responsabilités de clôture. Chaque prerelease possède une sous-section directement éditable ; lorsquun correctif est nécessaire, il est ajouté sous la prerelease concernée avec un titre `#### pre.NNN-fix.MMM`.
### `pre.001` — audit, architecture, threat map et plan/tests
**Statut : réalisé.**
Budget cible : **15-20 min**. Vérification des archives et règles, audit architectural de référence, décisions Worker API, threat map, plan de tests et sizing. Sortie : présente livraison documentaire uniquement ; aucune crate Worker ni implémentation RAW nest ouverte dans cette tranche.
#### `pre.001-fix.001` — forecast souple éditable et hiérarchie des fixes
**Statut : réalisé ; correctif documentaire uniquement.**
Remplacement du sizing tabulaire de la section 17 par des sous-sections `### pre.NNN` et `### rel.NNN`, avec insertion des correctifs sous leur tranche via `#### pre.NNN-fix.MMM`. Le découpage technique, les budgets, les sorties attendues et lordre des responsabilités restent inchangés. Conformément aux règles documentaires, ce correctif ne modifie pas `workspace.package.version`, qui reste `0.3.8`.
#### `pre.001-fix.002` — synchronisation tardive de la version Cargo
**Statut : réalisé ; correctif de versioning/build metadata.**
Laudit de `docs/rules/VERSION_WORKFLOW.md` a mis en évidence que `VER-ID-009` impose à toute prerelease non-fix de synchroniser `workspace.package.version`, même lorsque la tranche est documentaire. Lautorisation contraire présente dans le prompt 028 était donc incompatible avec la règle normative. Le correctif synchronise létat technique avec son identifiant de fix : `workspace.package.version = 0.3.9-pre.1.fix.2`, conformément à `VER-ID-007` et `VER-ID-010`.
### `pre.002` — crate + contrats Worker API décidés
**Statut : réalisé.**
Budget cible : **15-20 min**. Le noyau `ksp-worker-api` est matérialisé selon les contrats décidés, avec tests unitaires, public API et dependency boundary.
Surface effectivement ouverte :
```text
WorkerId / WorkerKindCode
WorkerState / WorkerHealth / WorkerActivity / WorkerLifecycle
WorkerStopToken
WorkerSnapshotSequence / WorkerSnapshot / WorkerSnapshotFuture / WorkerSnapshotSource
4 ErrorCode worker_api
reexports Error / ErrorCode / ErrorContext / Result
```
La crate dépend uniquement de `ksp-core-lib`, ne possède aucune feature/dev/build dependency et n'ouvre ni runtime, ni Job, ni Transport/Store/Config, ni contrat Solana. Le snapshot commun reste fixe et sans payload métier. Les preuves adversariales, object-safety, implémentation externe et latest-value multi-listener restent volontairement dans `pre.003`.
#### `pre.002-fix.001` — chemins crate-root dans les modules propriétaires
**Statut : réalisé ; gate opérateur en échec de compilation test.**
Correction de la nouvelle crate Worker selon `RUST-IMPORT-009` : tout `impl` d'un type partagé/exporté cible désormais explicitement `crate::Type`, y compris les implémentations de traits `Debug`/`Default`. Le gate opérateur confirme les audits et `cargo check --workspace`, puis Clippy `--all-targets` et `cargo test -p ksp-worker-api` révèlent `E0423` sur le helper de séquence : le re-export `crate::WorkerSnapshotSequence` expose le type mais pas son constructeur tuple à champ privé. `workspace.package.version` reste historiquement `0.3.9-pre.2.fix.1` pour cette livraison.
#### `pre.002-fix.002` — constructeur tuple privé de séquence
**Statut : réalisé ; correctif de compilation test.**
Le champ tuple de `WorkerSnapshotSequence` reste privé. L'`impl crate::WorkerSnapshotSequence` reçoit un constructeur associé strictement privé et `#[cfg(test)]`, `exhausted_for_test()`, qui effectue localement `Self(u64::MAX)`. Le helper privé appelle ensuite `crate::WorkerSnapshotSequence::exhausted_for_test()`. Cette forme maintient simultanément `RUST-IMPORT-009`, l'opacité publique de la séquence et la visibilité Rust correcte du constructeur. La canarie dependency-boundary distingue désormais explicitement target d'`impl` crate-root et construction privée confinée. `workspace.package.version` devient `0.3.9-pre.2.fix.2`.
### `pre.003` — hardening, races, object-safety, impl externe et freeze
**Statut : réalisé ; gate opérateur PASS, Worker API effectivement frozen.**
Budget cible : **15-20 min**. La production API de `pre.002` reste inchangée : aucun nouveau type, module runtime ou edge de dépendance n'est nécessaire. La tranche ferme le hardening par preuves externes et adversariales : immutabilité terminale sous tous les mutateurs, trois ordres stop/fault, redaction hostile, stop cross-thread, `Send + Sync`, object-safety, implémentation std-only de `WorkerSnapshotSource`, coalescing latest-value pour listeners indépendants/lents, late-listener resync, rétention du snapshot terminal, inventaires exacts de modules/exports et firewall runtime/domain. `README.md` et `USAGE.md` durables sont créés pour la bibliothèque désormais fonctionnellement complète ; leur dernière réconciliation reste réservée à `pre.008`. Sortie : Worker API matériellement fermée. Le gate opérateur exécuté le 4 septembre 2026 est PASS : audits propres, workspace check/clippy `-D warnings`, 32 tests Worker + doc-tests et deux arbres Cargo propres. La Worker API est donc effectivement frozen avant l'audit RAW détaillé.
### `pre.004` — audit RAW A : Solana standard + KSP Transport/Store/Config
**Statut : réalisé ; audit documentaire daté, aucun runtime modifié.**
Budget cible : **15-20 min**. Le document durable `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` est créé et ouvre l'audit daté du 4 septembre 2026. Les voies standard sont classifiées en capabilities orthogonales : discovery adressée + hydration HTTP, scan bloc HTTP, live discovery `logsSubscribe`, confirmation ciblée `signatureSubscribe`, direct full `blockSubscribe` sous capability explicite, et primitives slot/ledger auxiliaires. L'inventaire KSP confirme Store/provenance et Config déjà suffisants pour plusieurs voies, mais identifie notamment l'absence de `get_block_observed`, l'ownership Backfill actuelle du canonicalizer RAW v1, le besoin de gap repair explicite après reconnect WS et l'état alors observé où `mainnet-beta` servait d'identité réseau. Aucun gap n'est implémenté dans cette tranche ; la direction canonique de l'alias réseau est corrigée ensuite par `pre.006-fix.002`.
### `pre.005` — audit RAW B : Helius, Yellowstone/providers + kbot3 fonctionnel
**Statut : réalisé ; audit documentaire/provider + référence fonctionnelle kbot3, aucun runtime modifié.**
Budget cible : **15-20 min**. `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` est enrichi avec Helius Standard/Enhanced WSS, LaserStream gRPC, `getTransactionsForAddress`, Preconfirmations/Parsed Streams, Yellowstone upstream, PublicNode, OrbitFlare et l'audit fonctionnel historique autorisé de kbot3. Helius LaserStream gRPC prouve un replay `from_slot` de 24 h ; PublicNode et OrbitFlare prouvent l'accès Yellowstone live mais pas une profondeur de replay contractualisable dans les sources officielles consultées. La tranche distingue explicitement continuité WSS provider-managed et gap repair adressable KSP, confirme la réutilisation du moteur Yellowstone générique existant et ouvre les gaps Config/ingest/Backfill à consolider en `pre.006`. Aucun code, DTO, endpoint, Config ou dependency de kbot3 n'est repris.
### `pre.006` — synthèse RAW multi-source + handoff `0.3.10` / `0.3.12`
**Statut : réalisé ; synthèse exhaustive des possibilités, y compris non prouvées/non testables actuellement, sans changement runtime.**
Budget cible : **15-20 min**. `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` ferme la matrice par familles de sources, providers, réseaux, prix/tier datés, temporalité, complétude, replay, destination Worker/Backfill et statut de preuve. La synthèse sépare explicitement possibilité connue, support KSP et preuve live afin qu'un tier payant ou un compte indisponible n'exclue jamais une branche de l'architecture. Le handoff distingue deux producteurs indépendants de Store : `0.3.10` reçoit le Worker live start/stop, sans requête historique métier, et les adaptations communes nécessaires ; `0.3.12` reçoit l'extension du Job Backfill historique paramétré. Les protocoles restent orthogonaux aux rôles : HTTP peut servir au Worker et WS/gRPC au Job lorsque leur sémantique le justifie. Aucun endpoint, secret ou client provider n'est ajouté ici.
#### `pre.006-fix.001` — synthèse Store-centrique et séparation stricte Worker / Backfill
**Statut : réalisé ; correctif documentaire uniquement.**
Réécriture complète de `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` pour supprimer l'effet « audit A + audit B + synthèse ajoutée ». Le document devient une référence architecturale unique centrée sur `RawTransaction` dans Store. Il fixe explicitement : Job Backfill = historique paramétré/borné ; Worker Ingest = acquisition continue start/stop sans paramètres métier historiques ; aucun edge, délégation ou coordination obligatoire entre les deux ; capabilities HTTP/WS/gRPC/archive classifiées par usage et non par producteur. Le replay/repair Worker est limité à la continuité de son acquisition active et ne devient jamais une campagne historique. Correctif strictement documentaire : `workspace.package.version` reste `0.3.9-pre.6`.
#### `pre.006-fix.002` — identité réseau canonique `mainnet`
**Statut : réalisé ; correctif documentaire uniquement.**
La synthèse corrige la direction de compatibilité réseau : `mainnet` devient l'identité KSP canonique et `mainnet-beta` devient un alias legacy/externe, jamais l'inverse. Les libellés provider natifs qui utilisent encore `mainnet-beta` restent documentés comme tels, sans imposer cette valeur à l'identité Store. La base N1 RAW actuelle reste jetable tant que Jobs/Workers d'acquisition ne sont pas finalisés ; aucune donnée de test existante ne justifie donc de figer une convention de nommage indésirable. La normalisation runtime/Config/Store correspondante est un handoff technique ultérieur, pas un changement de code `0.3.9`. `workspace.package.version` reste `0.3.9-pre.6`.
#### `pre.006-fix.003` — normalisation runtime/config de l'identité Mainnet
**Statut : réalisé ; correctif configuration/runtime + tests.**
La décision de `pre.006-fix.002` est matérialisée immédiatement pendant N1 RAW : les identités Mainnet engagées dans `std.store`, `std.transport`, leurs exemples/fixtures et les tests Store/Backfill passent de `mainnet-beta` à `mainnet`. Les endpoints publics Solana engagés utilisent la nomenclature courante `api.mainnet.solana.com` / `wss://api.mainnet.solana.com`. Les anciens RAW N1 restent jetables ; aucune migration de compatibilité n'est ajoutée. Les libellés historiques/provider `mainnet-beta` restent documentés lorsqu'ils décrivent réellement une source externe ou l'historique du projet. Ce correctif synchronise Cargo en `0.3.9-pre.6.fix.3` conformément à `VER-ID-007` et `VER-ID-010`.
### `pre.007` — gate technique final
**Statut : livré ; premier gate opérateur interrompu par une canary historique Transport trop couplée au manifeste workspace.**
Budget cible : **10-15 min**. Fermer les gates workspace/Worker API, les arbres de dépendances et laudit des doublons sans ajouter de nouvelle fonctionnalité. La tranche synchronise uniquement `workspace.package.version = 0.3.9-pre.7` puis exige le gate sur cet état exact. Le clean gate complet de `pre.006-fix.003` est une précondition PASS, pas un substitut à la preuve `pre.007`.
#### `pre.007-fix.001` — ownership des canaries de dépendances Yellowstone
**Statut : livré ; correctif de test, revalidation opérateur requise.**
Le premier gate `pre.007` a échoué sur une canary historique de `ksp-onchain-transport-lib` qui inspectait littéralement des contraintes de versions externes dans le `Cargo.toml` racine, notamment `yellowstone-grpc-proto = ^12.6`. Cette responsabilité est globale au workspace et contrevient à `DEP-CARGO-007`, qui la réserve à la surface de gouvernance/fondation. Le correctif retire des deux canaries Yellowstone historiques les lectures/assertions de versions du manifeste racine et conserve leurs preuves propres au Transport : dépendances workspace locales, features nécessaires, absence de `yellowstone-grpc-client`, firewall Config et non-exposition des crates externes. La contrainte racine `^12.6` reste inchangée ; elle autorise déjà la résolution compatible `12.7.0` observée pendant le gate, sans pin arbitraire. La version Cargo devient `0.3.9-pre.7.fix.1`.
#### `pre.007-fix.002` — baseline Yellowstone 12.7 + adaptation des fixtures Geyser
**Statut : livré ; revalidation opérateur PASS, gate `pre.007` fermé.**
Après `fix.001`, l'opérateur choisit explicitement les baselines workspace `jsonschema = ^0.53` et `yellowstone-grpc-proto = ^12.7`. Le check workspace confirme l'adoption de `jsonschema 0.53.0`; Yellowstone 12.7 conserve les surfaces KSP utilisées mais ajoute au service protobuf `Geyser` le RPC serveur `SubscribeGossip`, ce qui rend incomplètes les deux implémentations fixtures de `unit_tests/grpc_unary.rs` et `unit_tests/grpc_stream.rs`. Le correctif implémente uniquement l'associated stream type et `subscribe_gossip` dans ces fixtures avec réponse `UNIMPLEMENTED`, sans exposer Gossip dans l'API Transport ni ouvrir une fonctionnalité `0.3.9`. Le fixture `GetVersion` est aligné sur `12.7`. Cargo devient `0.3.9-pre.7.fix.2`.
La revalidation opérateur du 5 septembre 2026 est PASS : audits Rust/Markdown propres, `cargo check --workspace`, Clippy workspace/all-targets/all-features `-D warnings`, 385/385 tests unitaires Transport, 43/43 canaries `release_completeness`, workspace complet `--all-targets --all-features`, Worker API complet et arbres Cargo jusqu'à `cargo tree --duplicates`. Les smokes live/operator-only restent uniquement `ignored` conformément à leur politique. `pre.007` est donc techniquement fermé.
### `pre.008` — réconciliation documentaire finale
**Statut : livré ; gate documentaire opérateur requis.**
Budget cible : **10-15 min**. La tranche passe mécaniquement Cargo à `0.3.9-pre.8` et réconcilie uniquement la documentation durable avec l'état technique fermé : README/USAGE Worker API, index documentation/architecture, séparation Worker/Job et ownership de la normalisation RAW, plan et validation. Aucun runtime, test Rust, Config, Store, Transport, CHANGELOG, ROADMAP ou prompt suivant n'est modifié.
### `pre.009` — préparation de publication
**Statut : prévu.**
Budget cible : **5-10 min**. Préparer le prompt `0.3.10`, CHANGELOG, ROADMAP, Cargo et le delta de publication, sans rattrapage fonctionnel.
### `rel.001` — publication mécanique stable
**Statut : prévu.**
Budget cible : **5-10 min**. Publier mécaniquement `v0.3.9` sur une candidate déjà fermée, sans rattrapage.
Les numéros restent souples. Un défaut réel peut ouvrir `pre.NNN-fix.MMM` dans le même couloir ou forcer un split supplémentaire. Les responsabilités `gate technique -> réconciliation documentaire -> préparation de publication` restent séparées.
## 18. Versioning de `pre.001`
La livraison initiale `pre.001` a été assemblée comme tranche documentaire et a laissé `workspace.package.version = 0.3.8` en suivant une autorisation explicite du prompt 028. Une relecture du document normatif `docs/rules/VERSION_WORKFLOW.md` a toutefois établi que cette exception contredisait `VER-ID-009`, qui impose la synchronisation Cargo pour toute publication non-fix dune prerelease, y compris lorsquelle est documentaire.
`pre.001-fix.002` corrige donc létat courant sans réécrire lhistorique des deltas précédents :
```text
workspace.package.version = 0.3.9-pre.1.fix.2
```
La forme `pre.1.fix.2` est imposée par `VER-ID-010` pour la livraison `0.3.9-pre.001-fix.002`. La version `0.3.9-pre.2` reste réservée à la future tranche non-fix `pre.002`, conformément à `VER-ID-006`.
## 19. Gate de sortie `pre.001`
La tranche est fermée architecturalement lorsque les points suivants sont présents dans le plan/validation :
```text
architecture Worker API décidée
state/health/activity/snapshot/stop décidés
surface publique prévue
Core-only exact dependency map
questions différées listées
threat map
plan de tests
prévision souple recalibrée
audit RAW positionné après freeze Worker API
aucun code worker concret commencé
```
Les gates Cargo restent à exécuter par lopérateur dans un environnement Rust. Leur absence locale nest pas masquée par les audits Python.

View File

@@ -0,0 +1,357 @@
<!-- file: docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md -->
<!-- version: 16 -->
# Validation v0.3.9 — Worker API + audit RAW Transaction
## 1. Objet
Cette matrice suit les preuves de `0.3.9` sans remplacer les deltas. `pre.001` ferme seulement le cadrage architectural ; les preuves Rust et laudit externe RAW restent ouverts jusquà leurs tranches dédiées.
## 2. Gate douverture `pre.001`
- [X] Archive KSP fournie explicitement comme stable `v0.3.8`.
- [X] SHA-256 KSP : `b3a34a3dfd56fac1eef3d23f13631ff001338b612834488038cf259112c63624`.
- [X] `unzip -t` KSP intégral propre.
- [X] 1 809 entrées KSP contrôlées sans traversal/absolu/symlink/artefact interdit.
- [X] `workspace.package.version = 0.3.8`.
- [X] `deltas/0.3.8/rel.001.md` présent.
- [X] 18 membres workspace inventoriés ; Store Desk, Job, Transport, Config et Store requis présents.
- [X] Archive kbot3 SHA-256 `ee47643b9f8b582ee8db97b2381ec107e45aef8c009fee44757531514615d318` et `unzip -t` propre.
- [X] kbot3 **non inspecté fonctionnellement** pendant la conception Worker API ; usage réservé à laudit RAW après freeze.
- [X] Règles normatives et architectures obligatoires du prompt lues avant décision.
- [X] Headers/version des documents obligatoires confirmés.
- [X] `ksp-job-api` manifest/src/tests et plan/validation `0.3.6` audités comme référence de propriétés.
- [X] `ksp-job-backfill-lib` et Backfill Desk audités comme contraste API générique / consumer concret.
- [X] Audit Rust Python baseline : clean, export completeness 0, KSP workspace clean.
- [X] Audit Markdown baseline : clean, 316 tables / 725 fichiers.
- [ ] `cargo fmt --all -- --check` local : non exécuté, `cargo` absent.
- [ ] `cargo check --workspace` local : non exécuté, `cargo` absent.
- [ ] `cargo clippy --workspace --all-targets` local : non exécuté, `cargo` absent.
- [ ] `cargo tree -p ksp-job-api --edges normal` local : non exécuté, `cargo` absent ; manifest statique Core-only confirmé séparément.
### Audit post-documentation `pre.001`
- [X] Audit Rust post-modification : General clean, export completeness 0, KSP workspace clean.
- [X] Audit Markdown post-modification : clean, 321 tables / 728 fichiers.
- [X] Diff byte-level contre le ZIP stable : trois ajouts, zéro modification, zéro suppression après retrait des artefacts ignorés.
- [X] La livraison initiale `pre.001` a laissé `Cargo.toml` byte-identique à la base ; cette décision a ensuite été identifiée comme non conforme à `VER-ID-009` et corrigée par `pre.001-fix.002`.
Le ZIP ne contient pas `.git`; lexistence du tag nest pas inspectée directement. Le `rel.001` stable décrit lopération `tag v0.3.8` et le prompt autorise larchive stable explicitement fournie comme base.
### Gate opérateur avant `pre.002`
Létat `0.3.9-pre.1.fix.2` a ensuite été validé par lopérateur avant ouverture de `pre.002` :
- [X] `cargo clean` exécuté.
- [X] `cargo fmt --all` exécuté.
- [X] audits Rust complets propres.
- [X] audit Markdown propre : 320 tables / 730 fichiers.
- [X] `cargo check --workspace` PASS.
- [X] `cargo clippy --workspace --all-targets --all-features -- -D warnings` PASS.
- [X] `cargo test --workspace --all-targets --all-features` PASS ; seuls les smokes opt-in explicitement `ignored` restent non exécutés.
- [X] les cinq Desks produisent chacun leurs bundles `deb` et `rpm` avec la version `0.3.9-pre.1.fix.2`.
- [X] `cargo tree --duplicates` exécuté jusqu'au retour shell, sans échec de commande signalé.
## 3. Architecture Worker API décidée en `pre.001`
- [X] Job et Worker restent deux lifecycle APIs sémantiquement distinctes.
- [X] Aucun edge `ksp-worker-api -> ksp-job-api`.
- [X] Dependency cible exacte : `ksp-worker-api -> ksp-core-lib` uniquement.
- [X] `WorkerId` et `WorkerKindCode` propres, 128 octets max, alphabet sûr.
- [X] States retenus : Created, Starting, Running, Stopping, Stopped, Faulted(ErrorCode).
- [X] Stopped et Faulted terminaux/immutables.
- [X] Health distinct : Unknown, Healthy, Degraded, Unhealthy.
- [X] Activity générique minimale : Unknown, Idle, Active.
- [X] Aucun progress `current/total`, pourcentage, ETA ou métrique métier dans lAPI commune.
- [X] Snapshot commun fixe, non générique, sans payload arbitraire.
- [X] Snapshot fields limités à identity/kind/sequence/state/health/activity.
- [X] Latest-value retenu : current + wait_for_change + coalescing + resync.
- [X] Sequence monotone checked, exhaustion explicite.
- [X] `WorkerSnapshotSource` prévu `Send + Sync` et object-safe sans Tokio.
- [X] `WorkerStopToken` coopératif, partagé, idempotent, non bloquant.
- [X] Stop token ne possède ni timeout, ni join, ni runtime.
- [X] Fault public ne conserve quun `ErrorCode` statique.
- [X] Restart/retry/factory/control plane explicitement caller-owned et différés.
- [X] Ancien lifecycle/source terminal jamais rebinding vers une nouvelle instance.
- [X] Aucun WorkerHandle générique imposé en V1.
- [X] Aucun type transaction/slot/provider/endpoint/Store/replay/backfill/checkpoint dans la surface prévue.
## 4. Surface et hardening à matérialiser
### `pre.002`
Les cases cochées de cette sous-section attestent la matérialisation de la surface et des canaries prévues. L'environnement d'assemblage ne disposant pas de Cargo, leur exécution Rust reste un gate opérateur avant `pre.003`.
- [X] `crates/ksp-worker-api` créée dans le workspace.
- [X] Manifest sans feature/dev/build dependency et avec Core uniquement.
- [X] `identity.rs`, `error.rs`, `lifecycle.rs`, `snapshot.rs`, `stop.rs`, `lib.rs` matérialisés.
- [X] Façade crate-root uniquement, aucun `pub mod`.
- [X] Quatre error codes stables `worker_api` matérialisés.
- [X] Bounds exacts identity testés.
- [X] Lifecycle transition matrix exacte testée.
- [X] Transition invalide conserve létat source.
- [X] Stop token idempotent/shared testée.
- [X] Snapshot sequence/exhaustion testée.
- [X] Public API consumer depuis crate root testée.
- [X] Dependency firewall initial testée.
#### Gate opérateur de `pre.002` avant fix
- [X] `cargo fmt --all` exécuté.
- [X] audits Rust complets propres.
- [X] audit Markdown propre : 320 tables / 731 fichiers.
- [X] `cargo check --workspace` PASS.
- [X] `cargo clippy --workspace --all-targets --all-features -- -D warnings` PASS.
- [X] `cargo test -p ksp-worker-api` PASS : 13 unitaires + 2 dependency boundary + 2 public API.
- [X] `cargo tree -p ksp-worker-api --edges normal` confirme `ksp-worker-api -> ksp-core-lib` uniquement côté KSP.
- [X] `cargo tree -p ksp-worker-api -e features` ne révèle aucune feature Worker propre ni dépendance runtime/domain ajoutée.
#### `pre.002-fix.001`
- [X] Violation `RUST-IMPORT-009` identifiée sur les targets d'`impl` des types Worker exportés.
- [X] `identity.rs` utilise `impl crate::WorkerId` et `impl crate::WorkerKindCode`, y compris pour `Debug`.
- [X] `lifecycle.rs` utilise la façade crate-root pour les cinq targets d'`impl` visibles.
- [X] `snapshot.rs` utilise la façade crate-root pour les trois targets d'`impl` visibles.
- [X] `stop.rs` utilise la façade crate-root pour l'impl inhérent, `Default` et `Debug`.
- [X] Canarie dependency-boundary ajoutée contre cette régression.
- [X] Gate opérateur exécuté : audits propres et `cargo check --workspace` PASS.
- [X] Clippy `--all-targets --all-features -- -D warnings` et `cargo test -p ksp-worker-api` bloquent sur `E0423` dans le helper de séquence.
#### `pre.002-fix.002`
- [X] Aucun élargissement de visibilité du champ tuple `WorkerSnapshotSequence`.
- [X] Constructeur associé privé `#[cfg(test)]` ajouté dans `impl crate::WorkerSnapshotSequence`.
- [X] Construction terminale confinée à `Self(u64::MAX)` dans l'impl propriétaire.
- [X] Helper privé appelle `crate::WorkerSnapshotSequence::exhausted_for_test()`.
- [X] Canarie dependency-boundary interdit le retour de `crate::WorkerSnapshotSequence(u64::MAX)`.
- [X] Gate Cargo du fix exécuté par l'opérateur : audits clean, check/clippy PASS, 13 unit + 4 dependency + 2 public API PASS, doc-tests PASS et arbres de dépendances Core-only.
### `pre.003`
- [X] Canarie des trois ordres stop-vs-fault ajoutée.
- [X] Canarie exhaustive vérifiant que tous les mutateurs refusent `Stopped` et `Faulted` ajoutée.
- [X] Canaries Debug hostile identity/lifecycle/snapshot et erreurs d'identité sans echo ajoutées.
- [X] Inventaire lexical exact de `WorkerSnapshot` ajouté : six dimensions communes uniquement, aucun payload/string libre.
- [X] Stop token `Send + Sync` et visibilité cross-thread couverts par canarie.
- [X] `Send + Sync` des primitives Worker publiques promises couvert par canarie de compilation.
- [X] `WorkerSnapshotSource: Send + Sync` et object-safety couverts par une implémentation externe via `&dyn WorkerSnapshotSource`.
- [X] Implémentation externe std-only matérialisée sans Tokio/Futures crate ni dev-dependency.
- [X] Listeners lent et rapide indépendants couverts avec coalescing vers la latest value.
- [X] Late listener `current()` puis `wait_for_change(sequence)` couvert.
- [X] Snapshot terminal retenu et relisible via `current()` couvert.
- [X] Exact production module inventory ajouté.
- [X] Exact crate-root export inventory ajouté et `pub mod` interdit.
- [X] Firewall production renforcé contre Job/Config/Interface/Transport/Store/Tokio/Futures/serde/Tauri/Solana et préoccupations RAW/provider.
- [X] `crates/ksp-worker-api/README.md` descriptif durable ajouté.
- [X] `crates/ksp-worker-api/USAGE.md` version-neutral ajouté avec exemples identity/lifecycle/health/activity/stop/snapshot/source/sequence.
- [X] Audits Python d'assemblage : Rust clean, export completeness 0, KSP workspace clean, Markdown clean (320 tables / 736 fichiers).
- [X] Gate Cargo opérateur `pre.003` exécuté : audits clean, `cargo check --workspace` PASS, Clippy `-D warnings` PASS, 13 unit + 4 dependency + 2 public API + 4 completeness + 6 hardening + 3 snapshot source PASS, doc-tests PASS.
- [X] Worker API déclarée effectivement frozen avant tout audit RAW détaillé.
## 5. Threat model à fermer
- [X] Queue non bornée évitée architecturalement par latest-value.
- [X] Slow-listener blocking évité architecturalement par coalescing/resync.
- [X] Stale snapshot couvert par sequence monotone.
- [X] Sequence wrap prévu comme erreur explicite.
- [X] Stop répété couvert par token first-wins.
- [X] Stop/fault terminal défini par first valid terminal transition.
- [X] Old-handle/restart fermé par immutabilité + no rebind.
- [X] Identity/log leakage réduit par bornes + Debug redaction.
- [X] Arbitrary payload dans snapshot commun interdit par forme fixe.
- [X] External error payload dans état interdit ; ErrorCode only.
- [X] Health/lifecycle séparés.
- [X] Fausse notion de completion/progress Worker interdite.
- [X] Runtime ownership reste hors API.
- [X] Canaries Rust correspondantes exécutées après matérialisation.
## 6. Audit RAW Transaction post-freeze
Aucun item ci-dessous nest déclaré exécuté en `pre.001`.
### `pre.004` — Solana standard + KSP
- [X] Réaudit Solana JSON-RPC HTTP primaire.
- [X] Réaudit Solana WebSocket primaire.
- [X] Capabilities KSP Transport réellement publiques inventoriées.
- [X] Store RAW identity/content/provenance réconfirmés.
- [X] Config/secrets/network descriptors inventoriés pour handoff uniquement.
- [X] HTTP discovery/hydration roles classifiés.
- [X] WS logs/signature/block roles classifiés.
- [X] Live/catch-up/gap-repair/history applicability documentée.
- [X] `mainnet` / `mainnet-beta` audit interne commencé sans migration ; l'état alors observé utilisait `mainnet-beta`, décision ensuite supersédée par `pre.006-fix.002`.
### `pre.005` — providers + kbot3 historique
- [X] Helius HTTP standard Mainnet/Devnet réaudité sur sources officielles courantes.
- [X] Helius WS standard Mainnet/Devnet réaudité ; limites de connexions/subscriptions et continuité LaserStream documentées.
- [X] Helius `transactionSubscribe` réaudité séparément : Developer+, filtres avancés, `transactionDetails=full`, surface KSP existante.
- [X] Helius LaserStream gRPC réaudité : Yellowstone-compatible, Devnet Developer+, Mainnet Business+, replay explicite jusqu'à 24 h.
- [X] Helius `getTransactionsForAddress`, Preconfirmations et Parsed Streams inventoriés séparément sans les confondre avec RAW canonique V1.
- [X] Yellowstone upstream `transactions` / `transaction_status` / `blocks` / `blocks_meta` / `from_slot` / replay info réaudité.
- [X] `SubscribeDeshred` enregistré comme extension pré-exécution spécialisée ; serveur OSS upstream standard actuellement non implémenté pour ce RPC.
- [X] Replay/from_slot classifié provider par provider : Helius 24 h prouvé ; PublicNode/OrbitFlare profondeur non prouvée par les sources officielles consultées.
- [X] Quotas/filter/subscription/reconnect limits sourcés lorsqu'ils sont publiés ; absence de limite provider explicite conservée comme UNKNOWN plutôt qu'inventée.
- [X] PublicNode et OrbitFlare réaudités uniquement sur leurs surfaces/docs officielles.
- [X] kbot3 réaudité fonctionnellement : acquisition/live/history/recovery/fallback/provenance.
- [X] Aucun code/DTO/URL d'endpoint/Config/dependency kbot3 repris.
- [X] Aucun endpoint ou secret provider ajouté/modifié en `pre.005`.
- [X] Audits opérateur post-`pre.004` : Rust clean, export completeness 0, KSP workspace clean, Markdown clean (327 tables / 738 fichiers).
### `pre.006` — synthèse et handoff
- [X] Matrice complète des dimensions imposées par le prompt 028, étendue aux possibilités connues même non prouvées/non testables avec les comptes actuels.
- [X] Alternatives/complements/redundancy/specialization classifiés, avec sources full, discovery+hydration, replay/archive et EARLY.
- [X] Discovery + hydration distingués des streams full transaction et des signaux/shreds pré-exécution.
- [X] Dedup content vs observations de provenance explicitée ; conflit canonique multi-source jamais résolu silencieusement par first-wins.
- [X] Stratégie multi-source V1 `0.3.10` proposée par capabilities, sans enum protocole/provider fermé.
- [X] Gaps Transport `0.3.10` listés (`get_block_observed`, projection/provenance full commune, conservation moteur Yellowstone), non implémentés en `0.3.9`.
- [X] Gaps Config `0.3.10` listés : sources multiples, rôles/capabilities, priorité/enablement et network binding ; aucun nouvel endpoint ajouté en `0.3.9`.
- [X] Réutilisation unique `KSP_SECRET_HELIUS_API_KEY` confirmée pour toutes les surfaces Helius concernées.
- [X] Stratégie `mainnet` / `mainnet-beta` conclue après correction : `mainnet` est l'identité réseau canonique KSP ; `mainnet-beta` est un alias legacy/externe.
- [X] Applicability `0.3.12` Backfill indiquée par source/méthode : GSFA, block scan, explicit signatures, Yellowstone replay, provider history/archive et Old Faithful.
- [X] `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` créé dès `pre.004` comme owner durable, à enrichir en `pre.005`/`pre.006`.
#### `pre.006-fix.001` — synthèse Store-centrique et rôles indépendants
- [X] `011-RAW_TRANSACTION_ACQUISITION.md` réécrit intégralement comme une synthèse unique ; les anciens blocs A/B ne structurent plus le document.
- [X] Store / `RawTransaction` explicitement placé au centre ; observations/provenance restent autour du RAW durable.
- [X] Job Backfill figé comme producteur historique paramétré, borné et terminable.
- [X] Worker Raw Transaction Ingest figé comme producteur continu start/stop sans signature/program_id/plage/limit de campagne au démarrage.
- [X] Aucun edge, délégation, coordination lifecycle ou checkpoint partagé Job <-> Worker.
- [X] Protocoles classifiés par capability/usage : HTTP peut servir au Worker ; WS/gRPC/replay peuvent servir au Job si la campagne historique l'exige.
- [X] Repair Worker borné à la continuité de son acquisition live ; aucune recherche historique arbitraire.
- [X] `ksp-raw-transaction-lib` conservé uniquement comme lower-layer source-neutral commune ; il ne crée aucune collaboration Job/Worker.
- [X] Matrices provider/réseau/prix/preuve et sources EARLY conservées dans la synthèse réorganisée.
- [X] Correctif documentaire uniquement : `workspace.package.version` reste `0.3.9-pre.6`.
#### `pre.006-fix.002` — identité réseau canonique `mainnet`
- [X] `mainnet` devient l'identité réseau canonique KSP pour Store/RAW/Config futurs.
- [X] `mainnet-beta` devient un alias de compatibilité/historique ou un libellé provider externe, jamais la cible canonique.
- [X] Les libellés provider documentés en `mainnet-beta` restent inchangés lorsqu'ils décrivent réellement l'API externe.
- [X] Les données Mainnet de la phase N1 RAW restent considérées comme données de test et peuvent être droppées/recréées pour normaliser l'identité réseau.
- [X] Aucun changement runtime/Config/Store n'est effectué dans ce correctif documentaire ; la migration technique reste un handoff ultérieur.
- [X] `workspace.package.version` reste `0.3.9-pre.6`.
#### `pre.006-fix.003` — normalisation technique `mainnet`
- [X] `config/std.store.json` et son exemple utilisent `network = mainnet` pour le profil Mainnet.
- [X] `config/std.transport.json`, son exemple et les fixtures utilisent `cluster = mainnet` pour l'identité logique KSP.
- [X] Les endpoints publics engagés utilisent `https://api.mainnet.solana.com` et `wss://api.mainnet.solana.com`.
- [X] Les tests Config, Store API/PostgreSQL, Job Backfill et Backfill/Store Desk concernés utilisent la nouvelle identité canonique.
- [X] Les anciens libellés `mainnet-beta` ne subsistent que dans l'historique documentaire, les anciens deltas/plans ou comme description explicite de l'alias/provider externe.
- [X] Aucun mécanisme de migration des données N1 n'est ajouté ; drop/recréation de la base de test reste admis.
- [X] Gate opérateur complet fourni : audits propres, check/clippy PASS, workspace `--all-targets --all-features` PASS et cinq bundles Desk PASS ; smokes live restent opt-in/ignored.
- [X] Version Cargo corrigée pour la livraison en `0.3.9-pre.6.fix.3` (`VER-ID-010`).
### Gate opérateur `pre.006-fix.003` avant fermeture technique
Le 5 septembre 2026, l'opérateur a exécuté un clean gate complet sur l'état canonique `0.3.9-pre.6.fix.3` après la normalisation technique `mainnet` :
- [X] `cargo clean`.
- [X] `cargo fmt --all`.
- [X] audits Rust complets : General clean, export completeness 0, workspace clean.
- [X] audit Markdown : clean, 332 tables / 743 fichiers.
- [X] `cargo check --workspace`.
- [X] `cargo clippy --workspace --all-targets --all-features -- -D warnings`.
- [X] `cargo test --workspace --all-targets --all-features` ; smokes live/diagnostiques ignorés selon politique.
- [X] cinq builds Tauri `--bundles deb,rpm` terminés.
- [X] `cargo tree --duplicates` exécuté jusqu'à son terme.
Cette preuve ferme la précondition fonctionnelle/configuration de `pre.007`. Elle ne remplace pas le gate final à exécuter après passage mécanique de Cargo à `0.3.9-pre.7`.
## 7. Couloirs de fermeture
### `pre.007` — gate technique final
Premier gate opérateur exécuté le 5 septembre 2026 sur `workspace.package.version = 0.3.9-pre.7` :
- [X] `cargo fmt --all -- --check`.
- [X] audits Rust/Markdown complets : Rust clean, export completeness 0, Markdown clean (332 tables / 744 fichiers).
- [X] `cargo check --workspace`.
- [X] `cargo clippy --workspace --all-targets --all-features -- -D warnings`.
- [ ] `cargo test --workspace --all-targets --all-features` : **FAIL unique observé** dans `ksp-onchain-transport-lib/tests/release_completeness.rs`, canary historique `release_v0_2_9_pre_002_materializes_minimal_yellowstone_engine_without_provider_or_ws_coupling` couplée textuellement à la contrainte workspace `yellowstone-grpc-proto = ^12.6` ; 42 autres tests de ce fichier PASS.
- [X] `cargo test -p ksp-worker-api` : 28 tests PASS, doc-tests PASS.
- [X] `cargo tree -p ksp-worker-api --edges normal` : `ksp-worker-api -> ksp-core-lib` uniquement.
- [X] `cargo tree -p ksp-worker-api -e features` : feature `default` de Core uniquement.
- [X] `cargo tree --duplicates` exécuté après le test ; la résolution compatible observée a notamment avancé `yellowstone-grpc-proto` de `12.6.0` à `12.7.0`.
Le gate n'est donc pas fermé : l'unique défaut est traité par `pre.007-fix.001`.
#### `pre.007-fix.001` — correction de la canary Yellowstone
- [X] Les deux canaries Yellowstone historiques de `ksp-onchain-transport-lib` ne lisent plus le `Cargo.toml` racine pour imposer des versions de dépendances workspace.
- [X] Les assertions Transport propres à la crate sont conservées : consommation `.workspace = true`, features runtime/dev, absence de `yellowstone-grpc-client`, absence de dépendance Config et non-exposition de `tonic`/`yellowstone_grpc_proto`.
- [X] Le correctif respecte `DEP-CARGO-007` : les politiques générales du workspace restent propriété de la surface fondation/Core.
- [X] `fix.001` avait conservé la contrainte racine `yellowstone-grpc-proto = ^12.6`; cette décision de baseline est ensuite explicitement supersédée par `fix.002`.
- [X] `workspace.package.version = 0.3.9-pre.7.fix.1`.
- [X] Revalidation ciblée de `tests/release_completeness.rs` : 43/43 PASS sous la résolution Yellowstone 12.7 observée ensuite.
#### `pre.007-fix.002` — Yellowstone 12.7 et nouvelles obligations du trait serveur
- [X] Baseline workspace `jsonschema = { version = "^0.53", default-features = false }`.
- [X] Baseline workspace `yellowstone-grpc-proto = { version = "^12.7", default-features = false }`.
- [X] Le log opérateur confirme `cargo check --workspace` avec `jsonschema 0.53.0` et `yellowstone-grpc-proto 12.7.0`.
- [X] Le log opérateur isole l'échec `E0046` aux deux fixtures serveur `FixtureGeyser` : nouveaux items requis `SubscribeGossipStream` et `subscribe_gossip`.
- [X] Les deux fixtures implémentent le nouveau RPC avec réponse `UNIMPLEMENTED`; aucune API Gossip de production n'est ajoutée.
- [X] Le fixture `GetVersion` est aligné sur `fixture-yellowstone-12.7`.
- [X] `workspace.package.version = 0.3.9-pre.7.fix.2`.
- [X] `cargo clippy --workspace --all-targets --all-features -- -D warnings` PASS.
- [X] `cargo test --workspace --all-targets --all-features` PASS ; smokes live/operator-only explicitement `ignored`.
- [X] Gate Worker/API PASS et arbres `--edges normal`, `-e features`, `--duplicates` exécutés jusqu'à leur terme.
### Fermeture définitive de `pre.007`
Le gate opérateur final sur `0.3.9-pre.7.fix.2` est fermé :
- [X] audits Rust/Markdown propres avant le gate ciblé ; Markdown 332 tables / 746 fichiers ;
- [X] `cargo check --workspace` PASS ;
- [X] Clippy workspace/all-targets/all-features `-D warnings` PASS ;
- [X] `ksp-onchain-transport-lib --lib` : 385/385 PASS ;
- [X] `ksp-onchain-transport-lib --test release_completeness` : 43/43 PASS ;
- [X] `cargo test --workspace --all-targets --all-features` PASS ; seuls les smokes/probes explicitement opt-in restent `ignored` ;
- [X] `cargo test -p ksp-worker-api` PASS : 32 tests au total, doc-tests sans échec ;
- [X] graphe normal Worker : `ksp-worker-api -> ksp-core-lib` uniquement ;
- [X] feature tree Worker : feature `default` de Core uniquement ;
- [X] `cargo tree --duplicates` exécuté jusqu'à son terme.
Aucun défaut technique ouvert ne subsiste pour `pre.007`.
### `pre.008` — réconciliation documentaire
- [X] Worker API README version-neutral réconcilié sur la surface/responsabilités finales.
- [X] Worker API USAGE version-neutral réconcilié avec exemples réutilisables et distinction lifecycle/runtime.
- [X] Plan/validation réconciliés sur les preuves opérateur réelles de `pre.007-fix.002`.
- [X] Index documentation/architecture réconciliés avec le plan/validation `0.3.9`.
- [X] `009-ACQUISITION_WORKERS_AND_JOBS.md` aligné sur la Worker API figée et sur `ksp-raw-transaction-lib` comme lower-layer commune cible.
- [X] Audit RAW `011` relu comme handoff indépendant `0.3.10` Worker live / `0.3.12` Backfill historique ; aucune correction de contenu nécessaire.
- [X] Aucun CHANGELOG/ROADMAP/prompt suivant finalisé ici.
- [X] `workspace.package.version = 0.3.9-pre.8`.
- [ ] Gate documentaire opérateur post-delta à exécuter.
### `pre.009` — publication
- [ ] Prompt `0.3.10` produit depuis laudit final.
- [ ] CHANGELOG synchronisé.
- [ ] ROADMAP synchronisé.
- [ ] Cargo version mécanique synchronisée selon workflow.
- [ ] Delta publication préparatoire minimal.
### `rel.001`
- [ ] Version stable `0.3.9` mécanique.
- [ ] Aucun rattrapage fonctionnel/documentaire.
- [ ] Commit/tag stable selon workflow.
## 8. Versioning courant
La livraison initiale `pre.001` avait conservé `workspace.package.version = 0.3.8` en suivant lexception du prompt 028. Cette exception est supplantée par la règle normative `VER-ID-009`.
Après la fermeture technique `pre.007` et la réconciliation documentaire `pre.008`, l'état courant est :
```text
workspace.package.version = 0.3.9-pre.8
```
`pre.002` reste la tranche non-fix `0.3.9-pre.2`. Les correctifs de conformité/compilation portent successivement `0.3.9-pre.2.fix.1` puis `0.3.9-pre.2.fix.2` conformément à `VER-ID-007` et `VER-ID-010`. `pre.003` reprend ensuite la séquence prerelease normale avec `0.3.9-pre.3`; son gate opérateur est PASS et freeze Worker API. `pre.004` poursuit avec `0.3.9-pre.4` sans changement runtime, uniquement l'audit standard Solana/KSP et la création de l'owner durable d'acquisition. `pre.005` passe à `0.3.9-pre.5` et enrichit cet owner avec l'audit Helius/Yellowstone/providers/kbot3 sans modifier le runtime. `pre.006` passe à `0.3.9-pre.6` et ferme la synthèse exhaustive possibilités/support/preuve ainsi que les handoffs `0.3.10`/`0.3.12`. `pre.006-fix.001` réécrit ensuite l'owner d'architecture comme synthèse Store-centrique et corrige la séparation Worker live / Job historique sans modifier Cargo ; `pre.006-fix.002` fixe `mainnet` comme identité canonique sans runtime. `pre.006-fix.003` matérialise finalement cette normalisation dans Config/Store/Transport/tests et porte la version Cargo `0.3.9-pre.6.fix.3`. `pre.007` ouvre ensuite le gate technique en `0.3.9-pre.7`; son premier passage révèle une canary Transport historique trop couplée au manifeste racine. `pre.007-fix.001` corrige cet ownership et porte Cargo en `0.3.9-pre.7.fix.1`. L'opérateur choisit ensuite les baselines `jsonschema ^0.53` et `yellowstone-grpc-proto ^12.7`; `pre.007-fix.002` adapte les fixtures serveur au nouveau RPC `SubscribeGossip` de Yellowstone 12.7 et porte Cargo en `0.3.9-pre.7.fix.2`. Le gate global `pre.007` est ensuite PASS. `pre.008` porte l'état documentaire candidat à `0.3.9-pre.8`. L'état précédent `0.3.9-pre.1.fix.2` reste documenté dans `pre.001-fix.002`.

View File

@@ -0,0 +1,839 @@
<!-- file: prompts/029-V0_3_10_START_PROMPT.md -->
<!-- version: 1 -->
# Prompt de démarrage `0.3.10` — normalisation RAW commune + Worker `RawTransaction` live multi-source
## 1. Identité de la release et base exacte
Ouvrir **uniquement** `0.3.10` depuis la release stable/taggée :
```text
v0.3.9
```
La base de travail fournie par l'opérateur est autoritaire sur les souvenirs de session, snippets, anciens deltas ou copies intermédiaires. Avant toute modification, vérifier qu'elle correspond bien à `v0.3.9` et lire les règles/documents obligatoires listés ci-dessous.
La mission de `0.3.10` est double mais cohérente :
```text
1. matérialiser la lower-layer commune ksp-raw-transaction-lib
2. introduire ksp-worker-raw-transaction-ingest-lib comme worker live multi-source V1
```
Ces deux crates servent le même cœur durable `Store / RawTransaction`, mais **ne créent aucune relation fonctionnelle entre Job Backfill et Worker Ingest**.
## 2. Mission et résultat attendu
### 2.1 Centre du modèle
Le résultat durable recherché reste :
```text
sources d'acquisition
|
v
normalisation RAW v1
|
v
ksp-store-lib
|
v
RawTransaction + RawTransactionObservation
```
`RawTransaction` est la vérité RAW persistée. Les observations/provenances décrivent les acquisitions multiples possibles d'une même transaction.
Identité canonique :
```text
RawTransaction identity = (network, signature)
network canonique Mainnet KSP = mainnet
mainnet-beta = alias legacy/externe seulement
```
Un même RAW peut être observé depuis plusieurs providers/protocoles. L'idempotence doit converger vers une seule entité canonique et plusieurs observations ; une divergence de contenu canonique reste un conflit explicite.
### 2.2 `ksp-raw-transaction-lib`
La canonicalisation RAW v1 aujourd'hui prouvée dans `ksp-job-backfill-lib` doit être extraite vers une lower-layer source-neutral commune au Job existant et au nouveau Worker, sans duplication et sans edge Job ↔ Worker.
Responsabilités cibles à confirmer pendant `pre.001` :
```text
format id/version RAW
matériau source-neutral de transaction complète
canonical payload bytes
content hash
construction RawTransaction
construction/projection RawTransactionObservation
validation réseau/signature/slot/meta/version/index
```
Responsabilités interdites :
```text
runtime
HTTP / WS / gRPC
Config/env/secrets
scheduler
Job lifecycle/checkpoint/campagne
Worker lifecycle/source loop
backend Store physique
```
La migration du Job existant doit préserver **byte-for-byte** les golden bytes/hash RAW v1 déjà validés. Aucun RAW v2 n'est justifié par cette extraction.
### 2.3 `ksp-worker-raw-transaction-ingest-lib`
Le Worker est un service continu. Son contrat fonctionnel cible est :
```text
start
-> ouvre les sources techniques activées par la composition
-> acquiert à partir de son démarrage
-> normalise/persiste continuellement
-> publie snapshots/notifications concrètes indépendamment de leurs lecteurs
-> répare uniquement ses propres pertes de continuité live
stop
-> arrêt coopératif/borné
```
Le démarrage **ne reçoit pas** de requête métier historique telle que :
```text
signature
program_id
address
slot range
before/after
historical limit
```
Ces entrées appartiennent au Job Backfill. Le Worker ne lance, n'appelle, n'attend ni ne supervise `ksp-job-backfill-lib`.
Le Worker peut néanmoins utiliser HTTP, replay Yellowstone ou une autre primitive lorsqu'elle sert **son acquisition live ou la réparation de son propre frontier live**. La frontière Worker/Job est définie par l'intention et le lifecycle, jamais par le protocole.
## 3. Sources de vérité internes obligatoires — ordre de lecture
### 3.1 Gouvernance générale
Lire d'abord :
```text
RULES.md
ROADMAP.md
CHANGELOG.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
```
Le prompt complète ces règles ; il ne les remplace pas.
Rappels directement bloquants :
```text
Rust 2024
unsafe / unwrap / expect / panic interdits selon les règles KSP
? interdit en production
retours explicites ; clippy::implicit_return deny
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
pas de pub mod
pub/pub(crate) partagés consommés via crate::Item
item seulement module-local => private
unit tests sous unit_tests/
integration tests sous tests/
```
Après toute modification Rust :
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
```
Pour tout Markdown touché :
```bash
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.10
```
Une commande non exécutée n'est jamais déclarée PASS.
### 3.2 Architecture acquisition autoritaire
Lire intégralement, dans cet ordre :
```text
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.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/008-DATA_MATERIALIZATION_AND_STORE.md
docs/architecture/010-APPS_SERVICES_SCENARIOS_AND_CONTROL.md
```
`011-RAW_TRANSACTION_ACQUISITION.md` est le handoff fonctionnel principal de `0.3.9`. Ne pas revenir à la juxtaposition des anciens audits A/B et ne pas réduire sa matrice aux seules sources actuellement testables.
Décisions déjà fermées :
```text
centre = Store / RawTransaction
Job Backfill = historique paramétré, borné, terminable
Worker Ingest = acquisition continue start/stop sans requête métier historique
Job et Worker = producteurs indépendants
HTTP / WS / gRPC / archive = capabilities, pas rôles
Worker repair = seulement continuité de son run live
support architectural != preuve live
provider model = ouvert/capability-driven
mainnet = identité KSP canonique
mainnet-beta = alias legacy/externe
canonicalisation RAW = lower-layer commune
```
### 3.3 Worker API figée en `0.3.9`
Lire :
```text
crates/ksp-worker-api/Cargo.toml
crates/ksp-worker-api/README.md
crates/ksp-worker-api/USAGE.md
crates/ksp-worker-api/src/
crates/ksp-worker-api/tests/
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
```
Surface générique acquise :
```text
WorkerId
WorkerKindCode
WorkerState
WorkerHealth
WorkerActivity
WorkerLifecycle
WorkerStopToken
WorkerSnapshotSequence
WorkerSnapshot
WorkerSnapshotFuture
WorkerSnapshotSource
```
`ksp-worker-api` est **runtime-neutral**. Il ne fournit pas de `start()/stop()` universel et ne doit pas être élargi pour les besoins Solana du premier worker concret sauf défaut réellement générique démontré.
### 3.4 Backfill et canonicalisation RAW v1 existante
Lire intégralement :
```text
crates/ksp-job-backfill-lib/Cargo.toml
crates/ksp-job-backfill-lib/README.md
crates/ksp-job-backfill-lib/USAGE.md
crates/ksp-job-backfill-lib/src/conversion.rs
crates/ksp-job-backfill-lib/src/
crates/ksp-job-backfill-lib/unit_tests/
crates/ksp-job-backfill-lib/tests/
docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md
docs/validation/023-V0_3_6_JOB_API_BACKFILL.md
```
Inventorier avant extraction :
```text
RAW_TRANSACTION_FORMAT_ID/version
canonical bytes exacts
digest/hash exact
mapping getTransaction -> RawTransaction
mapping provenance/observation
golden vectors
network/signature/slot/block_time guards
error codes/Debug/redaction
```
Le changement autorisé dans Backfill en `0.3.10` est la **migration vers la lower-layer commune** avec comportement inchangé. L'ajout de nouvelles stratégies historiques reste `0.3.12`.
### 3.5 Store
Lire :
```text
crates/ksp-store-api/README.md
crates/ksp-store-api/USAGE.md
crates/ksp-store-api/src/
crates/ksp-store-lib/README.md
crates/ksp-store-lib/USAGE.md
crates/ksp-store-lib/src/
```
Préserver :
```text
RawTransaction identity = network + signature
RawTransactionObservation séparée de l'entité canonique
persistance acquisition atomique/idempotente
conflit de contenu explicite
rétention/tombstone existants
Store façade uniquement pour les consumers ordinaires
aucun backend PostgreSQL direct dans Worker/Common RAW
```
La lower-layer commune doit utiliser la façade/les reexports Store approuvés par l'architecture actuelle et ne doit pas introduire arbitrairement une nouvelle dépendance directe à `ksp-store-api` sans audit de frontière.
### 3.6 Transport
Lire les surfaces actuelles de `ksp-onchain-transport-lib`, notamment :
```text
HTTP observed getTransaction
getBlock / getBlocks / getBlocksWithLimit
WS logsSubscribe
WS signatureSubscribe
WS blockSubscribe
Helius transactionSubscribe
Yellowstone transactions
Yellowstone transaction_status
Yellowstone blocks
Yellowstone blocks_meta / slots
Yellowstone from_slot / replay info / reconnect snapshots
```
Réauditer explicitement les gaps `011` :
```text
TR-B = get_block_observed
TR-C = projection source-neutral des transactions full WS/Yellowstone
TR-D = métadonnées sûres d'acquisition live pour observation uniforme
TR-E = exploitation du from_slot/replay existant sans second moteur Yellowstone
TR-F = adapters EARLY seulement lorsque le protocole est réellement implémenté
```
Ne pas ajouter un second client Yellowstone ou Helius si la surface existante suffit.
### 3.7 Config et composition
Lire :
```text
crates/ksp-config-lib/README.md
crates/ksp-config-lib/USAGE.md
crates/ksp-config-lib/src/transport.rs
config/std.transport.json
config/schemas/std.transport.schema.json
config/examples/std.transport.example.json
.env.example
```
Config reste propriétaire des endpoints, credentials et secrets. Le Worker ne lit jamais directement l'environnement.
Cible conceptuelle à auditer :
```text
source id
network
endpoint ref
capabilities
priority
enabled
settings techniques
```
Une composition peut activer plusieurs sources simultanément. Les paramètres de campagne historique ne doivent pas entrer dans cette configuration Worker.
Le graphe cible actuel de `009` ne suppose pas une dépendance directe Worker -> Config : la composition/adapter supérieur résout la Config en settings source-neutral. Toute divergence doit être argumentée pendant `pre.001` avant codage.
## 4. Sources externes à réauditer pour fraîcheur
La matrice `0.3.9` est une photographie documentée au 4 septembre 2026. `0.3.10-pre.001` doit revalider uniquement ce qui peut avoir changé et qui affecte l'implémentation réelle :
```text
Solana JSON-RPC / PubSub actuels
Yellowstone gRPC upstream/proto actuel
Helius standard WSS / transactionSubscribe / LaserStream
PublicNode Yellowstone
OrbitFlare Yellowstone
providers/tier réellement utilisés dans les smokes
QuickNode / Alchemy / Shyft / Triton / dRPC lorsque leur capability influence le modèle
DoubleZero/feeds EARLY seulement si une intégration réelle est envisagée
```
Utiliser les sources primaires. Distinguer systématiquement :
```text
capability protocolaire
capability documentée provider
support implémenté KSP
preuve live KSP
```
Une branche connue mais inaccessible sur le compte opérateur reste admissible et doit être testable par fixtures/mocks lorsque cela a du sens.
Le workspace stable `v0.3.9` utilise notamment `yellowstone-grpc-proto ^12.7`; vérifier la version réellement courante au début de la tranche avant toute nouvelle dépendance ou adaptation protobuf. Ne pas confondre mise à jour de dépendance et objectif fonctionnel de la release.
## 5. État validé à préserver
`v0.3.9` ferme notamment :
```text
Worker API Core-only/générique
RawTransaction Store backend-neutral + PostgreSQL
RawTransactionObservation/provenance
Backfill HTTP historique existant
Transport HTTP/WS/Helius/Yellowstone existant
Config Transport V3 et profils publics actuels
mainnet canonique
jsonschema ^0.53
yellowstone-grpc-proto ^12.7
```
Les gates `0.3.9` ont validé le workspace complet `--all-targets --all-features`, les 385 tests unitaires Transport, 43 canaries de release Transport, Worker API complet et les arbres Cargo. Ne pas affaiblir ces canaris pour faire passer le nouveau code.
## 6. Architecture cible et dépendances
### 6.1 Lower-layer commune
Cible à confirmer :
```text
ksp-raw-transaction-lib
-> ksp-core-lib # seulement si nécessaire directement
-> ksp-store-lib # façade ; default-features=false
-> serde_json / sha2 # seulement si RAW v1 les requiert encore
```
Pas de Transport, Config, Job API, Worker API, runtime async ou backend physique.
### 6.2 Worker concret
Cible à confirmer :
```text
ksp-worker-raw-transaction-ingest-lib
-> ksp-worker-api
-> ksp-onchain-transport-lib
-> ksp-raw-transaction-lib
-> ksp-store-lib # façade ; default-features=false
-> ksp-logging-lib
```
`ksp-interface-lib` n'est ajouté que si un fait passif réellement partagé apporte une valeur démontrée ; ne pas créer une dépendance par anticipation.
Interdits :
```text
ksp-worker-raw-transaction-ingest-lib -> ksp-job-api
ksp-worker-raw-transaction-ingest-lib -> ksp-job-backfill-lib
ksp-worker-raw-transaction-ingest-lib -> ksp-store-postgres-lib
ksp-worker-raw-transaction-ingest-lib -> provider SDK
lower layer -> Worker/Job
```
### 6.3 Job existant
Après extraction :
```text
ksp-job-backfill-lib
-> ksp-raw-transaction-lib
-> ksp-onchain-transport-lib
-> ksp-store-lib
-> ses dépendances Job/runtime existantes
```
Le Job conserve seul scopes, limites, campagnes, frontier historique, checkpoint et reprise.
## 7. Sources/capabilities Worker V1
Le modèle V1 doit pouvoir représenter au minimum les voies admises par `011`, même lorsque certaines ne peuvent pas être prouvées live immédiatement :
```text
Yellowstone transactions full
Yellowstone blocks full
Yellowstone transaction_status + hydration
WS logsSubscribe + HTTP getTransaction
WS blockSubscribe full
Helius transactionSubscribe full
HTTP live block polling
HTTP transaction hydration
Yellowstone replay/from_slot pour continuité du run
source EARLY via adapter extensible
```
Ne pas coder la sélection sous forme d'un enum fermé de providers. La composition se fait par capabilities et settings techniques.
Une source peut être :
```text
alternative
complémentaire
redondante
spécialisée
```
Plusieurs sources peuvent être actives simultanément.
## 8. Continuité, déduplication et persistence
Le Worker doit distinguer :
```text
reconnect physique
resubscribe
replay adressable
hydration
scan blocs live
repair du frontier live
```
Un reconnect WS ne vaut pas replay.
Le Worker ne cherche pas l'historique arbitraire antérieur à son run. Lorsqu'un gap apparaît pendant son activité, il peut réparer la zone perdue jusqu'à son frontier live avec les capabilities disponibles.
Ordre conceptuel de repair, à auditer/sizer :
```text
replay natif qualifié
source live redondante
scan HTTP blocs
hydration signatures découvertes
fail/degraded explicite si la continuité ne peut pas être prouvée
```
Ne jamais déclarer lossless sans preuve suffisante.
La persistence doit utiliser l'idempotence Store existante. Les duplicates multi-source sont attendus ; une observation supplémentaire n'est pas une seconde entité RAW.
## 9. Notifications et supervision
Le Worker concret doit exposer un état observable compatible avec `ksp-worker-api` :
```text
identity/kind
state
health
activity
sequence
snapshot latest-value
stop request partagé
```
Les notifications/snapshots sont produits indépendamment de la présence de lecteurs. Un consumer lent ou absent ne doit pas devenir propriétaire du lifecycle du Worker.
`pre.001` doit décider le minimum de projection concrète nécessaire pour rates/source health/gap state/compteurs sans élargir `ksp-worker-api` ni inventer un event bus global.
## 10. Hors périmètre `0.3.10`
Ne pas implémenter :
```text
ksp-app-raw-transaction-ingest-desk # 0.3.11
nouvelles stratégies Job Backfill # 0.3.12
campagnes historiques dans le Worker
checkpoint Backfill dans le Worker
Program decode / DEX / materialization
RawAccountState ingest worker
backend PostgreSQL direct dans le Worker
provider SDK
control plane global
nouveau format RAW v2 sans nécessité démontrée
```
Les adaptations du Job autorisées sont limitées à la migration vers la canonicalisation commune et aux tests de non-régression correspondants.
## 11. Première mission `pre.001` — audit, brainstorming et sizing
**Ne pas commencer l'implémentation lourde du Worker en arrivant dans la session.**
`pre.001` doit d'abord produire un plan détaillé de `0.3.10` à partir de la base réelle.
### 11.1 Vérification de base
- vérifier la base/tag/archive `v0.3.9` ;
- lire les règles et sources obligatoires ;
- inventorier les crates/manifests/features réellement présents ;
- exécuter les audits statiques disponibles ;
- ne déclarer aucun gate Cargo PASS sans l'avoir réellement exécuté.
### 11.2 Audit de l'extraction RAW commune
Comparer précisément le code actuel Backfill avec la cible `ksp-raw-transaction-lib` :
```text
items à déplacer
items à laisser Job-owned
dépendances exactes nécessaires
golden bytes/hash à préserver
public API minimale de la common crate
erreurs/validation/redaction
absence de cycle de dépendances
```
Décider le découpage avant de déplacer du code.
### 11.3 Audit Transport/Config pour chaque capability live
Construire une matrice implementation-ready avec au moins :
```text
capability
méthode/protocole
surface KSP existante
gap exact
donnée complète ou hydration requise
provenance disponible
continuity/replay semantics
network/provider testable aujourd'hui
adaptation Transport nécessaire
adaptation Config nécessaire
fixture test
live smoke possible
```
Réutiliser les IDs `TR-B` à `TR-F` de `011` ou les superséder explicitement si l'audit montre une meilleure découpe.
### 11.4 Audit du runtime Worker
Décider avant codage :
```text
ownership du runtime/task
start/stop concret
source supervisor
multi-source concurrency
bounded channels/backpressure
source health
latest-value status
admission/dedup/persistence flow
gap detection + repair
shutdown order
terminal fault policy
```
Aucun paramètre métier historique ne doit apparaître dans le contrat start.
### 11.5 Audit des preuves
Séparer :
```text
unit/fixture deterministic
integration local
provider live gratuit
provider live bloqué par tier
preuve de replay
preuve de continuity repair
Store persistence proof
```
Les tests live restent opt-in/ignored si secrets, endpoint externe, tier ou coût sont requis.
### 11.6 Sizing et planification
Produire un plan dédié `0.3.10` et une validation dédiée avant implémentation lourde.
Chaque prerelease intermédiaire visée à plus d'environ 15-20 minutes de travail effectif doit être scindée. Réserver explicitement les couloirs finaux :
```text
gate technique/live
réconciliation documentaire
préparation de publication
rel.001
```
### 11.7 Critères de sortie de `pre.001`
Le gate `pre.001` est fermé seulement si les points suivants sont explicites :
```text
boundary exacte ksp-raw-transaction-lib
migration Backfill sans changement RAW v1
surface publique minimale du Worker concret
runtime/source supervision décidés
capability matrix Worker implementation-ready
TR-B..TR-F réévalués
Config/source settings décidés sans secret leak
continuité/gap repair bornés
notification/snapshot contract concret
multi-source dedup/provenance/persistence décidés
provider/access/proof matrix revalidée
liste de tests/smokes
dependency graph cible
prévision souple détaillée et dimensionnée
aucune implémentation lourde commencée avant cohérence du plan
```
## 12. Prévision souple initiale des prereleases
Cette prévision est un point de départ à recalibrer par `pre.001`, pas un calendrier rigide.
### `pre.001` — audit/sizing/plan
Lecture complète, audit lower-layer/Transport/Config/Worker runtime, fraîcheur providers, dependency graph, threat model, tests et plan détaillé.
### `pre.002` — `ksp-raw-transaction-lib` foundation
Créer la common crate, déplacer la canonicalisation RAW v1 sans changer les golden bytes/hash, migrer Backfill vers elle et verrouiller les frontières de dépendances.
### `pre.003` — Worker foundation/runtime
Créer `ksp-worker-raw-transaction-ingest-lib`, lifecycle concret, start/stop, source supervision abstraite, snapshots/latest-value, persistence pipeline minimal et shutdown borné sans source live complexe.
### `pre.004` — Transport/Common acquisition gaps P0
Matérialiser les adaptations source-neutral indispensables : `get_block_observed`, transaction material/provenance commune et autres gaps réellement confirmés par `pre.001`.
### `pre.005` — Yellowstone live
Brancher transactions/blocks/status+hydration, multi-source admission, continuity metadata et replay `from_slot` uniquement pour le frontier live du Worker.
### `pre.006` — WS/HTTP live
Brancher logs+hydration, blockSubscribe, HTTP live block polling/hydration et Helius `transactionSubscribe` existant selon Config/capabilities.
### `pre.007` — multi-source hardening
Déduplication, observations multiples, backpressure, source health, failure isolation, continuity/gap repair et adversarial tests.
### `pre.008` — providers/config/smokes accessibles
Compléter les profils/capabilities réellement nécessaires, exécuter les smokes gratuits disponibles Mainnet/Devnet/Testnet et conserver les branches payantes derrière fixtures/opt-in.
### `pre.009` — extensions EARLY réellement accessibles ou tranche de consolidation
N'implémenter une source EARLY vendor-specific que si son protocole, son accès et son rôle sont suffisamment prouvés. Sinon utiliser cette tranche pour consolidation/hardening plutôt que créer un faux support.
### `pre.010` — gate technique/live final
Workspace complet, graphes de dépendances, tests live pertinents, continuité/recovery et preuves Store.
### `pre.011` — réconciliation documentaire finale
README/USAGE/architecture/plan/validation uniquement.
### `pre.012` — préparation de publication
Prompt `0.3.11`, CHANGELOG, ROADMAP uniquement, plus fichiers mécaniques de version/delta.
### `rel.001` — publication mécanique stable
Publication `v0.3.10` sans rattrapage fonctionnel ou documentaire.
`pre.001` peut scinder, fusionner ou insérer des tranches/fixes selon le résultat réel de l'audit. Il doit préserver l'ordre des responsabilités de fermeture.
## 13. Sécurité et hardening spécifiques
Préserver au minimum :
```text
aucun secret/URL/header/token dans Debug ou erreurs publiques
aucun raw transaction payload dans tracing par défaut
bornes explicites sur queues/buffers/filter sets
pas de panic/unwrap/expect/unsafe
pas de provider body/message copié dans ErrorContext
aucune rétention d'un endpoint secret dans snapshot
source failure isolée lorsque possible
shutdown/reconnect bornés
conflit de contenu canonique terminal/explicite selon policy décidée
no-loss claim interdit sans preuve
```
Les diagnostics Worker doivent être utiles sans exposer signatures/payloads lorsque leur exposition n'est pas nécessaire.
## 14. Validation Rust et dépendances
Pendant les tranches Rust :
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.10
cargo check --workspace
cargo clippy --workspace --all-targets
```
Puis tests ciblés des crates touchées.
Pour les gates techniques :
```bash
cargo test --workspace --all-targets --all-features
cargo tree -p ksp-raw-transaction-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
cargo tree --duplicates
```
Adapter les graphes si le plan `pre.001` modifie réellement les noms/edges, sans supprimer le contrôle de dépendances.
Les smokes live doivent être explicitement opt-in lorsqu'ils nécessitent réseau externe, secrets ou accès payant.
## 15. Versionnement, deltas et publication
- chaque nouvelle prerelease non-fix synchronise `workspace.package.version` selon `VER-ID-009` ;
- `0.3.10-pre.001` correspond à Cargo `0.3.10-pre.1` ;
- les correctifs utilisent la forme Cargo `0.3.10-pre.N.fix.M` ;
- chaque livraison a son delta minimal sous `deltas/0.3.10/` ;
- les archives d'échange suivent `VER-ARCHIVE-*` ;
- aucun lockfile/cache/secret/build output dans les deltas ;
- chaque delta est commité selon les règles Git KSP ;
- seul le stable final reçoit le tag `v0.3.10`.
Ne jamais réécrire un delta déjà publié pour masquer un défaut ; ouvrir un fix ou une tranche appropriée.
## 16. Critères de clôture de `0.3.10`
La release peut fermer lorsque :
```text
ksp-raw-transaction-lib possède une canonicalisation RAW v1 unique et prouvée
Backfill utilise cette common crate sans changement fonctionnel de campagne
Worker live démarre/s'arrête sans paramètres métier historiques
plusieurs sources peuvent être actives simultanément
les voies P0 Yellowstone + WS/HTTP admises sont représentées/implémentées selon plan
les adaptations Transport/Config sont capability-driven et minimales
RawTransaction + observations convergent correctement dans Store
multi-source duplicates et conflits sont traités explicitement
repair ne dépasse pas la continuité du run live
snapshots/notifications sont receiver-independent et sûrs
les branches non testables live restent testées déterministiquement quand possible
aucun edge Job <-> Worker
aucun backend physique/provider SDK dans le Worker
gate technique/live final vert
réconciliation documentaire séparée
prompt 0.3.11/CHANGELOG/ROADMAP préparés séparément
```
## 17. Release suivante envisagée
`0.3.11` introduira `ksp-app-raw-transaction-ingest-desk` pour sélectionner et superviser une ou plusieurs sources/méthodes offertes par le Worker `0.3.10`, sans recopier discovery, hydration, dedup, persistence ou recovery.
`0.3.12` reviendra ensuite sur `ksp-job-backfill-lib`/Desk pour les stratégies historiques multi-source/multi-protocole. Ne pas anticiper ce travail dans `0.3.10`.
## 18. Instruction d'ouverture
Au début de la prochaine session :
1. vérifier que la base fournie correspond exactement à `v0.3.9` ;
2. lire les règles, `009`, `011`, Worker API, Backfill conversion, Store, Transport et Config dans l'ordre prescrit ;
3. réauditer les dépendances/provider capabilities dont la fraîcheur affecte réellement l'implémentation ;
4. produire **`pre.001` comme audit/sizing/plan**, avec dependency graph, capability matrix, threat model et stratégie de tests ;
5. **ne pas commencer l'implémentation lourde de `ksp-raw-transaction-lib` ou du Worker avant fermeture cohérente de ce gate**.
L'objectif n'est pas de coder le chemin le plus facile avec les comptes gratuits actuels. L'objectif est de construire un socle live multi-source maximal, capability-driven et extensible, tout en distinguant clairement support architectural, support implémenté et preuve live disponible.