Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9047af626 | |||
| 52d692e8ec | |||
| 5c3c8b3fef | |||
| b0079fc3ee | |||
| ecfa123a79 | |||
| 9da372e4a2 | |||
| 9a998e7b52 | |||
| c3c9a80310 | |||
| 116eabba7d | |||
| e6c2649410 | |||
| e85d6f6963 | |||
| eb39aaa904 | |||
| bb7ee3cc3a | |||
| 84647c12ea | |||
| 156d237460 | |||
| 7d0710a1e6 | |||
| 5c797827f7 | |||
| b87b7f4505 | |||
| 92f08ca5a8 |
10
.env.example
10
.env.example
@@ -1,5 +1,5 @@
|
||||
# file: .env.example
|
||||
# version: 13
|
||||
# version: 14
|
||||
|
||||
# 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.
|
||||
@@ -28,16 +28,16 @@ KSP_WALLETS_DIRECTORY=wallets
|
||||
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.
|
||||
# The committed Transport document falls back to https://api.mainnet-beta.solana.com when this variable is absent.
|
||||
KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL=https://api.mainnet-beta.solana.com
|
||||
# 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.solana.com
|
||||
|
||||
# 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.
|
||||
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.
|
||||
# The committed Transport document falls back to wss://api.mainnet-beta.solana.com when this variable is absent.
|
||||
KSP_PUBLIC_SOLANA_MAINNET_WS_URL=wss://api.mainnet-beta.solana.com
|
||||
# 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.solana.com
|
||||
|
||||
# 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.
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,8 +1,22 @@
|
||||
<!-- file: CHANGELOG.md -->
|
||||
<!-- version: 28 -->
|
||||
<!-- version: 29 -->
|
||||
|
||||
# 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` 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.
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 475
|
||||
# version: 489
|
||||
|
||||
[workspace]
|
||||
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]
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
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 }
|
||||
getrandom = { version = "^0.4", 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 }
|
||||
rustls = { version = "^0.23", 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-prost = { version = "^0.14", default-features = false }
|
||||
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" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: README.md -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 11 -->
|
||||
|
||||
# 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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
12
ROADMAP.md
12
ROADMAP.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: ROADMAP.md -->
|
||||
<!-- version: 103 -->
|
||||
<!-- version: 104 -->
|
||||
|
||||
# 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.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 n’est 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.
|
||||
- [ ] `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`.
|
||||
- [ ] `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.
|
||||
- [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-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.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** — maintenir la matrice d’admission 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.
|
||||
- [ ] **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 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 `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 `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.
|
||||
- [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.
|
||||
- [ ] **TODO** — statut/commitment transactionnel restant : `0.3.5` couvre uniquement le fait passif d’exécution `slot + signature + outcome`; réauditer séparément `signatureSubscribe` et `getSignatureStatuses` lorsqu’un 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 d’un 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-*`.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"profile_id": "mainnet",
|
||||
"network": "mainnet-beta",
|
||||
"network": "mainnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"name": "mainnet_public",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
|
||||
"cluster": "mainnet",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
|
||||
"connect_timeout_ms": 5000,
|
||||
"request_timeout_ms": 15000,
|
||||
"max_idle_connections_per_host": 8,
|
||||
@@ -57,7 +57,7 @@
|
||||
"name": "mainnet_private",
|
||||
"enabled": true,
|
||||
"provider": "private-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"url": "${KSP_SECRET_SOLANA_HTTP_URL:-https://example.invalid}",
|
||||
"connect_timeout_ms": 5000,
|
||||
"request_timeout_ms": 10000,
|
||||
@@ -85,15 +85,15 @@
|
||||
"name": "mainnet_public_ws",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"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",
|
||||
"enabled": true,
|
||||
"provider": "helius",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"kind": "helius_laserstream",
|
||||
"url": "wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-replace-me}",
|
||||
"session": {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"profile_id": "mainnet",
|
||||
"network": "mainnet-beta",
|
||||
"network": "mainnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",
|
||||
|
||||
@@ -144,8 +144,8 @@
|
||||
"name": "solana_mainnet_public",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
|
||||
"cluster": "mainnet",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
|
||||
"connect_timeout_ms": 5000,
|
||||
"request_timeout_ms": 15000,
|
||||
"max_idle_connections_per_host": 8,
|
||||
@@ -172,9 +172,9 @@
|
||||
"name": "solana_mainnet_public_ws",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"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",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
|
||||
"cluster": "mainnet",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
|
||||
"connect_timeout_ms": 5000,
|
||||
"request_timeout_ms": 15000,
|
||||
"max_idle_connections_per_host": 8,
|
||||
@@ -225,7 +225,7 @@
|
||||
"name": "publicnode_solana_mainnet_http",
|
||||
"enabled": true,
|
||||
"provider": "publicnode",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"url": "https://solana-rpc.publicnode.com",
|
||||
"connect_timeout_ms": 5000,
|
||||
"request_timeout_ms": 15000,
|
||||
@@ -267,9 +267,9 @@
|
||||
"name": "solana_mainnet_public_ws",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"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",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
|
||||
"cluster": "mainnet",
|
||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet.solana.com}",
|
||||
"connect_timeout_ms": 5000,
|
||||
"request_timeout_ms": 15000,
|
||||
"max_idle_connections_per_host": 8,
|
||||
@@ -308,9 +308,9 @@
|
||||
"name": "solana_mainnet_public_ws",
|
||||
"enabled": true,
|
||||
"provider": "solana-public",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"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": [
|
||||
@@ -318,7 +318,7 @@
|
||||
"name": "publicnode_solana_mainnet_yellowstone",
|
||||
"enabled": true,
|
||||
"provider": "publicnode",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"protocol": "solana_yellowstone",
|
||||
"url": "https://solana-yellowstone-grpc.publicnode.com:443",
|
||||
"secret_metadata": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/unit_tests/backfill_request.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
fn ready_options() -> crate::BackfillDeskOptionsDto {
|
||||
let limits = crate::backfill_request_limits();
|
||||
@@ -22,7 +22,7 @@ fn ready_options() -> crate::BackfillDeskOptionsDto {
|
||||
return crate::BackfillDeskOptionsDto {
|
||||
commitments: crate::backfill_commitment_codes(),
|
||||
composition_ready: true,
|
||||
configured_networks: vec!["mainnet-beta".to_owned()],
|
||||
configured_networks: vec!["mainnet".to_owned()],
|
||||
http_routes: vec![
|
||||
crate::BackfillHttpRouteOptionDto {
|
||||
pooled: true,
|
||||
@@ -36,7 +36,7 @@ fn ready_options() -> crate::BackfillDeskOptionsDto {
|
||||
program_id_options: crate::program_id_autocomplete_options(),
|
||||
scope_kinds: crate::backfill_scope_kind_codes(),
|
||||
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,
|
||||
transport_diagnostic: std::option::Option::None,
|
||||
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::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.commitment(), ksp_job_backfill_lib::BackfillCommitment::Finalized);
|
||||
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 {
|
||||
assert!(!serialized.contains("11111111111111111111111111111111"));
|
||||
assert!(!serialized.contains(&"1".repeat(64)));
|
||||
assert!(serialized.contains("mainnet-beta"));
|
||||
assert!(serialized.contains("mainnet"));
|
||||
assert!(serialized.contains("backfill_pool"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/unit_tests/backfill_run.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[test]
|
||||
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::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::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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>> {
|
||||
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::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::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn store_health_mapping_is_explicit_and_conservative() {
|
||||
@@ -10,7 +10,7 @@ fn store_health_mapping_is_explicit_and_conservative() {
|
||||
#[test]
|
||||
fn store_transport_network_coherence_requires_exact_logical_identity() {
|
||||
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());
|
||||
if let std::result::Result::Err(error) = mismatch {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_STORE_NETWORK_MISMATCH);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-store-desk/unit_tests/dto_common.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
#[test]
|
||||
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,
|
||||
health_state: "ready".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,
|
||||
pool_available: 1,
|
||||
pool_capacity: 8,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"tracing:default",
|
||||
"dialog:default"
|
||||
"tracing:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"@fltsci/tauri-plugin-tracing": "^0.3",
|
||||
"@fortawesome/fontawesome-free": "^7.3",
|
||||
"@tauri-apps/api": "^2.11",
|
||||
"@tauri-apps/plugin-dialog": "^2.7",
|
||||
"bootstrap": "^5.3",
|
||||
"datatables.net-bs5": "^3.0",
|
||||
"datatables.net-select-bs5": "^4.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
||||
<!-- version: 14 -->
|
||||
<!-- version: 15 -->
|
||||
|
||||
# Utilisation de ksp-config-lib
|
||||
|
||||
@@ -250,7 +250,7 @@ Targets committed :
|
||||
|
||||
```text
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"profile_id": "mainnet",
|
||||
"network": "mainnet-beta",
|
||||
"network": "mainnet",
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"connection_uri": "${KSP_SECRET_STORE_MAINNET_POSTGRES_URI:-postgresql://localhost/ksp_mainnet}",
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"name": "fixture_helius_ws",
|
||||
"enabled": true,
|
||||
"provider": "helius",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"kind": "helius_laserstream",
|
||||
"url": "wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-fixture-helius-key}"
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"name": "fixture_http",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"url": "https://fallback.invalid",
|
||||
"connect_timeout_ms": 1000,
|
||||
"request_timeout_ms": 3000,
|
||||
@@ -73,7 +73,7 @@
|
||||
"name": "fixture_ws",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"kind": "solana_standard",
|
||||
"url": "wss://fallback.invalid"
|
||||
}
|
||||
@@ -83,7 +83,7 @@
|
||||
"name": "fixture_grpc",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"protocol": "solana_yellowstone",
|
||||
"url": "https://grpc.invalid",
|
||||
"metadata": [
|
||||
@@ -115,7 +115,7 @@
|
||||
"name": "fixture_http",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"url": "https://fallback.invalid",
|
||||
"connect_timeout_ms": 1000,
|
||||
"request_timeout_ms": 3000,
|
||||
@@ -143,7 +143,7 @@
|
||||
"name": "fixture_ws",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"kind": "solana_standard",
|
||||
"url": "wss://fallback.invalid"
|
||||
}
|
||||
@@ -153,7 +153,7 @@
|
||||
"name": "fixture_grpc",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"protocol": "solana_yellowstone",
|
||||
"url": "https://grpc.invalid",
|
||||
"metadata": [
|
||||
@@ -180,7 +180,7 @@
|
||||
"name": "fixture_http",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"url": "https://fallback.invalid",
|
||||
"connect_timeout_ms": 1000,
|
||||
"request_timeout_ms": 3000,
|
||||
@@ -208,7 +208,7 @@
|
||||
"name": "fixture_ws",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"kind": "solana_standard",
|
||||
"url": "wss://fallback.invalid"
|
||||
}
|
||||
@@ -218,7 +218,7 @@
|
||||
"name": "fixture_grpc",
|
||||
"enabled": true,
|
||||
"provider": "fixture-provider",
|
||||
"cluster": "mainnet-beta",
|
||||
"cluster": "mainnet",
|
||||
"protocol": "solana_yellowstone",
|
||||
"url": "https://grpc.invalid",
|
||||
"metadata": [],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/store.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
#[test]
|
||||
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());
|
||||
for (target_id, network, expected_uri) in [
|
||||
("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"),
|
||||
] {
|
||||
let resolved = engine.load_resolved_store_config(std::option::Option::Some(target_id), &environment);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/transport.rs
|
||||
// version: 12
|
||||
// version: 13
|
||||
|
||||
#[test]
|
||||
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];
|
||||
assert_eq!(helius.name(), "fixture_helius_ws");
|
||||
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.url().as_str(), "wss://mainnet.helius-rpc.com/?api-key=fixture-helius-key");
|
||||
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 {
|
||||
assert_eq!(mainnet.profile_id(), "mainnet_public");
|
||||
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].url().as_str(), "https://api.mainnet-beta.solana.com");
|
||||
assert_eq!(mainnet.settings().endpoints()[0].cluster().as_str(), "mainnet");
|
||||
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");
|
||||
let ws = mainnet.ws_settings();
|
||||
assert!(ws.is_some(), "committed V3 Mainnet profile should expose WebSocket settings");
|
||||
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_solana_mainnet_yellowstone",
|
||||
"mainnet-beta",
|
||||
"mainnet",
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443",
|
||||
"KSP_SECRET_PUBLICNODE_MAINNET_GRPC_X_TOKEN",
|
||||
"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);
|
||||
let endpoint = &ws.endpoints()[1];
|
||||
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.url().as_str(), "wss://mainnet.helius-rpc.com/?api-key=helius-api-key-canary");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-job-backfill-lib/unit_tests/request.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
fn signature(character: char) -> std::option::Option<crate::BackfillSignature> {
|
||||
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]
|
||||
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::None => return,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
|
||||
<!-- version: 23 -->
|
||||
<!-- version: 24 -->
|
||||
|
||||
# Utilisation de `ksp-onchain-transport-lib`
|
||||
|
||||
@@ -281,7 +281,7 @@ let grpc_endpoint = match ksp_onchain_transport_lib::YellowstoneGrpcEndpointSett
|
||||
"publicnode_mainnet_yellowstone",
|
||||
true,
|
||||
ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new("publicnode"),
|
||||
ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new("mainnet-beta"),
|
||||
ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new("mainnet"),
|
||||
grpc_url,
|
||||
ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings::default(),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -1000,14 +1000,10 @@ fn release_v0_2_8_pre_010_live_smoke_policy_preserves_secret_and_dependency_owne
|
||||
#[test]
|
||||
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 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 settings_source = include_str!("../src/grpc_settings.rs");
|
||||
let channel_source = include_str!("../src/grpc_channel.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("yellowstone-grpc-proto.workspace = true"));
|
||||
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]
|
||||
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 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 settings_source = include_str!("../src/grpc_settings.rs");
|
||||
let channel_source = include_str!("../src/grpc_channel.rs");
|
||||
let unary_source = include_str!("../src/grpc_unary.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 = [\"codegen\", \"server\"] }"));
|
||||
assert!(transport_manifest.contains("tonic-prost.workspace = true"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -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"]
|
||||
async fn publicnode_mainnet_yellowstone_streams_slots_with_network_scoped_secret_x_token() {
|
||||
let tokens = publicnode_x_tokens();
|
||||
assert_publicnode_slot_stream(
|
||||
"publicnode_mainnet_yellowstone",
|
||||
"mainnet-beta",
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443",
|
||||
tokens.mainnet.as_str(),
|
||||
)
|
||||
.await;
|
||||
assert_publicnode_slot_stream("publicnode_mainnet_yellowstone", "mainnet", "https://solana-yellowstone-grpc.publicnode.com:443", tokens.mainnet.as_str())
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
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"));
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
request: tonic::Request<yellowstone_grpc_proto::geyser::SubscribeReplayInfoRequest>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_unary.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
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"));
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
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::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!(validity.valid());
|
||||
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:?}");
|
||||
assert!(!rendered.contains("GRPC-SECRET-CANARY"));
|
||||
assert!(!rendered.contains(server.endpoint_url.as_str()));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/tests/public_api.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! 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]
|
||||
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::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);
|
||||
assert_eq!(transaction.slot(), 123);
|
||||
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!(ksp_store_api::ERROR_CODE_RAW_PAYLOAD_INVALID.domain(), "store_api");
|
||||
return;
|
||||
@@ -83,7 +83,7 @@ fn public_pre_003_surface_keeps_backend_and_structural_types_out() {
|
||||
|
||||
#[test]
|
||||
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::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::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::Err(_) => return,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/tests/security_hardening.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! 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_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::Err(_) => return,
|
||||
};
|
||||
@@ -125,7 +125,7 @@ fn pre_007_retention_outcome_distinguishes_lost_compare_and_transition_race() {
|
||||
|
||||
#[test]
|
||||
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::Err(_) => return,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_account.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
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::Err(_) => std::option::Option::None,
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_inspection.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Unit tests for backend-neutral RAW inspection contracts.
|
||||
|
||||
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::Err(_) => std::option::Option::None,
|
||||
};
|
||||
@@ -61,7 +61,7 @@ fn inspection_queries_keep_backend_neutral_filters_and_random_access_page() {
|
||||
};
|
||||
let transaction =
|
||||
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.slots().end_inclusive(), std::option::Option::Some(20));
|
||||
assert_eq!(transaction.direction(), crate::RawSortDirection::Descending);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_pagination.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
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]
|
||||
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::Err(_) => return,
|
||||
};
|
||||
@@ -45,7 +45,7 @@ fn transaction_query_preserves_caller_limit_range_direction_and_network() {
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
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.page().limit().get(), 5_000_000);
|
||||
assert_eq!(query.direction(), crate::RawSortDirection::Ascending);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_primitives.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
fn code(value: &str) -> std::option::Option<crate::RawProvenanceCode> {
|
||||
return match crate::RawProvenanceCode::new(value.to_owned()) {
|
||||
@@ -17,7 +17,7 @@ fn timestamp(unix_millis: u64) -> std::option::Option<crate::RawTimestamp> {
|
||||
|
||||
#[test]
|
||||
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());
|
||||
let format = crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned());
|
||||
assert!(format.is_ok());
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_retention.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
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::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_transaction.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
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::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 reference = crate::RawTransactionReference::new(network, signature);
|
||||
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.slot(), 42);
|
||||
assert!(transaction.block_time().is_none());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/unit_tests/raw_account.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
fn network() -> ksp_store_api::RawNetworkId {
|
||||
return match ksp_store_api::RawNetworkId::new("devnet") {
|
||||
@@ -311,7 +311,7 @@ fn pre_004_account_observation_rejects_hostile_rows_without_echoing_values() {
|
||||
#[test]
|
||||
fn pre_004_account_wrong_network_guard_is_pre_io_and_static() {
|
||||
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::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:?}"),
|
||||
};
|
||||
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::Err(error) => panic!("valid foreign network rejected: {error:?}"),
|
||||
};
|
||||
@@ -437,7 +437,7 @@ fn pre_005_account_origin_encoding_is_exact_and_static() {
|
||||
#[test]
|
||||
fn pre_006_additional_observation_input_guard_rejects_wrong_network_before_io() {
|
||||
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::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::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 =
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
fn network() -> ksp_store_api::RawNetworkId {
|
||||
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]
|
||||
fn pre_004_wrong_network_is_rejected_by_the_private_pre_io_guard() {
|
||||
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::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 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));
|
||||
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::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::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_range = page_query("devnet", Some(11), Some(20), ksp_store_api::RawSortDirection::Ascending, None);
|
||||
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]
|
||||
fn pre_007_retention_transition_pre_io_guard_rejects_wrong_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::Err(error) => panic!("valid alternate network rejected: {error:?}"),
|
||||
};
|
||||
|
||||
14
crates/ksp-worker-api/Cargo.toml
Normal file
14
crates/ksp-worker-api/Cargo.toml
Normal 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
|
||||
69
crates/ksp-worker-api/README.md
Normal file
69
crates/ksp-worker-api/README.md
Normal 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.
|
||||
149
crates/ksp-worker-api/USAGE.md
Normal file
149
crates/ksp-worker-api/USAGE.md
Normal 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.
|
||||
12
crates/ksp-worker-api/src/error.rs
Normal file
12
crates/ksp-worker-api/src/error.rs
Normal 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");
|
||||
76
crates/ksp-worker-api/src/identity.rs
Normal file
76
crates/ksp-worker-api/src/identity.rs
Normal 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;
|
||||
62
crates/ksp-worker-api/src/lib.rs
Normal file
62
crates/ksp-worker-api/src/lib.rs
Normal 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;
|
||||
199
crates/ksp-worker-api/src/lifecycle.rs
Normal file
199
crates/ksp-worker-api/src/lifecycle.rs
Normal 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;
|
||||
143
crates/ksp-worker-api/src/snapshot.rs
Normal file
143
crates/ksp-worker-api/src/snapshot.rs
Normal 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;
|
||||
44
crates/ksp-worker-api/src/stop.rs
Normal file
44
crates/ksp-worker-api/src/stop.rs
Normal 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;
|
||||
128
crates/ksp-worker-api/tests/dependency_boundary.rs
Normal file
128
crates/ksp-worker-api/tests/dependency_boundary.rs
Normal 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;
|
||||
}
|
||||
52
crates/ksp-worker-api/tests/public_api.rs
Normal file
52
crates/ksp-worker-api/tests/public_api.rs
Normal 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;
|
||||
}
|
||||
161
crates/ksp-worker-api/tests/release_completeness.rs
Normal file
161
crates/ksp-worker-api/tests/release_completeness.rs
Normal 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;
|
||||
}
|
||||
185
crates/ksp-worker-api/tests/security_hardening.rs
Normal file
185
crates/ksp-worker-api/tests/security_hardening.rs
Normal 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;
|
||||
}
|
||||
219
crates/ksp-worker-api/tests/snapshot_source.rs
Normal file
219
crates/ksp-worker-api/tests/snapshot_source.rs
Normal 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;
|
||||
}
|
||||
64
crates/ksp-worker-api/unit_tests/identity.rs
Normal file
64
crates/ksp-worker-api/unit_tests/identity.rs
Normal 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;
|
||||
}
|
||||
151
crates/ksp-worker-api/unit_tests/lifecycle.rs
Normal file
151
crates/ksp-worker-api/unit_tests/lifecycle.rs
Normal 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;
|
||||
}
|
||||
66
crates/ksp-worker-api/unit_tests/snapshot.rs
Normal file
66
crates/ksp-worker-api/unit_tests/snapshot.rs
Normal 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;
|
||||
}
|
||||
22
crates/ksp-worker-api/unit_tests/stop.rs
Normal file
22
crates/ksp-worker-api/unit_tests/stop.rs
Normal 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;
|
||||
}
|
||||
95
deltas/0.3.9/pre.001-fix.001.md
Normal file
95
deltas/0.3.9/pre.001-fix.001.md
Normal 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 l’architecture Worker API, ni le découpage technique retenu, ni les budgets, ni les sorties attendues, ni l’ordre 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`, l’opé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 n’est pas présentée comme une exécution locale du sandbox d’assemblage.
|
||||
|
||||
## Version Cargo
|
||||
|
||||
Correctif strictement documentaire :
|
||||
|
||||
```text
|
||||
workspace.package.version reste 0.3.8
|
||||
```
|
||||
|
||||
Aucun `Cargo.toml`, code Rust, runtime, build ou configuration n’est 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
|
||||
```
|
||||
116
deltas/0.3.9/pre.001-fix.002.md
Normal file
116
deltas/0.3.9/pre.001-fix.002.md
Normal 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 l’anomalie 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 l’exception 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 lorsqu’elle 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`. Puisqu’elle modifie `Cargo.toml`, `VER-ID-007` impose la synchronisation de `workspace.package.version` avec l’identifiant 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` n’est 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 l’entré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
|
||||
|
||||
L’opé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 d’assemblage du présent fix
|
||||
|
||||
Exécuté sur l’arbre 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` n’est pas disponible dans l’environnement d’assemblage ; aucun `cargo fmt/check/clippy/test/tree` du présent fix n’est 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
187
deltas/0.3.9/pre.001.md
Normal 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 l’architecture générique `ksp-worker-api`, threat map, tests et sizing, **sans créer la crate Worker ni commencer l’audit 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é n’a été détecté.
|
||||
|
||||
La source de code reste exclusivement KSP `v0.3.8`. kbot3 n’a pas été utilisé pour concevoir Worker API ; son futur usage est réservé à la référence **fonctionnelle historique** lors de l’audit 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 l’environnement d’assemblage. 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 n’est modifié ou supprimé. Le cache Python créé par l’audit 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 l’API 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 n’est 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
|
||||
```
|
||||
|
||||
L’audit 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
|
||||
```
|
||||
|
||||
L’owner prévu du document d’audit 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 n’est modifié. Conformément à l’autorisation 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.
|
||||
114
deltas/0.3.9/pre.002-fix.001.md
Normal file
114
deltas/0.3.9/pre.002-fix.001.md
Normal 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
|
||||
```
|
||||
96
deltas/0.3.9/pre.002-fix.002.md
Normal file
96
deltas/0.3.9/pre.002-fix.002.md
Normal 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
202
deltas/0.3.9/pre.002.md
Normal 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
145
deltas/0.3.9/pre.003.md
Normal 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
60
deltas/0.3.9/pre.004.md
Normal 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 l’architecture 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
71
deltas/0.3.9/pre.005.md
Normal 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.
|
||||
135
deltas/0.3.9/pre.006-fix.001.md
Normal file
135
deltas/0.3.9/pre.006-fix.001.md
Normal 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é.
|
||||
51
deltas/0.3.9/pre.006-fix.002.md
Normal file
51
deltas/0.3.9/pre.006-fix.002.md
Normal 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é.
|
||||
52
deltas/0.3.9/pre.006-fix.003.md
Normal file
52
deltas/0.3.9/pre.006-fix.003.md
Normal 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
114
deltas/0.3.9/pre.006.md
Normal 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.
|
||||
78
deltas/0.3.9/pre.007-fix.001.md
Normal file
78
deltas/0.3.9/pre.007-fix.001.md
Normal 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`.
|
||||
75
deltas/0.3.9/pre.007-fix.002.md
Normal file
75
deltas/0.3.9/pre.007-fix.002.md
Normal 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
76
deltas/0.3.9/pre.007.md
Normal 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
42
deltas/0.3.9/pre.008.md
Normal 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
36
deltas/0.3.9/pre.009.md
Normal 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
172
deltas/0.3.9/rel.001.md
Normal 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
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/000-README.md -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 12 -->
|
||||
|
||||
# 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 ;
|
||||
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 D1–D4, 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.
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md -->
|
||||
<!-- version: 12 -->
|
||||
<!-- version: 14 -->
|
||||
|
||||
# Acquisition, workers, jobs et pipelines spécialisés
|
||||
|
||||
@@ -55,9 +55,7 @@ persistence D1 RAW
|
||||
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.
|
||||
|
||||
Elle ne choisit pas le provider réseau et ne pilote pas le range historique.
|
||||
La canonicalisation `RawTransaction` réutilisable entre producteurs est désormais attribuée à une lower-layer source-neutral dédiée, `ksp-raw-transaction-lib`, à matérialiser avec le Worker RAW. Elle possède uniquement la normalisation/canonicalisation commune et la construction des modèles RAW/provenance ; elle ne possède ni lifecycle Worker/Job, ni provider, ni routing réseau, ni campagne historique.
|
||||
|
||||
### `ksp-job-backfill-lib`
|
||||
|
||||
@@ -136,15 +134,15 @@ Une stratégie peut donc être :
|
||||
- **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 ;
|
||||
- **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.
|
||||
|
||||
#### 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
|
||||
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
|
||||
```
|
||||
|
||||
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 :
|
||||
|
||||
@@ -204,20 +202,11 @@ L'archive kbot3 doit être relue uniquement comme **référence fonctionnelle**
|
||||
|
||||
#### `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
|
||||
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.
|
||||
Les frontières externes restent libres de documenter ou d'accepter un nom provider legacy lorsque nécessaire, sans recopier ce nom dans `RawNetworkId` canonique.
|
||||
|
||||
#### Idempotence et multi-source
|
||||
|
||||
@@ -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.
|
||||
|
||||
Concepts candidats :
|
||||
Contrats communs actuels :
|
||||
|
||||
```text
|
||||
WorkerId
|
||||
WorkerDescriptor
|
||||
WorkerKindCode
|
||||
WorkerState
|
||||
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
|
||||
start
|
||||
stop
|
||||
status
|
||||
health
|
||||
```
|
||||
|
||||
Une capability comme `reconfigure` n'est pas imposée à tous les workers.
|
||||
La crate n'expose aucune opération runtime universelle `start`, `stop`, `restart` ou `reconfigure`. Le Worker concret possède son runtime et traduit ses opérations de contrôle en transitions `WorkerLifecycle` et snapshots communs.
|
||||
|
||||
## Job API
|
||||
|
||||
@@ -467,6 +454,8 @@ Les événements utiles comprennent notamment :
|
||||
|
||||
### RAW backfill
|
||||
|
||||
État actuel avant extraction de la normalisation commune :
|
||||
|
||||
```text
|
||||
ksp-job-backfill-lib
|
||||
-> ksp-job-api
|
||||
@@ -475,15 +464,26 @@ ksp-job-backfill-lib
|
||||
-> ksp-onchain-transport-lib
|
||||
-> ksp-store-lib # façade Store ; default-features=false côté Job
|
||||
-> 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
|
||||
|
||||
```text
|
||||
ksp-worker-raw-transaction-ingest-lib
|
||||
-> ksp-worker-api
|
||||
-> ksp-onchain-transport-lib
|
||||
-> ksp-raw-transaction-lib
|
||||
-> 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-logging-lib
|
||||
@@ -495,6 +495,8 @@ composition supérieure / future Desk
|
||||
-> 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.
|
||||
|
||||
### CORE replay/worker
|
||||
@@ -525,9 +527,7 @@ selon les capacités réellement introduites.
|
||||
|
||||
## 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 ;
|
||||
- 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 ;
|
||||
- politique d'alias externe `mainnet-beta` à matérialiser uniquement aux frontières qui en ont réellement besoin, sans créer une seconde identité Store ;
|
||||
- modèle de claim/lease PostgreSQL pour les futurs processors continus ;
|
||||
- taille de batch et stratégie backpressure des workers de processing ;
|
||||
- découpage des workers DECODE/SPECIALIZED par groupe lorsque les premiers groupes existent ;
|
||||
|
||||
886
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
Normal file
886
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
Normal 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.
|
||||
690
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
Normal file
690
docs/plans/030-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT_PLAN.md
Normal 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 d’acquisition 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 d’ouverture
|
||||
|
||||
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 d’archive :
|
||||
|
||||
```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` n’est donc pas réinspectable depuis `.git`. La base est néanmoins admissible selon le prompt car l’opérateur l’a 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 l’archive 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` n’est pas installé dans l’environnement d’assemblage. 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 qu’un 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 n’est 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 s’arrête, il ne termine pas un Job annulé |
|
||||
| Completed + JobCompletion | non | interdit | un Worker continu n’a pas de completion normale |
|
||||
| Cancelled | non | WorkerState::Stopped | arrêt normal distinct d’une 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 qu’une 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 d’un 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 l’instance 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` n’a aucune notion de completion ;
|
||||
- `Faulted(ErrorCode)` ne conserve qu’un identifiant d’erreur 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 l’autorité 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 : l’idempotence appartient à l’intention 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 l’intention 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 qu’un 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 n’entre 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 d’empê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` n’est attendu qu’aprè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 s’arrê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 l’intention 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 n’est 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 d’attente 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 qu’un besoin réel n’est 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 d’identité 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 l’API | 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 qu’un 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 lorsqu’au 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 n’est effectué dans `pre.001`, conformément au séquencement du prompt.
|
||||
|
||||
Après freeze fonctionnelle de `ksp-worker-api` en `pre.003`, l’audit 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. L’index architecture ne sera synchronisé qu’au 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 n’en sera repris.
|
||||
|
||||
## 17. Prévision souple recalibrée
|
||||
|
||||
Le `pre.004` unique imaginé par le prompt est scindé en trois tranches d’audit/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 l’exige, à condition de préserver les responsabilités de clôture. Chaque prerelease possède une sous-section directement éditable ; lorsqu’un 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 n’est 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 l’ordre 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.**
|
||||
|
||||
L’audit 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. L’autorisation 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 l’audit 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 d’une prerelease, y compris lorsqu’elle est documentaire.
|
||||
|
||||
`pre.001-fix.002` corrige donc l’état courant sans réécrire l’historique 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 l’opérateur dans un environnement Rust. Leur absence locale n’est pas masquée par les audits Python.
|
||||
357
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
Normal file
357
docs/validation/026-V0_3_9_WORKER_API_RAW_TRANSACTION_AUDIT.md
Normal 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 l’audit externe RAW restent ouverts jusqu’à leurs tranches dédiées.
|
||||
|
||||
## 2. Gate d’ouverture `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é à l’audit 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`; l’existence du tag n’est pas inspectée directement. Le `rel.001` stable décrit l’opération `tag v0.3.8` et le prompt autorise l’archive 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 l’opé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 l’API 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 qu’un `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 n’est 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 l’audit 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 l’exception 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`.
|
||||
839
prompts/029-V0_3_10_START_PROMPT.md
Normal file
839
prompts/029-V0_3_10_START_PROMPT.md
Normal 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.
|
||||
Reference in New Issue
Block a user