79 Commits

Author SHA1 Message Date
002d08ba5f v0.3.14-rel.001 2026-09-12 23:46:51 +02:00
a7ac30f39b v0.3.14-pre.016 2026-09-12 23:31:19 +02:00
cd7cf2c4c6 v0.3.14-pre.015 2026-09-12 23:07:58 +02:00
8d8b6b9bcb v0.3.14-pre.014 2026-09-12 21:29:00 +02:00
02012f105a v0.3.14-pre.013-fix.001 2026-09-12 20:55:57 +02:00
09c91172ae v0.3.14-pre.013 2026-09-12 20:43:43 +02:00
8a0717c839 v0.3.14-pre.012 2026-09-12 20:28:00 +02:00
f3c4169752 v0.3.14-pre.011-fix.002 2026-09-12 19:29:26 +02:00
eedad4b81e v0.3.14-pre.011-fix.001 2026-09-12 19:07:27 +02:00
4e39ddd5d0 v0.3.14-pre.011 2026-09-12 19:05:43 +02:00
c99cb048bf v0.3.14-pre.010-fix.001 2026-09-12 18:11:16 +02:00
8c4b458724 v0.3.14-pre.010 2026-09-12 14:20:02 +02:00
792560191c v0.3.14-pre.009-fix.002 2026-09-12 13:04:49 +02:00
c884ce88ba v0.3.14-pre.009-fix.001 2026-09-12 12:30:27 +02:00
4bcd942928 v0.3.14-pre.009 2026-09-12 11:22:53 +02:00
bf394e0e4b v0.3.14-pre.008-fix.002 2026-09-12 09:45:58 +02:00
ec94001ab8 v0.3.14-pre.008-fix.001 2026-09-12 09:44:36 +02:00
505cc8c3f6 v0.3.14-pre.008 2026-09-12 09:00:36 +02:00
9fc30d3ab9 v0.3.14-pre.007-fix.001 2026-09-12 08:04:19 +02:00
887664f3c0 v0.3.14-pre.007 2026-09-12 07:47:16 +02:00
ae712eae64 v0.3.14-pre.006 2026-09-11 23:48:53 +02:00
a3df8f6ef5 v0.3.14-pre.005 2026-09-11 23:15:00 +02:00
1006079654 v0.3.14-pre.004 2026-09-11 22:22:51 +02:00
6721a4142a v0.3.14-pre.003 2026-09-11 21:54:19 +02:00
7bf938c19f v0.3.14-pre.002-fix.002 2026-09-11 20:55:53 +02:00
5932250314 v0.3.14-pre.002-fix.001 2026-09-11 20:00:56 +02:00
b2068e9ef2 v0.3.14-pre.002 2026-09-11 19:58:32 +02:00
2ca56dbaf4 v0.3.14-pre.001 2026-09-11 19:37:36 +02:00
9785cda39d v0.3.13-rel.001 2026-09-11 00:41:22 +02:00
0cea99cd5e v0.3.13-pre.015 2026-09-11 00:39:51 +02:00
71bc754feb v0.3.13-pre.014 2026-09-10 23:25:05 +02:00
cabbfed730 v0.3.13-pre.013 2026-09-10 22:15:21 +02:00
8b6ef7778a v0.3.13-pre.012 2026-09-10 21:55:02 +02:00
06319655b0 v0.3.13-pre.011 2026-09-10 21:27:18 +02:00
f636169783 v0.3.13-pre.010 2026-09-10 21:02:29 +02:00
0f8f38ff52 v0.3.13-pre.009-fix.002 2026-09-10 20:24:12 +02:00
142998afb3 v0.3.13-pre.009-fix.001 2026-09-10 20:13:01 +02:00
665d7d2bb6 v0.3.13-pre.009 2026-09-10 19:56:16 +02:00
1746d47412 v0.3.13-pre.008-fix.002 2026-09-10 18:25:54 +02:00
9bb2f7bcae v0.3.13-pre.008-fix.001 2026-09-10 17:55:46 +02:00
1f2720463b v0.3.13-pre.008 2026-09-10 17:52:35 +02:00
8bb530e9b4 v0.3.13-pre.007 2026-09-10 17:33:02 +02:00
72df965a9c v0.3.13-pre.006-fix.001 2026-09-10 16:17:50 +02:00
f688325170 v0.3.13-pre.006 2026-09-10 16:05:52 +02:00
c711213dcc v0.3.13-pre.005-fix.001 2026-09-10 15:23:09 +02:00
11ecdccd65 v0.3.13-pre.005 2026-09-10 15:01:00 +02:00
27ab84d198 v0.3.13-pre.004-fix.001 2026-09-10 14:51:28 +02:00
28e4da3879 v0.3.13-pre.004 2026-09-10 14:25:30 +02:00
31c0a83e51 v0.3.13-pre.003-fix.003 2026-09-10 13:44:17 +02:00
34fddc3bf5 v0.3.13-pre.003-fix.002 2026-09-10 13:20:09 +02:00
13c3dac554 v0.3.13-pre.003-fix.001 2026-09-10 13:12:25 +02:00
f7ed053e8e v0.3.13-pre.003 2026-09-10 11:38:34 +02:00
2e8feb56dd v0.3.13-pre.002-fix.001 2026-09-10 10:00:42 +02:00
793178b345 v0.3.13-pre.002 2026-09-10 09:50:42 +02:00
ee0359efd5 v0.3.13-pre.001 2026-09-10 09:01:00 +02:00
1dc57a85c2 v0.3.12-rel.001 2026-09-10 00:09:44 +02:00
e7e8960fec v0.3.12-pre.013 2026-09-10 00:00:12 +02:00
79e278064d v0.3.12-pre.012 2026-09-09 23:24:20 +02:00
474b894d1c v0.3.12-pre.011 2026-09-09 23:07:52 +02:00
0c76b74afe v0.3.12-pre.010 2026-09-09 22:35:30 +02:00
b608ed2c50 v0.3.12-pre.009-fix.001 2026-09-09 22:14:29 +02:00
f9d4e6bc74 v0.3.12-pre.009 2026-09-09 22:06:18 +02:00
223f11b989 v0.3.12-pre.008-fix.002 2026-09-09 20:57:54 +02:00
5095be0b0d v0.3.12-pre.008-fix.001 2026-09-09 20:46:44 +02:00
2aa64cf0a4 v0.3.12-pre.008 2026-09-09 20:42:32 +02:00
90c266d68a v0.3.12-pre.007-fix.002 2026-09-09 17:14:47 +02:00
8410b2f4ef v0.3.12-pre.007-fix.001 2026-09-09 16:22:09 +02:00
ba69a5ad20 v0.3.12-pre.007 2026-09-09 16:21:23 +02:00
07b9b20eb5 v0.3.12-pre.006-fix.001 2026-09-09 15:00:27 +02:00
901a0515b7 v0.3.12-pre.006 2026-09-09 13:30:15 +02:00
50c01a19c9 v0.3.12-pre.005-fix.001 2026-09-09 12:59:51 +02:00
3a8fa87acb v0.3.12-pre.005 2026-09-09 12:45:29 +02:00
df1dbbb8fb v0.3.12-pre.004-fix.001 2026-09-09 11:55:36 +02:00
531ca851c4 v0.3.12-pre.004 2026-09-09 11:44:36 +02:00
b3fd74529d v0.3.12-pre.003-fix.002 2026-09-09 11:43:19 +02:00
00b376d234 v0.3.12-pre.003-fix.001 2026-09-09 08:14:45 +02:00
fa720d0054 v0.3.12-pre.003 2026-09-09 07:39:42 +02:00
91438e8214 v0.3.12-pre.002 2026-09-09 07:12:37 +02:00
08e9a34183 v0.3.12-pre.001 2026-09-08 23:44:19 +02:00
135 changed files with 42827 additions and 327 deletions

View File

@@ -1,8 +1,46 @@
<!-- file: CHANGELOG.md -->
<!-- version: 31 -->
<!-- version: 34 -->
# Changelog KSP
## 0.3.14 — gap repair et hardening multi-source du Worker RawTransaction — 2026-09-12
`0.3.14` ferme `ksp-worker-raw-transaction-ingest-lib` par une continuité run-local explicite au-dessus des cinq familles live de `0.3.13`, sans transformer le Worker en moteur historique. Le Worker distingue désormais reconnect, tentative de replay, delivery de replay et preuve de coverage ; il maintient des gaps bornés avec plage inclusive, `TargetCoverage` conservative et frontier de continuité distincte de la processing frontier. Une reprise live ou l'acceptation d'un `from_slot` ne vaut jamais à elle seule preuve de réparation.
La réconciliation peut s'appuyer sur une coverage réellement prouvée d'une source redondante, un scan HTTP borné de blocs/slots ou l'hydration `getTransaction observed` des références connues. Les bornes finales restent 64 gaps ouverts, 4096 slots par plage, fenêtres de discovery de 512 slots, 4 `getBlock` logiques en vol et un seul gap activement réparé. Les matériaux de repair repassent par la même canonicalisation Common RAW, la même admission, la même hydration globale et le même Store ; aucun second pipeline, retry réseau Worker, Job Backfill automatique ou backend Store direct n'est introduit.
La politique multi-source ne fault plus mécaniquement sur toute perte de source : la continuation n'est autorisée que lorsque les gaps concernés sont réconciliés et que la coverage présente/future requise est réellement démontrée. Les snapshots publics ajoutent une observabilité source-neutral et bornée des gaps avec états/raisons/méthodes et compteurs checked, sans source key, endpoint, token, signature, payload ou erreur provider arbitraire. La health policy reste conservative, la fairness alterne trafic nominal/repair sur les capacités existantes et le shutdown borne également le drain interne des sources avec abort+join des siblings non coopératifs.
La fermeture cross-layer confirme le pipeline unique `Transport -> Worker -> ksp-raw-transaction-lib -> Store`, la non-régression Legacy/V0/V1, les dependency firewalls et l'indépendance Worker/Backfill. Le gate technique final passe fmt, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace/all-targets/all-features avec `-D warnings`, `cargo test --workspace --all-targets --all-features` et les graphes Cargo : `1 931` tests passent, `0` échoue et `15` restent ignored opt-in/operator-only. Les smokes keyless Solana HTTP Devnet et WebSocket Devnet passent `1/1` chacun ; Yellowstone/provider-gated, Helius live, PostgreSQL live et Worker->Store end-to-end restent explicitement non exécutés lorsqu'aucune ressource correspondante n'a été provisionnée.
`prompts/034-V0_3_15_START_PROMPT.md` ouvre `0.3.15` exclusivement depuis le futur tag stable `v0.3.14`. Cette release introduit `ksp-app-raw-transaction-ingest-desk` comme Desk Tauri de composition/supervision : une **route** est une stratégie complète pouvant nécessiter plusieurs capacités Config/Transport, elle n'est sélectionnable que si toutes ses requirements sont composables, puis une instance Worker réelle est démarrée par route sélectionnée et valide l'opérabilité runtime au Start. Le frontend reste une surface sûre ; Config, Transport, Store et Worker conservent leurs responsabilités.
## 0.3.13 — convergence live multi-source du Worker RawTransaction — 2026-09-10
`0.3.13` généralise `ksp-worker-raw-transaction-ingest-lib` de la verticale Yellowstone de `0.3.12` vers une composition caller-owned de `1..32` sources logiques appartenant au même réseau. Cinq familles live convergent désormais vers le même pipeline Common RAW / admission / Store : Yellowstone, Standard Logs avec hydration `getTransaction observed`, Standard Block RAW-direct, Helius `transactionSubscribe` avec hydration et HTTP Block Polling run-local RAW-direct. Le Worker reste sans Config, Job Backfill, backend Store physique, client réseau direct ou SDK provider parallèle.
La convergence partage un registre global d'hydration entre Yellowstone, Standard Logs et Helius afin de coalescer `(network, signature, commitment)` avant fan-out HTTP. Après canonicalisation, les acquisitions de même `(network, signature)` sont sérialisées de manière bornée : un contenu identique conserve les observations/provenances distinctes, tandis qu'une divergence devient un `content_conflict` terminal explicite. Aucune majorité provider, préférence silencieuse ou stratégie `first-provider-wins` n'est introduite. Standard Block et HTTP Block Polling admettent directement les transactions Legacy/V0/V1 qualifiées avec `maxSupportedTransactionVersion = 1`.
Le supervisor multi-source possède toutes les tâches et applique des bornes source-neutral sur admission, pending signals, hydrations et persistence avec une fairness minimale entre sources reference-bearing. Les snapshots agrègent activity/health/backpressure/reconnect/replay/gap sans exposer le matériau provider. La politique reste conservative : une source configurée qui échoue est terminale faute d'équivalence de coverage prouvée ; le gap repair, les rôles degraded/failover et la preuve de couverture restent donc `0.3.14`. Le shutdown stop/fault/drain/abort+join est durci contre les races, leaders d'hydration abandonnés, Store lent et publications tardives.
Le gate déterministe final passe `cargo fmt`, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace/all-targets/all-features avec `-D warnings`, `cargo test --workspace --all-targets --all-features` et les graphes Cargo : `1 850` tests passent, `0` échoue et `15` restent ignored opt-in/operator-only. Les smokes keyless Solana HTTP Devnet et WebSocket Devnet passent `1/1` chacun. Yellowstone provider-gated, Helius `transactionSubscribe` live, `blockSubscribe` provider dédié, HTTP polling Worker end-to-end et Worker multi-source vers Store restent explicitement non exécutés faute de gate provisionné ; aucune fixture ne les requalifie en PASS.
`prompts/033-V0_3_14_START_PROMPT.md` ouvre `0.3.14` exclusivement depuis le futur tag stable `v0.3.13`. Cette release doit fermer le gap repair/hardening multi-source limité au run actif — replay adressable, coverage redondante, scan HTTP borné, hydration de réparation, unresolved gaps et health policy — sans transformer le Worker en moteur historique ni créer de dépendance vers `ksp-job-backfill-lib`.
## 0.3.12 — Yellowstone + hydration HTTP + continuité de run du Worker RawTransaction — 2026-09-09
`0.3.12` ouvre la première source réseau productive de `ksp-worker-raw-transaction-ingest-lib` sans remettre en cause la fondation source-neutral de `0.3.11`. Le Worker dépend désormais de la façade `ksp-onchain-transport-lib` mais reste sans Config, Job Backfill, backend Store physique, `reqwest`, `tonic` ou proto provider direct. `RawTransactionIngestRuntimeResources` reçoit une source Yellowstone caller-composed et un pool HTTP cohérent ; `start` conserve la fondation sans source, tandis que `start_with_runtime_resources` lance la verticale live supervisée.
Les updates Yellowstone `Transaction`, `TransactionStatus` et les transactions incluses dans `Block` sont projetées vers des signaux source-neutral, coalescées de manière bornée par `(network, signature, commitment)`, hydratées via `getTransaction` observed puis converties par `ksp-raw-transaction-lib` avant admission/persistence Store. `BlockMeta` et `Slot` restent continuity-only. La provenance composite conserve les identités logiques sûres de la source et de la route dhydration, sans URL, secret ni payload distant. Les gardes réseau/signature/slot/index/commitment empêchent les croisements de matériau avant admission.
La release matérialise une processing frontier strictement run-local : le Worker distingue travail pending/settled, navance jamais au travers dun pending plus ancien et compacte ses états sans revendiquer une preuve de complétude durable. Le reconnect/replay reste Transport-owned : `from_slot` et `SubscribeReplayInfo` ne sont pas pilotés par le Worker. Le Worker projette un état source-neutral `Active/Reconnecting/Closing/Closed/Failed`, compte reconnect/replay-attempt/continuity-gap et fault avec `source_failed` lorsquune hausse du gap Transport prouve que la rétention ne permet plus la reprise demandée. Aucun Backfill ou repair automatique nest déclenché.
Le hardening final ferme les duplicate storms, saturation/coalescence, Store lent/fautif, stop/fault pendant hydration/reconnect, overflow de compteurs, abandon des pending sans faux `settled`, abort/join des tâches privées et absence de retry HTTP possédé par le Worker. `pre.010` ajoute les canaris cross-layer Transport -> Worker -> Common RAW -> Store et verrouille les dependency firewalls, fixtures Legacy/V0, redaction et inventaires publics.
Le gate déterministe `pre.011` passe rustfmt check, audits Rust/Markdown, `cargo check --workspace`, Clippy strict, `cargo test --workspace --all-targets --all-features`, les suites ciblées et les trois inspections Cargo demandées. Les smokes live keyless HTTP Devnet et WebSocket Devnet sont exécutés séparément et passent ; les smokes Yellowstone token-gated et le Worker end-to-end Yellowstone + HTTP + Store restent explicitement non exécutés faute de ressources fournies, sans faux PASS. `pre.012` réconcilie ensuite README/USAGE et les architectures concernées avec cette verticale réellement matérialisée.
La suite reste découpée : `0.3.13` ajoute WS standard, Helius `transactionSubscribe`, HTTP live polling et la convergence multi-source ; `0.3.14` ferme le gap repair/hardening multi-source. `prompts/032-V0_3_13_START_PROMPT.md` ouvre `0.3.13` uniquement depuis le tag stable `v0.3.12` et impose un `pre.001` daudit/sizing avant toute extension productive.
## 0.3.11 — Fondation runtime source-neutral du Worker RawTransaction ingest — 2026-09-08
`0.3.11` introduit `ksp-worker-raw-transaction-ingest-lib` comme premier Worker concret KSP pour lacquisition continue de `RawTransaction`, tout en fermant volontairement la release **sans source réseau productive**. La crate consomme `ksp-worker-api`, `ksp-raw-transaction-lib` et la seule façade `ksp-store-lib`; elle ne dépend ni de Config, ni dun Job, ni dun backend Store physique, ni encore de `ksp-onchain-transport-lib`. Son identité stable est `raw_transaction_ingest`, ses settings bornent la capacité dadmission, la concurrence de persistence et la deadline de drain, et son `start` utilise exclusivement le runtime Tokio actif fourni par le caller.

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 518
# version: 593
[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-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"]
[workspace.package]
version = "0.3.11"
version = "0.3.14"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md -->
<!-- version: 12 -->
<!-- version: 14 -->
# Khadhroony Solana Project
@@ -57,7 +57,7 @@ La couche RAW dispose d'une façade Store backend-neutral et d'un premier job hi
`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.
`ksp-worker-raw-transaction-ingest-lib` est désormais le premier Worker concret distinct du Job Backfill. Sa fondation source-neutral possède le lifecycle Start/Stop, l'admission bornée, la canonicalisation Common RAW, la persistance Store backend-neutral, le shutdown borné et les snapshots latest-value, mais aucune source réseau productive ni dépendance Transport. Les adapters live/catch-up seront ajoutés dans les tranches suivantes sans transformer le Worker en campagne historique. `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.
`ksp-worker-raw-transaction-ingest-lib` est le premier Worker concret distinct du Job Backfill. Il conserve sa fondation Start/Stop, admission bornée, Common RAW, Store backend-neutral, shutdown borné et snapshots latest-value. Ses sources productives couvrent maintenant Yellowstone + hydration HTTP `getTransaction`, Standard WS `logsSubscribe` + hydration, Standard WS `blockSubscribe` RAW-direct, Helius `transactionSubscribe` + hydration et Solana HTTP live block polling. Le polling HTTP démarre au `getSlot` observé du run, découvre uniquement les blocs live via `getBlocksWithLimit` puis matérialise les slots listés via `getBlock observed`; il n'est jamais utilisé comme Backfill implicite. Les signaux hydratés de même `(network, signature, commitment)` convergent vers le même coordinator, tandis que les voies block RAW-direct passent par la même admission Common RAW. Le reconnect/replay reste propriétaire de Transport, la frontier de traitement reste run-local et un gap de rétention prouvé provoque un fault au lieu de lancer une campagne historique. `ksp-job-backfill-lib` conserve 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

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md -->
<!-- version: 109 -->
<!-- version: 112 -->
# Roadmap KSP
@@ -104,10 +104,10 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
- [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.
- [X] `0.3.10` — Lower-layer RAW Transaction commune stabilisée : `ksp-raw-transaction-lib` possède la canonicalisation RAW v1 source-neutral partagée, le Backfill est migré sans changement des golden bytes/hash, Transport expose `get_block_observed`, le wire Solana Legacy/V0/V1 est matérialisé et les canaris HTTP/WS standard/Helius/Yellowstone qualifient précisément les voies directes ou nécessitant hydration. La release se ferme sans runtime Worker concret ; celui-ci commence en `0.3.11` selon le redécoupage `0.3.11``0.3.14`.
- [X] `0.3.11` — Fondation runtime de `ksp-worker-raw-transaction-ingest-lib` livrée : crate/dependency firewall, settings source-neutral, handle/start-stop, lifecycle, snapshots, supervisor, admission bornée, canonicalisation Common RAW, persistence/déduplication Store déterministe, backpressure et shutdown/fault hardening. La release se ferme volontairement sans source réseau productive ; Yellowstone + hydration HTTP commencent en `0.3.12`.
- [ ] `0.3.12`Ajouter au Worker la voie **Yellowstone + hydration HTTP** : transactions/blocks/status, projection vers la common RAW, provenance sûre, `from_slot`, replay info, frontier de run et continuité propre au run, avec fixtures déterministes et smokes accessibles sans prétendre à un RAW-direct lorsque les métadonnées restent incomplètes.
- [ ] `0.3.13`Ajouter les voies **WS standard / Helius / HTTP live** et leur convergence multi-source : `logsSubscribe + getTransaction`, `blockSubscribe`, `transactionSubscribe + hydration`, polling blocs HTTP, observations multiples, content conflict explicite, coalescence et backpressure sans second actor Transport.
- [ ] `0.3.14`Fermer le Worker par le **gap repair/hardening multi-source** : replay natif, source redondante, scan HTTP blocs, hydration de réparation, unresolved gaps visibles, politiques degraded/unhealthy/faulted, shutdown pendant repair, smokes provider accessibles et adapter EARLY seulement si réellement prouvé.
- [ ] `0.3.15` — 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 le Worker finalisé : 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.
- [X] `0.3.12`Première source live productive du Worker livrée : Yellowstone `Transaction`/`TransactionStatus`/`Block` vers signaux source-neutral, coalescence bornée puis hydration HTTP `getTransaction` observed avant Common RAW/Store ; `BlockMeta`/`Slot` restent continuity-only. Processing frontier run-local, reconnect/from_slot/ReplayInfo Transport-owned, compteurs source-neutral et fault sur gap de rétention prouvé sans Backfill automatique. Hardening duplicate/backpressure/stop/fault et canaris cross-layer fermés ; gate workspace complet vert, smokes live HTTP Devnet + WebSocket Devnet verts, Yellowstone token-gated/Worker end-to-end laissés explicitement non exécutés.
- [X] `0.3.13`Convergence live multi-source du Worker RAW livrée : composition caller-owned `1..32` sources dun même réseau, cinq familles Yellowstone / Standard Logs / Standard Block / Helius Transaction / HTTP Block Polling, hydration globale des trois voies reference-bearing, RAW-direct Legacy/V0/V1 pour les deux voies blocs, observations multiples, content conflict explicite, coalescence cross-source, fairness/backpressure/health source-neutral et shutdown hardening sans second actor Transport. Gate workspace complet vert (`1 850` PASS / `0` échec / `15` ignored) et smokes keyless HTTP + WebSocket Devnet verts ; les preuves provider/resource-gated non exécutées restent explicitement non revendiquées.
- [X] `0.3.14`Gap repair/hardening multi-source du Worker RAW livré : gaps run-local bornés, distinction reconnect/replay/delivery/coverage, TargetCoverage conservative, coverage redondante prouvée, discovery HTTP bornée, known-reference hydration, réconciliation source-loss, health gap-aware, fairness nominal/repair, snapshots publics source-neutral et shutdown/drain renforcé. Le pipeline Common RAW/Store reste unique, Worker/Backfill restent indépendants, EARLY nest pas ajouté. Gate workspace complet vert (`1 931` PASS / `0` échec / `15` ignored) et smokes keyless HTTP + WebSocket Devnet verts ; preuves provider/resource-gated non provisionnées explicitement non revendiquées.
- [ ] `0.3.15` — Introduire `ksp-app-raw-transaction-ingest-desk`, Desk Tauri KSP de composition/supervision des routes live `RawTransaction`. Une route est une stratégie complète pouvant nécessiter plusieurs capabilities/endpoints (par exemple gRPC + HTTP ou WS + HTTP) et nest sélectionnable que si toutes ses requirements sont composables depuis Config sur le réseau choisi ; la présence dun provider ne vaut jamais capability implicite. Au Start, la Config est revalidée et **une instance Worker est lancée par route sélectionnée** — pas par source physique — afin de valider lopérabilité réelle puis exposer lifecycle, health, backpressure, reconnect/replay, gaps/repair et compteurs sûrs. Plusieurs Workers peuvent partager le même Store, sans coordination implicite de gaps entre routes. La Desk ne réimplémente ni Transport, discovery, hydration, déduplication, repair ni persistence.
- [ ] `0.3.16` — É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, sans supposer qu'une source live WS constitue un historique universel.
### TODO/IDEAS — applications spécialisées et control plane
@@ -119,7 +119,7 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
- [ ] **TODO** — maintenir la matrice dadmission HTTP/WS/gRPC/provider lors de toute nouvelle famille D1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte.
- [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 désormais scindés `0.3.11``0.3.14` Worker live / `0.3.16` Backfill historique.
- [ ] **TODO Helius `0.3.13`**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 Helius `0.3.13`**la voie Worker Helius `transactionSubscribe` réutilise la surface Transport et les profils Config Helius déjà existants avec `KSP_SECRET_HELIUS_API_KEY`; aucun nouveau profil, secret, SDK provider, tier codé ou second client parallèle na été nécessaire. Le payload riche reste Transport-owned et la voie Worker hydrate via `getTransaction observed` tant que le RAW complet nest pas prouvé directement.
- [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 D1 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 dexécution `slot + signature + outcome`; réauditer séparément `signatureSubscribe` et `getSignatureStatuses` lorsquun consumer de commitment/snapshot réel apparaît, sans fusionner snapshot, transition et execution update dans un modèle Option-soup.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
<!-- version: 25 -->
<!-- version: 26 -->
# Utilisation de `ksp-onchain-transport-lib`
@@ -387,9 +387,11 @@ let snapshot = stream.snapshot();
let closed = stream.close().await;
```
`try_update()` remplace dynamiquement la requête complète tant que la session est `Active`. Une mutation pendant `Reconnecting` est refusée pour éviter une application ambiguë. Le snapshot expose reconnects, replay attempts, gaps, duplicates, dernier `from_slot` demandé et dernier slot observé, sans endpoint ni payload arbitraire.
`try_update()` remplace dynamiquement la requête complète tant que la session est `Active`. Une mutation pendant `Reconnecting` est refusée pour éviter une application ambiguë. Le snapshot expose reconnects, replay attempts, replay deliveries conservatrices, reprises dont la couverture reste non prouvée, gaps de rétention, duplicates, dernier `from_slot` demandé et dernier slot observé, sans endpoint ni payload arbitraire.
Le reconnect réutilise la dernière requête acceptée et peut avancer `from_slot`, mais le consumer doit traiter cette reprise comme best-effort. KSP ne promet ni exactly-once, ni replay historique complet, ni absence de fork/equivocation entre nœuds.
Une `replay_delivery_count` n'augmente que lorsque le premier flux post-reconnect redélivre exactement le slot de reprise demandé. Elle prouve une livraison au bord de replay, pas la complétude de l'intervalle. Dès que le premier update slot-bearing atteint ou dépasse cette borne, `replay_coverage_unproven_count` augmente aussi : Transport ne possède pas de preuve générique que tous les updates correspondant aux filtres ont été livrés entre la borne et la reprise live. Le compteur augmente également si un nouveau reconnect survient avant tout matériau slot-bearing. Cette valeur signifie explicitement « couverture non prouvée » ; elle ne prouve pas qu'un événement filtré existait ou a été perdu.
Le reconnect réutilise la dernière requête acceptée et peut avancer `from_slot`, mais le consumer doit traiter cette reprise comme best-effort. `replay_attempt_count`, `replay_delivery_count` et une preuve de coverage sont des notions distinctes. Transport ne publie actuellement aucun compteur `replay_covered` générique, car ni l'acceptation de `from_slot` ni une livraison ponctuelle ne démontrent à elles seules une couverture historique complète. KSP ne promet ni exactly-once, ni replay historique complet, ni absence de fork/equivocation entre nœuds.
## 5. Appels typés

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/grpc_stream.rs
// version: 3
// version: 5
use tonic_prost::prost::Message; // rust-rules: trait-import
@@ -34,11 +34,50 @@ pub struct YellowstoneGrpcSubscribeSnapshot {
continuity_gap_count: u64,
duplicate_update_count: u64,
replay_attempt_count: u64,
replay_delivery_count: u64,
replay_coverage_unproven_count: u64,
last_requested_from_slot: std::option::Option<u64>,
last_observed_slot: std::option::Option<u64>,
terminal_error_code: std::option::Option<ksp_core_lib::ErrorCode>,
}
/// Cloneable latest-value observer for one standard Yellowstone Subscribe session snapshot.
///
/// The observer exposes only the already-safe transport snapshot and keeps the internal Tokio watch channel private. Cloning the observer does not duplicate
/// the gRPC stream, request state or reconnect actor.
#[derive(Clone)]
pub struct YellowstoneGrpcSubscribeSnapshotSource {
receiver: tokio::sync::watch::Receiver<crate::YellowstoneGrpcSubscribeSnapshot>,
}
impl crate::YellowstoneGrpcSubscribeSnapshotSource {
fn new(receiver: tokio::sync::watch::Receiver<crate::YellowstoneGrpcSubscribeSnapshot>) -> Self {
return Self { receiver };
}
/// Returns the current safe reconnect/replay snapshot without waiting for another actor transition.
#[must_use]
pub fn current(&self) -> crate::YellowstoneGrpcSubscribeSnapshot {
return *self.receiver.borrow();
}
/// Waits for one newer safe reconnect/replay snapshot.
///
/// `None` means the owning Subscribe actor dropped the latest-value publisher and no further snapshot can arrive.
pub async fn wait_for_change(&mut self) -> std::option::Option<crate::YellowstoneGrpcSubscribeSnapshot> {
return match self.receiver.changed().await {
std::result::Result::Ok(()) => std::option::Option::Some(*self.receiver.borrow_and_update()),
std::result::Result::Err(_) => std::option::Option::None,
};
}
}
impl std::fmt::Debug for crate::YellowstoneGrpcSubscribeSnapshotSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("YellowstoneGrpcSubscribeSnapshotSource").field("current", &self.current()).finish();
}
}
impl YellowstoneGrpcSubscribeSnapshot {
const fn new(initial_from_slot: std::option::Option<u64>) -> Self {
return Self {
@@ -47,6 +86,8 @@ impl YellowstoneGrpcSubscribeSnapshot {
continuity_gap_count: 0,
duplicate_update_count: 0,
replay_attempt_count: 0,
replay_delivery_count: 0,
replay_coverage_unproven_count: 0,
last_requested_from_slot: initial_from_slot,
last_observed_slot: std::option::Option::None,
terminal_error_code: std::option::Option::None,
@@ -85,6 +126,25 @@ impl YellowstoneGrpcSubscribeSnapshot {
return self.replay_attempt_count;
}
/// Returns the number of replay-bearing reconnects that delivered the requested replay boundary slot again.
///
/// This is conservative delivery evidence only. It does not prove that every matching update in the replay interval was delivered and must never be
/// interpreted as `replay_covered`.
#[must_use]
pub const fn replay_delivery_count(self) -> u64 {
return self.replay_delivery_count;
}
/// Returns the number of successful replay-bearing reconnects whose target coverage remains unproven.
///
/// The counter advances when the first post-reconnect slot-bearing update reaches or passes the requested replay boundary, because generic Transport
/// cannot prove from that delivery alone that every matching update in the replay interval was delivered. It also advances when another reconnect starts
/// before any slot-bearing replay material arrives. This is an explicit lack of coverage proof, not proof that a filtered event actually existed or was lost.
#[must_use]
pub const fn replay_coverage_unproven_count(self) -> u64 {
return self.replay_coverage_unproven_count;
}
/// Returns the most recent effective `from_slot` sent by KSP, including any clamp to `SubscribeReplayInfo.first_available`.
#[must_use]
pub const fn last_requested_from_slot(self) -> std::option::Option<u64> {
@@ -154,6 +214,12 @@ impl SolanaYellowstoneGrpcSubscribeSession {
return *self.snapshot_rx.borrow();
}
/// Returns one cloneable latest-value observer for reconnect/replay snapshot transitions.
#[must_use]
pub fn snapshot_source(&self) -> crate::YellowstoneGrpcSubscribeSnapshotSource {
return crate::YellowstoneGrpcSubscribeSnapshotSource::new(self.snapshot_rx.clone());
}
/// Queues one complete standard Yellowstone request mutation without waiting for network dispatch.
///
/// The mutation is rejected synchronously when local validation fails, the encoded request exceeds the configured outbound bound, the bounded request
@@ -463,17 +529,44 @@ enum UpdateIdentity {
}
struct ContinuityTracker {
pending_replay_from_slot: std::option::Option<u64>,
recent_order: std::collections::VecDeque<UpdateIdentity>,
recent_set: std::collections::HashSet<UpdateIdentity>,
}
impl ContinuityTracker {
fn new() -> Self {
return Self { recent_order: std::collections::VecDeque::new(), recent_set: std::collections::HashSet::new() };
return Self {
pending_replay_from_slot: std::option::Option::None,
recent_order: std::collections::VecDeque::new(),
recent_set: std::collections::HashSet::new(),
};
}
fn begin_replay(&mut self, from_slot: std::option::Option<u64>) {
self.pending_replay_from_slot = from_slot;
return;
}
fn abandon_pending_replay(&mut self, snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot) -> bool {
if self.pending_replay_from_slot.take().is_none() {
return false;
}
snapshot.replay_coverage_unproven_count = snapshot.replay_coverage_unproven_count.saturating_add(1);
return true;
}
fn observe(&mut self, update: &crate::YellowstoneSubscribeUpdate, snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot) {
if let std::option::Option::Some(slot) = update_slot(update) {
if let std::option::Option::Some(requested) = self.pending_replay_from_slot
&& slot >= requested
{
if slot == requested {
snapshot.replay_delivery_count = snapshot.replay_delivery_count.saturating_add(1);
}
snapshot.replay_coverage_unproven_count = snapshot.replay_coverage_unproven_count.saturating_add(1);
self.pending_replay_from_slot = std::option::Option::None;
}
snapshot.last_observed_slot = std::option::Option::Some(match snapshot.last_observed_slot {
std::option::Option::Some(previous) => std::cmp::max(previous, slot),
std::option::Option::None => slot,
@@ -577,6 +670,7 @@ async fn run_subscribe_actor(
&mut shutdown_rx,
&mut snapshot,
&snapshot_tx,
&mut tracker,
)
.await
{
@@ -619,6 +713,7 @@ async fn run_subscribe_actor(
&mut shutdown_rx,
&mut snapshot,
&snapshot_tx,
&mut tracker,
)
.await
{
@@ -658,8 +753,12 @@ async fn reconnect_subscribe_stream(
shutdown_rx: &mut tokio::sync::watch::Receiver<std::option::Option<tokio::time::Instant>>,
snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot,
snapshot_tx: &tokio::sync::watch::Sender<crate::YellowstoneGrpcSubscribeSnapshot>,
tracker: &mut ContinuityTracker,
) -> ReconnectOutcome {
clear_request_sender(request_state);
if tracker.abandon_pending_replay(snapshot) {
snapshot_tx.send_replace(*snapshot);
}
snapshot.state = crate::YellowstoneGrpcSubscribeState::Reconnecting;
snapshot.terminal_error_code = std::option::Option::None;
snapshot_tx.send_replace(*snapshot);
@@ -742,6 +841,7 @@ async fn reconnect_subscribe_stream(
return ReconnectOutcome::Exhausted(error);
}
snapshot.reconnect_count = snapshot.reconnect_count.saturating_add(1);
tracker.begin_replay(effective_from_slot);
snapshot.state = crate::YellowstoneGrpcSubscribeState::Active;
snapshot.terminal_error_code = std::option::Option::None;
snapshot_tx.send_replace(*snapshot);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
// version: 8
// version: 9
const MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES: usize = 128;
const MAX_GRPC_BLOCK_VECTOR_COUNT: usize = 65_536;
@@ -2529,13 +2529,34 @@ impl YellowstoneSubscribeEntryFilter {
}
}
/// Opaque deterministic identity material for one complete Yellowstone Subscribe request.
///
/// The encoded bytes remain private. `Hash` feeds those canonical bytes to a caller-provided hasher while `Debug` exposes only their length.
#[derive(Clone, Eq, PartialEq)]
pub struct YellowstoneSubscribeRequestIdentity {
bytes: std::vec::Vec<u8>,
}
impl std::hash::Hash for crate::YellowstoneSubscribeRequestIdentity {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write(&(self.bytes.len() as u64).to_be_bytes());
state.write(self.bytes.as_slice());
return;
}
}
impl std::fmt::Debug for crate::YellowstoneSubscribeRequestIdentity {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("YellowstoneSubscribeRequestIdentity").field("byte_len", &self.bytes.len()).finish();
}
}
/// Provider-neutral standard Yellowstone subscribe request owned by KSP.
///
/// The seven upstream maps are represented independently and retain named empty entries. An entirely empty map is the logical KSP representation of no active
/// filter in that family; protobuf map encoding does not distinguish an omitted map from an empty map. Filter-group names are globally unique across all seven
/// maps so the names echoed by `SubscribeUpdate.filters` remain unambiguous. `Debug` exposes only counts and common scalar options, never filter names or
/// future
/// filter payloads.
/// future filter payloads.
#[derive(Clone, Eq, PartialEq)]
pub struct YellowstoneSubscribeRequest {
accounts: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeAccountFilter>,
@@ -2551,7 +2572,7 @@ pub struct YellowstoneSubscribeRequest {
from_slot: std::option::Option<u64>,
}
impl YellowstoneSubscribeRequest {
impl crate::YellowstoneSubscribeRequest {
/// Creates an empty subscribe request. Empty requests are valid because later bidi lifecycle code uses request mutations to clear filters or carry ping
/// state.
#[must_use]
@@ -2762,6 +2783,40 @@ impl YellowstoneSubscribeRequest {
return self.from_slot;
}
/// Builds one opaque deterministic identity for the complete logical Subscribe request.
///
/// The identity preserves filter-family separation, globally sorted filter names, exact filter wire payloads and common request options. Its Debug surface
/// exposes only the encoded byte length. Callers may hash the opaque value but cannot recover the underlying identity bytes through this API.
pub fn identity(&self) -> ksp_core_lib::Result<crate::YellowstoneSubscribeRequestIdentity> {
let validation = self.validate();
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
let mut bytes = std::vec::Vec::new();
append_subscribe_identity_map(&mut bytes, b"accounts", &self.accounts, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"blocks", &self.blocks, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"blocks_meta", &self.blocks_meta, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"entry", &self.entry, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"slots", &self.slots, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"transactions", &self.transactions, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"transactions_status", &self.transactions_status, |filter| return filter.to_wire());
let mut common = match self.to_wire() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
common.accounts.clear();
common.blocks.clear();
common.blocks_meta.clear();
common.entry.clear();
common.slots.clear();
common.transactions.clear();
common.transactions_status.clear();
let common = yellowstone_grpc_proto::prost::Message::encode_to_vec(&common);
append_subscribe_identity_component(&mut bytes, b"common");
append_subscribe_identity_component(&mut bytes, common.as_slice());
return std::result::Result::Ok(crate::YellowstoneSubscribeRequestIdentity { bytes });
}
/// Validates all deterministic common subscribe-request bounds before any network I/O.
pub fn validate(&self) -> ksp_core_lib::Result<()> {
if self.total_filter_count() > MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT {
@@ -3710,13 +3765,38 @@ fn commitment_to_wire(commitment: std::option::Option<crate::SolanaCommitment>)
});
}
impl std::default::Default for YellowstoneSubscribeRequest {
fn append_subscribe_identity_component(output: &mut std::vec::Vec<u8>, value: &[u8]) {
output.extend_from_slice(&(value.len() as u64).to_be_bytes());
output.extend_from_slice(value);
return;
}
fn append_subscribe_identity_map<V, W, F>(
output: &mut std::vec::Vec<u8>,
family: &[u8],
values: &std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, V>,
mut to_wire: F,
) where
W: yellowstone_grpc_proto::prost::Message,
F: FnMut(&V) -> W,
{
append_subscribe_identity_component(output, family);
output.extend_from_slice(&(values.len() as u64).to_be_bytes());
for (name, filter) in values {
append_subscribe_identity_component(output, name.as_str().as_bytes());
let wire = yellowstone_grpc_proto::prost::Message::encode_to_vec(&to_wire(filter));
append_subscribe_identity_component(output, wire.as_slice());
}
return;
}
impl std::default::Default for crate::YellowstoneSubscribeRequest {
fn default() -> Self {
return Self::new();
}
}
impl std::fmt::Debug for YellowstoneSubscribeRequest {
impl std::fmt::Debug for crate::YellowstoneSubscribeRequest {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("YellowstoneSubscribeRequest")

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 45
// version: 48
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -143,6 +143,8 @@ pub use self::grpc_settings::YellowstoneGrpcTransportSettings;
pub use self::grpc_stream::SolanaYellowstoneGrpcSubscribeSession;
/// Safe Yellowstone reconnect/replay continuity snapshot.
pub use self::grpc_stream::YellowstoneGrpcSubscribeSnapshot;
/// Cloneable latest-value observer for one standard Yellowstone Subscribe session snapshot.
pub use self::grpc_stream::YellowstoneGrpcSubscribeSnapshotSource;
/// Safe Yellowstone bidirectional Subscribe lifecycle state.
pub use self::grpc_stream::YellowstoneGrpcSubscribeState;
/// One validated standard Yellowstone account predicate.
@@ -213,6 +215,8 @@ pub use self::grpc_subscribe::YellowstoneSubscribePingUpdate;
pub use self::grpc_subscribe::YellowstoneSubscribePongUpdate;
/// Provider-neutral standard Yellowstone Subscribe request.
pub use self::grpc_subscribe::YellowstoneSubscribeRequest;
/// Opaque deterministic identity for one complete Yellowstone Subscribe request.
pub use self::grpc_subscribe::YellowstoneSubscribeRequestIdentity;
/// Complete slot-family filter group for standard Yellowstone Subscribe.
pub use self::grpc_subscribe::YellowstoneSubscribeSlotFilter;
/// Complete transaction-family filter shared by transactions and transaction-status maps.
@@ -551,6 +555,8 @@ pub use self::ws_protocol_session::HeliusLaserStreamWsSession;
pub use self::ws_protocol_session::SolanaStandardWsSession;
/// Shareable compatibility handle for one explicitly created standard Solana physical WebSocket session.
pub use self::ws_session::WsSession;
/// Cloneable latest-value observer for one safe physical WebSocket session snapshot.
pub use self::ws_session::WsSessionSnapshotSource;
/// Open cluster or network descriptor used by WebSocket endpoint settings.
pub use self::ws_settings::WsClusterName;
/// Runtime settings for one named WebSocket endpoint.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
// version: 6
// version: 7
/// Typed facade for one standard Solana WebSocket physical session.
///
@@ -42,6 +42,12 @@ impl SolanaStandardWsSession {
return self.inner.snapshot();
}
/// Returns a cloneable latest-value observer for safe physical-session snapshots from the shared actor.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return self.inner.snapshot_source();
}
/// Returns the latest observable physical-session state.
#[must_use]
pub fn state(&self) -> crate::WsSessionState {
@@ -117,6 +123,12 @@ impl HeliusLaserStreamWsSession {
return self.inner.snapshot();
}
/// Returns a cloneable latest-value observer for safe physical-session snapshots from the shared actor.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return self.inner.snapshot_source();
}
/// Returns the latest observable physical-session state.
#[must_use]
pub fn state(&self) -> crate::WsSessionState {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/ws_session.rs
// version: 14
// version: 15
use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import
@@ -10,6 +10,43 @@ const HELIUS_WS_HEARTBEAT_INTERVAL: std::time::Duration = std::time::Duration::f
type WsPhysicalStream = tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
/// Cloneable latest-value observer for one physical WebSocket session snapshot.
///
/// The observer exposes only the already-safe [`crate::WsSessionSnapshot`] projection and keeps the internal Tokio watch channel private. Cloning it does
/// not create another socket, actor, reconnect loop or subscription registry.
#[derive(Clone)]
pub struct WsSessionSnapshotSource {
receiver: tokio::sync::watch::Receiver<crate::WsSessionSnapshot>,
}
impl crate::WsSessionSnapshotSource {
fn new(receiver: tokio::sync::watch::Receiver<crate::WsSessionSnapshot>) -> Self {
return Self { receiver };
}
/// Returns the current safe physical-session snapshot without waiting for another actor transition.
#[must_use]
pub fn current(&self) -> crate::WsSessionSnapshot {
return (*self.receiver.borrow()).clone();
}
/// Waits for one newer safe physical-session snapshot.
///
/// `None` means the owning WebSocket actor dropped the latest-value publisher and no further snapshot can arrive.
pub async fn wait_for_change(&mut self) -> std::option::Option<crate::WsSessionSnapshot> {
return match self.receiver.changed().await {
std::result::Result::Ok(()) => std::option::Option::Some((*self.receiver.borrow_and_update()).clone()),
std::result::Result::Err(_) => std::option::Option::None,
};
}
}
impl std::fmt::Debug for crate::WsSessionSnapshotSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("WsSessionSnapshotSource").field("current", &self.current()).finish();
}
}
/// Shareable handle for one explicitly created physical WebSocket session.
///
/// The handle never exposes the sensitive endpoint URL or the underlying socket. All socket I/O is owned by one internal actor task and all caller
@@ -124,6 +161,12 @@ impl WsSession {
return self.snapshot_rx.borrow().clone();
}
/// Returns a cloneable latest-value observer for safe physical-session snapshots.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return crate::WsSessionSnapshotSource::new(self.snapshot_rx.clone());
}
/// Returns the latest observable physical-session state.
#[must_use]
pub fn state(&self) -> crate::WsSessionState {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 50
// version: 54
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -569,6 +569,17 @@ fn public_v0_2_7_pre_004_physical_websocket_session_contract_is_available_from_c
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_WS_SESSION_CLOSED.code(), "ws_session_closed");
}
#[test]
fn public_v0_3_14_pre_003_websocket_snapshot_source_is_available_from_crate_root() {
let _physical = ksp_onchain_transport_lib::WsSession::snapshot_source;
let _standard = ksp_onchain_transport_lib::SolanaStandardWsSession::snapshot_source;
let _helius = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::snapshot_source;
let _current = ksp_onchain_transport_lib::WsSessionSnapshotSource::current;
let _wait = ksp_onchain_transport_lib::WsSessionSnapshotSource::wait_for_change;
assert!(std::any::type_name::<ksp_onchain_transport_lib::WsSessionSnapshotSource>().ends_with("WsSessionSnapshotSource"));
return;
}
#[test]
fn public_v0_2_7_pre_005_bounded_websocket_close_contract_is_available_from_crate_root() {
let _close = ksp_onchain_transport_lib::WsSession::close;
@@ -1013,10 +1024,16 @@ fn public_v0_2_9_pre_010_yellowstone_reconnect_snapshot_is_available_from_crate_
let _snapshot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot>();
let _state = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting;
let _session_snapshot = ksp_onchain_transport_lib::SolanaYellowstoneGrpcSubscribeSession::snapshot;
let _snapshot_source = ksp_onchain_transport_lib::SolanaYellowstoneGrpcSubscribeSession::snapshot_source;
let _source_type = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshotSource>();
let _source_current = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshotSource::current;
let _source_wait = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshotSource::wait_for_change;
let _reconnect_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::reconnect_count;
let _gap_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::continuity_gap_count;
let _duplicate_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::duplicate_update_count;
let _replay_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::replay_attempt_count;
let _delivery_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::replay_delivery_count;
let _coverage_unproven_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::replay_coverage_unproven_count;
let _requested = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::last_requested_from_slot;
let _observed = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::last_observed_slot;
let _terminal = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::terminal_error_code;
@@ -1028,3 +1045,15 @@ fn public_v0_3_10_pre_004_observed_get_block_surface_is_available_from_crate_roo
let _ = method;
return;
}
#[test]
fn public_v0_3_13_pre_002_yellowstone_subscribe_identity_is_opaque_and_available_from_crate_root() {
let request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
let identity = request.identity().expect("empty validated request identity must build");
let _identity_type = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeRequestIdentity>();
let debug = std::format!("{identity:?}");
assert!(debug.contains("YellowstoneSubscribeRequestIdentity"));
assert!(debug.contains("byte_len"));
assert!(!debug.contains("bytes:"));
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 42
// version: 46
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
@@ -1307,6 +1307,9 @@ fn release_v0_2_9_pre_010_adds_bounded_reconnect_replay_and_conservative_continu
for required in [
"YellowstoneGrpcSubscribeState::Reconnecting",
"YellowstoneGrpcSubscribeSnapshot",
"YellowstoneGrpcSubscribeSnapshotSource",
"snapshot_source",
"wait_for_change",
"reconnect_subscribe_stream",
"replay_first_available",
"SubscribeReplayInfo",
@@ -1336,3 +1339,63 @@ fn release_v0_2_9_pre_010_adds_bounded_reconnect_replay_and_conservative_continu
let _snapshot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot>();
let _state = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting;
}
#[test]
fn release_v0_3_13_pre_002_yellowstone_subscribe_identity_remains_opaque_and_dependency_neutral() {
let source = include_str!("../src/grpc_subscribe.rs");
let root = include_str!("../src/lib.rs");
let identity_start = source.find("pub struct YellowstoneSubscribeRequestIdentity").expect("identity struct must remain present");
let request_start = source.find("/// Provider-neutral standard Yellowstone subscribe request").expect("request contract marker must remain present");
let identity_surface = &source[identity_start..request_start];
assert!(source.contains("pub fn identity(&self)"));
assert!(root.contains("pub use self::grpc_subscribe::YellowstoneSubscribeRequestIdentity;"));
assert!(!identity_surface.contains("pub fn bytes("));
assert!(!identity_surface.contains("pub fn as_bytes("));
assert!(!root.contains("pub use yellowstone_grpc_proto"));
return;
}
#[test]
fn release_v0_3_14_pre_003_ws_snapshot_source_reuses_actor_watch_without_second_runtime() {
let session_source = include_str!("../src/ws_session.rs");
let facade_source = include_str!("../src/ws_protocol_session.rs");
let session_tests = include_str!("../unit_tests/ws_session.rs");
assert!(session_source.contains("pub struct WsSessionSnapshotSource"));
assert!(session_source.contains("self.snapshot_rx.clone()"));
assert!(facade_source.matches("pub fn snapshot_source").count() >= 2);
for required in [
"v0_3_14_pre_003_snapshot_source_tracks_latest_value_without_owning_runtime",
"websocket_reconnect_resubscribes_in_local_id_order_and_remaps_remote_ids",
"websocket_notification_queue_overflow_fails_only_slow_subscription_and_cleans_remote_binding",
] {
assert!(session_tests.contains(required), "missing pre.003 WebSocket continuity test: {required}");
}
assert!(!session_source.contains("WsSessionSnapshotSource {\n socket:"));
assert!(!facade_source.contains("tokio_tungstenite::connect_async"));
return;
}
#[test]
fn release_v0_3_14_pre_004_native_replay_evidence_never_conflates_attempt_delivery_and_coverage() {
let stream_source = include_str!("../src/grpc_stream.rs");
let stream_tests = include_str!("../unit_tests/grpc_stream.rs");
for required in [
"replay_attempt_count",
"replay_delivery_count",
"replay_coverage_unproven_count",
"pending_replay_from_slot",
"begin_replay",
"abandon_pending_replay",
"first_available > requested",
"This is conservative delivery evidence only",
"explicit lack of coverage proof",
] {
assert!(stream_source.contains(required), "missing pre.004 native replay evidence token: {required}");
}
assert!(stream_tests.contains("v0_3_14_pre_004_replay_acceptance_without_boundary_delivery_never_claims_coverage"));
assert!(stream_tests.contains("yellowstone_reconnect_replays_from_last_observed_slot_and_counts_duplicate_identity"));
assert!(stream_tests.contains("yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossless_claim"));
assert!(!stream_source.contains("replay_covered_count"));
assert!(!stream_source.contains("replay_coverage_proven_count"));
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
// version: 4
// version: 6
#[derive(Clone, Copy)]
enum FixtureMode {
@@ -13,6 +13,7 @@ enum FixtureMode {
Idle,
ReconnectReplay,
ReplayGap,
ReplayAcceptedWithoutBoundary,
ReconnectExhausted,
}
@@ -153,12 +154,31 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
}
}
},
FixtureMode::ReplayAcceptedWithoutBoundary => {
if subscribe_call == 1 {
assert_eq!(initial.from_slot, std::option::Option::None);
let _ = outbound_tx.send(std::result::Result::Ok(slot_update(800))).await;
} else {
assert_eq!(subscribe_call, 2);
assert_eq!(initial.from_slot, std::option::Option::Some(800));
if outbound_tx.send(std::result::Result::Ok(slot_update(805))).await.is_err() {
return;
}
let half_close = inbound.message().await;
if matches!(half_close, std::result::Result::Ok(std::option::Option::None)) {
half_close_seen.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
},
FixtureMode::ReconnectExhausted => {
assert_eq!(subscribe_call, 1);
let _ = outbound_tx.send(std::result::Result::Ok(slot_update(700))).await;
},
}
});
if matches!(mode, FixtureMode::ReconnectReplay) && subscribe_call == 2 {
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
return std::result::Result::Ok(tonic::Response::new(super::MpscStream::new(outbound_rx)));
}
@@ -188,7 +208,7 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
return std::result::Result::Err(error);
}
let first_available = match self.mode {
FixtureMode::ReconnectReplay | FixtureMode::ReconnectExhausted => std::option::Option::Some(400),
FixtureMode::ReconnectReplay | FixtureMode::ReconnectExhausted | FixtureMode::ReplayAcceptedWithoutBoundary => std::option::Option::Some(400),
FixtureMode::ReplayGap => std::option::Option::Some(505),
_ => return std::result::Result::Err(tonic::Status::unimplemented("replay info is outside this fixture mode")),
};
@@ -592,6 +612,8 @@ async fn yellowstone_reconnect_replays_from_last_observed_slot_and_counts_duplic
assert_eq!(snapshot.state(), crate::YellowstoneGrpcSubscribeState::Active);
assert_eq!(snapshot.reconnect_count(), 1);
assert_eq!(snapshot.replay_attempt_count(), 1);
assert_eq!(snapshot.replay_delivery_count(), 1);
assert_eq!(snapshot.replay_coverage_unproven_count(), 1);
assert_eq!(snapshot.continuity_gap_count(), 0);
assert_eq!(snapshot.duplicate_update_count(), 1);
assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(500));
@@ -624,6 +646,8 @@ async fn yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossles
let snapshot = session.snapshot();
assert_eq!(snapshot.reconnect_count(), 1);
assert_eq!(snapshot.replay_attempt_count(), 1);
assert_eq!(snapshot.replay_delivery_count(), 1);
assert_eq!(snapshot.replay_coverage_unproven_count(), 1);
assert_eq!(snapshot.continuity_gap_count(), 1);
assert_eq!(snapshot.duplicate_update_count(), 0);
assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(505));
@@ -632,6 +656,42 @@ async fn yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossles
server.stop().await;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_14_pre_004_replay_acceptance_without_boundary_delivery_never_claims_coverage() {
let server = FixtureServer::start(FixtureMode::ReplayAcceptedWithoutBoundary).await;
let defaults = crate::YellowstoneGrpcSessionSettings::default();
let settings = fixture_settings_with_reconnect(
server.endpoint_url.as_str(),
8,
8,
defaults.max_inbound_message_size_bytes(),
defaults.max_outbound_message_size_bytes(),
crate::YellowstoneGrpcReconnectSettings::new(3, std::time::Duration::from_millis(5), std::time::Duration::from_millis(20)),
);
let channel = crate::YellowstoneGrpcChannel::connect(&settings).await.expect("fixture channel must connect");
let mut session = channel.open_standard_subscribe(initial_request()).await.expect("fixture Subscribe stream must open");
let first = session.next_update().await.expect("first slot must decode").expect("first slot must be present");
match first {
crate::YellowstoneSubscribeUpdate::Slot(value) => assert_eq!(value.slot(), 800),
_ => panic!("fixture must return first Slot"),
}
let resumed = session.next_update().await.expect("post-reconnect slot must decode").expect("post-reconnect slot must be present");
match resumed {
crate::YellowstoneSubscribeUpdate::Slot(value) => assert_eq!(value.slot(), 805),
_ => panic!("fixture must return post-reconnect Slot"),
}
let snapshot = session.snapshot();
assert_eq!(snapshot.reconnect_count(), 1);
assert_eq!(snapshot.replay_attempt_count(), 1);
assert_eq!(snapshot.replay_delivery_count(), 0);
assert_eq!(snapshot.replay_coverage_unproven_count(), 1);
assert_eq!(snapshot.continuity_gap_count(), 0);
assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(800));
assert_eq!(snapshot.last_observed_slot(), std::option::Option::Some(805));
session.close().await.expect("coverage-unproven fixture must close cleanly");
server.stop().await;
}
#[tokio::test(flavor = "current_thread")]
async fn yellowstone_reconnect_budget_exhaustion_is_terminal_and_safe() {
let server = FixtureServer::start(FixtureMode::ReconnectExhausted).await;
@@ -653,6 +713,8 @@ async fn yellowstone_reconnect_budget_exhaustion_is_terminal_and_safe() {
assert_eq!(snapshot.state(), crate::YellowstoneGrpcSubscribeState::Failed);
assert_eq!(snapshot.reconnect_count(), 0);
assert_eq!(snapshot.replay_attempt_count(), 2);
assert_eq!(snapshot.replay_delivery_count(), 0);
assert_eq!(snapshot.replay_coverage_unproven_count(), 0);
assert_eq!(snapshot.terminal_error_code(), std::option::Option::Some(crate::ERROR_CODE_GRPC_CHANNEL_FAILED));
let rendered = format!("{error:?} {session:?}");
assert!(!rendered.contains("GRPC-RECONNECT-SECRET-CANARY"));
@@ -662,6 +724,45 @@ async fn yellowstone_reconnect_budget_exhaustion_is_terminal_and_safe() {
server.stop().await;
}
#[tokio::test(flavor = "current_thread")]
async fn yellowstone_snapshot_source_observes_replay_attempt_before_successful_reconnect() {
let server = FixtureServer::start(FixtureMode::ReconnectReplay).await;
let defaults = crate::YellowstoneGrpcSessionSettings::default();
let settings = fixture_settings_with_reconnect(
server.endpoint_url.as_str(),
8,
8,
defaults.max_inbound_message_size_bytes(),
defaults.max_outbound_message_size_bytes(),
crate::YellowstoneGrpcReconnectSettings::new(3, std::time::Duration::from_millis(5), std::time::Duration::from_millis(20)),
);
let channel = crate::YellowstoneGrpcChannel::connect(&settings).await.expect("fixture channel must connect");
let mut session = channel.open_standard_subscribe(initial_request()).await.expect("fixture Subscribe stream must open");
let mut snapshots = session.snapshot_source();
assert_eq!(snapshots.current().state(), crate::YellowstoneGrpcSubscribeState::Active);
let _ = session.next_update().await.expect("first slot must decode").expect("first slot must be present");
let replaying = tokio::time::timeout(std::time::Duration::from_secs(1), async {
loop {
let snapshot = match snapshots.wait_for_change().await {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
if snapshot.replay_attempt_count() > 0 && snapshot.reconnect_count() == 0 {
return std::option::Option::Some(snapshot);
}
}
})
.await
.expect("snapshot source must observe reconnect progress")
.expect("snapshot source must stay open during reconnect");
assert_eq!(replaying.state(), crate::YellowstoneGrpcSubscribeState::Reconnecting);
assert_eq!(replaying.replay_attempt_count(), 1);
assert_eq!(replaying.reconnect_count(), 0);
let _ = session.next_update().await.expect("replayed duplicate must decode").expect("replayed duplicate must be present");
session.close().await.expect("reconnected stream must close cleanly");
server.stop().await;
}
#[tokio::test(flavor = "current_thread")]
async fn yellowstone_shutdown_interrupts_reconnect_backoff_and_mutation_is_rejected_during_reconnect() {
let server = FixtureServer::start(FixtureMode::ReconnectReplay).await;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
// version: 5
// version: 6
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
@@ -138,6 +138,37 @@ fn yellowstone_subscribe_common_bounds_reject_before_wire_conversion() {
assert!(filters.insert_account_filter(excess_name, crate::YellowstoneSubscribeAccountFilter::new()).is_err());
}
#[test]
fn yellowstone_subscribe_request_identity_is_order_stable_exact_and_debug_redacted() {
let mut first = crate::YellowstoneSubscribeRequest::new();
let mut second = crate::YellowstoneSubscribeRequest::new();
let mut changed = crate::YellowstoneSubscribeRequest::new();
let mut filter_a = crate::YellowstoneSubscribeTransactionFilter::new();
filter_a.set_failed(std::option::Option::Some(false));
let mut filter_b = crate::YellowstoneSubscribeTransactionFilter::new();
filter_b.set_vote(std::option::Option::Some(false));
assert!(first.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b.clone()).is_ok());
assert!(first.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a.clone()).is_ok());
assert!(second.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a.clone()).is_ok());
assert!(second.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b.clone()).is_ok());
filter_a.set_failed(std::option::Option::Some(true));
assert!(changed.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a).is_ok());
assert!(changed.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b).is_ok());
first.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
second.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
changed.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
let first_identity = first.identity().expect("first request identity must build");
let second_identity = second.identity().expect("second request identity must build");
let changed_identity = changed.identity().expect("changed request identity must build");
assert_eq!(first_identity, second_identity);
assert_ne!(first_identity, changed_identity);
let debug = std::format!("{first_identity:?}");
assert!(debug.contains("byte_len"));
assert!(!debug.contains("identity-filter-alpha-canary"));
assert!(!debug.contains("identity-filter-beta-canary"));
return;
}
#[test]
fn yellowstone_subscribe_debug_omits_filter_names_and_future_payloads() {
let mut request = crate::YellowstoneSubscribeRequest::new();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
// version: 11
// version: 12
use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import
@@ -1355,3 +1355,40 @@ async fn websocket_dropped_notification_receiver_triggers_remote_cleanup_and_rel
session.close().await.expect("session close must remain bounded");
server.await.expect("local server task must complete");
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_14_pre_003_snapshot_source_tracks_latest_value_without_owning_runtime() {
let session_id = crate::WsSessionId::new(std::num::NonZeroU64::new(1).expect("test session ID must be non-zero"));
let initial = crate::WsSessionSnapshot::new(
session_id,
"local_ws",
crate::WsProviderName::new("local-fixture"),
crate::WsClusterName::new("local"),
crate::WsProtocolKind::SolanaStandard,
crate::WsSessionState::Active,
0,
0,
0,
std::vec::Vec::new(),
);
let reconnecting = crate::WsSessionSnapshot::new(
session_id,
"local_ws",
crate::WsProviderName::new("local-fixture"),
crate::WsClusterName::new("local"),
crate::WsProtocolKind::SolanaStandard,
crate::WsSessionState::Reconnecting { attempt: 1 },
0,
1,
0,
std::vec::Vec::new(),
);
let (sender, receiver) = tokio::sync::watch::channel(initial.clone());
let mut source = crate::WsSessionSnapshotSource::new(receiver);
assert_eq!(source.current(), initial);
sender.send_replace(reconnecting.clone());
assert_eq!(source.wait_for_change().await, std::option::Option::Some(reconnecting));
drop(sender);
assert_eq!(source.wait_for_change().await, std::option::Option::None);
return;
}

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-raw-transaction-lib/README.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# ksp-raw-transaction-lib
@@ -26,6 +26,7 @@ La canonicalisation produit un `ksp_store_api::RawTransaction` avec payload KSP
### Signature Solana
- `parse_raw_transaction_signature` valide et décode une signature Base58 bornée vers exactement 64 bytes ;
- `format_raw_transaction_signature` encode exactement 64 bytes canoniques vers la forme Base58 bornée correspondante ;
- `extract_raw_transaction_signature_from_binary_base64` extrait la première signature canonique d'un wire transactionnel Base64 complet ;
- les bornes textuelles publiques permettent l'admission avant décodage.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-raw-transaction-lib/USAGE.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Utilisation de ksp-raw-transaction-lib
@@ -17,6 +17,16 @@ fn parse_signature(value: &str) -> ksp_core_lib::Result<ksp_store_api::RawTransa
Le résultat contient exactement 64 bytes canoniques. Les entrées vides, hors bornes, Base58 invalides ou de longueur décodée incorrecte sont rejetées sans recopier la valeur hostile dans l'erreur.
Lorsqu'un consumer possède déjà les 64 bytes canoniques et doit appeler une API textuelle Solana, utiliser l'encodeur commun plutôt que d'implémenter Base58 localement :
```rust
fn format_signature(value: &ksp_store_api::RawTransactionSignature) -> std::string::String {
return ksp_raw_transaction_lib::format_raw_transaction_signature(value);
}
```
Le texte produit respecte les bornes publiques de signature et effectue un round-trip exact avec `parse_raw_transaction_signature`.
Lorsqu'un wire Base64 complet contient déjà son tableau de signatures, utiliser :
```rust

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-raw-transaction-lib/src/lib.rs
// version: 3
// version: 4
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -7,7 +7,7 @@
//! Source-neutral canonicalization of complete Solana transactions into KSP RAW transaction v1.
//!
//! This lower layer owns the frozen RAW transaction format, textual signature parsing, canonical
//! This lower layer owns the frozen RAW transaction format, textual signature parsing/formatting, canonical
//! JSON construction, content hashing and assembly of one canonical transaction with one
//! producer-owned observation. It owns no Transport, Config, Job, Worker, async runtime or Store
//! backend behavior.
@@ -46,6 +46,8 @@ pub use self::signature::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES;
pub use self::signature::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES;
/// Extracts the first canonical 64-byte Solana signature from one complete Base64 transaction wire.
pub use self::signature::extract_raw_transaction_signature_from_binary_base64;
/// Encodes one canonical 64-byte Solana transaction signature as bounded Base58 text.
pub use self::signature::format_raw_transaction_signature;
/// Parses one bounded Base58 Solana transaction signature to exactly 64 canonical bytes.
pub use self::signature::parse_raw_transaction_signature;
/// One source-neutral v0 address-table lookup.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-raw-transaction-lib/src/signature.rs
// version: 3
// version: 4
use base64::Engine; // rust-rules: trait-import
@@ -8,6 +8,35 @@ pub const MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES: usize = 88;
/// Minimum UTF-8 byte length admitted for one textual Base58 Solana transaction signature.
pub const MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES: usize = 64;
/// Encodes one canonical 64-byte Solana transaction signature as bounded Base58 text.
#[must_use]
pub fn format_raw_transaction_signature(signature: &ksp_store_api::RawTransactionSignature) -> std::string::String {
const ALPHABET: &[u8; 58] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
let bytes = signature.as_bytes();
let leading_zeroes = bytes.iter().take_while(|byte| return **byte == 0).count();
let mut digits = std::vec::Vec::with_capacity(crate::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES);
for byte in bytes {
let mut carry = u32::from(*byte);
for digit in &mut digits {
let expanded = (u32::from(*digit) * 256) + carry;
*digit = (expanded % 58) as u8;
carry = expanded / 58;
}
while carry != 0 {
digits.push((carry % 58) as u8);
carry /= 58;
}
}
let mut output = std::string::String::with_capacity(leading_zeroes + digits.len());
for _ in 0..leading_zeroes {
output.push('1');
}
for digit in digits.iter().rev() {
output.push(char::from(ALPHABET[usize::from(*digit)]));
}
return output;
}
/// Parses one bounded Base58 Solana transaction signature to exactly 64 canonical bytes.
pub fn parse_raw_transaction_signature(value: &str) -> ksp_core_lib::Result<ksp_store_api::RawTransactionSignature> {
let text = value.as_bytes();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-raw-transaction-lib/tests/public_api.rs
// version: 4
// version: 5
//! Integration canaries for the public common RAW Transaction crate-root surface.
@@ -120,3 +120,16 @@ fn pre_006_source_neutral_wire_contract_is_available_from_crate_root() {
}
return;
}
#[test]
fn pre_004_signature_formatter_is_consumable_from_crate_root() {
let signature = ksp_store_api::RawTransactionSignature::new([0_u8; 64]);
let text = ksp_raw_transaction_lib::format_raw_transaction_signature(&signature);
assert_eq!(text, "1".repeat(64));
let parsed = ksp_raw_transaction_lib::parse_raw_transaction_signature(text.as_str());
assert!(parsed.is_ok());
if let std::result::Result::Ok(parsed) = parsed {
assert_eq!(parsed, signature);
}
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-raw-transaction-lib/unit_tests/signature.rs
// version: 3
// version: 4
use base64::Engine; // rust-rules: trait-import
@@ -79,3 +79,19 @@ fn pre_006_embedded_signature_extracts_terminal_v1_signature_and_rejects_trailin
assert!(crate::extract_raw_transaction_signature_from_binary_base64(trailing.as_str()).is_err());
}
}
#[test]
fn pre_004_base58_formatter_round_trips_canonical_signatures() {
for bytes in [[0_u8; 64], [1_u8; 64], [0xAB_u8; 64]] {
let signature = ksp_store_api::RawTransactionSignature::new(bytes);
let text = crate::format_raw_transaction_signature(&signature);
assert!((crate::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES..=crate::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES).contains(&text.len()));
let parsed = crate::parse_raw_transaction_signature(text.as_str());
assert!(parsed.is_ok());
if let std::result::Result::Ok(parsed) = parsed {
assert_eq!(parsed, signature);
}
}
assert_eq!(crate::format_raw_transaction_signature(&ksp_store_api::RawTransactionSignature::new([0_u8; 64])), "1".repeat(64));
return;
}

View File

@@ -1,5 +1,5 @@
# file: crates/ksp-worker-raw-transaction-ingest-lib/Cargo.toml
# version: 1
# version: 2
[package]
name = "ksp-worker-raw-transaction-ingest-lib"
@@ -10,6 +10,7 @@ repository.workspace = true
[dependencies]
ksp-core-lib = { path = "../ksp-core-lib" }
ksp-logging-lib = { path = "../ksp-logging-lib" }
ksp-onchain-transport-lib = { path = "../ksp-onchain-transport-lib" }
ksp-raw-transaction-lib = { path = "../ksp-raw-transaction-lib" }
ksp-store-lib = { path = "../ksp-store-lib", default-features = false }
ksp-worker-api = { path = "../ksp-worker-api" }

View File

@@ -1,65 +1,276 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/README.md -->
<!-- version: 1 -->
<!-- version: 12 -->
# ksp-worker-raw-transaction-ingest-lib
`ksp-worker-raw-transaction-ingest-lib` est le premier Worker concret de KSP pour l'alimentation continue de la couche RAW Transaction.
`ksp-worker-raw-transaction-ingest-lib` est le Worker concret KSP chargé de l'alimentation continue de la couche RAW Transaction.
La crate fournit la fondation runtime **source-neutral** du Worker : identité et settings bornés, lifecycle Start/Stop, admission privée bornée, canonicalisation via `ksp-raw-transaction-lib`, persistance backend-neutral via `ksp-store-lib`, supervision des tâches, shutdown borné et snapshots latest-value projetables sur `ksp-worker-api`.
La fondation ne contient volontairement encore **aucune source réseau productive**. Elle ne dépend pas de `ksp-onchain-transport-lib` et n'expose pas d'API publique permettant au caller d'injecter directement des transactions dans la queue interne. Les adapters live/catch-up sont des responsabilités ultérieures du même Worker, pas de son API de fondation.
## Identité et réseau
Une exécution est liée à :
La crate possède deux niveaux publics complémentaires :
```text
RawNetworkId
WorkerId
Worker kind = raw_transaction_ingest
RawTransactionIngestWorker::start
-> fondation source-neutral, sans source productive
RawTransactionIngestWorker::start_with_runtime_resources
-> même runtime + 1..32 sources productives supervisées simultanément
Yellowstone, WS standard logsSubscribe, WS standard blockSubscribe, Helius transactionSubscribe et/ou HTTP block polling
+ hydration HTTP getTransaction lorsque la source produit une référence
```
Le réseau logique doit être identique à celui du `Store` remis au démarrage. La transaction canonique conserve l'identité durable définie par la couche RAW commune ; le Worker n'ajoute ni provider, ni endpoint, ni protocole à cette identité.
Le Worker reste indépendant de Config et de tout backend Store physique. Le caller compose les ressources Transport et Store, puis les remet à la crate par ses façades publiques.
## Runtime
## Pipeline productif actuel
Le point d'entrée public est :
Les verticales live productives sont :
```text
RawTransactionIngestWorker::start(settings, Arc<Store>)
-> RawTransactionIngestHandle
Yellowstone standard subscribe
-> Transaction / TransactionStatus / Block
-> signal source-neutral (network, signature, slot, commitment, provenance sûre)
Solana standard WS logsSubscribe
-> context.slot + signature
-> signal source-neutral (network, signature, slot, commitment, provenance sûre)
-> coalescence bornée par (network, signature, commitment)
-> HTTP getTransaction observed, Base64, maxSupportedTransactionVersion=1
Solana standard WS blockSubscribe
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
-> qualification explicite Legacy / V0 / V1
-> matériau Common RAW direct par transaction du bloc
Helius transactionSubscribe
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
-> signature + slot + transactionIndex uniquement dans le signal Worker
-> coalescence bornée par (network, signature, commitment)
-> HTTP getTransaction observed avant Common RAW
Solana HTTP live block polling
-> getSlot borne le run courant
-> getBlocksWithLimit découvre les blocs live
-> getBlock observed matérialise Full/Base64 Legacy/V0/V1
-> matériau Common RAW direct par transaction du bloc
les chemins productifs
-> ksp-raw-transaction-lib
-> admission centrale bornée
-> ksp-store-lib
-> RawTransaction + RawTransactionObservation
```
Le démarrage est synchrone mais nécessite qu'un runtime Tokio courant appartienne déjà au caller. Le Worker ne crée pas de runtime global et n'expose aucun `JoinHandle` public.
`BlockMeta` et `Slot` ne produisent pas de RAW directement. Ils servent uniquement à la projection de continuité du run.
La qualification reste conservative : même lorsqu'une update Yellowstone contient une transaction complète côté protobuf, le Worker hydrate actuellement les signaux transactionnels par HTTP `getTransaction` avant de construire le RAW canonique. Il n'existe donc pas de second canonicaliseur Yellowstone.
## Contrat de source Yellowstone + HTTP
`RawTransactionIngestYellowstoneSource::new` reçoit :
```text
YellowstoneGrpcChannel
YellowstoneSubscribeRequest
HttpTransportPool
HttpRoleName d'hydration
```
La construction est sans I/O et refuse notamment :
- une requête Yellowstone invalide ;
- l'absence de famille porteuse d'ingestion ;
- un commitment `Processed` ou implicite ;
- un réseau Yellowstone non représentable ;
- une provenance provider/endpoint non représentable ;
- l'absence d'une route HTTP compatible pour `getTransaction` sur le même réseau.
Le runtime-resource aggregate public accepte une collection validée de 1 à 32 sources logiques et les démarre simultanément sous un supervisor privé. La collection est validée entièrement avant spawn ; aucun sous-ensemble silencieux, source primaire implicite ou standby n'est choisi. La collection interne, les `source_key`, les URLs, les filtres et les clients inférieurs ne sont pas exposés.
Le supervisor possède toutes les tâches source. Une perte de source n'est plus assimilée automatiquement à un fault : lorsqu'elle porte une plage de continuité bornée, le Worker l'inscrit dans son ledger run-local puis n'autorise la continuation des siblings que si la coverage passée de cette perte est réconciliée et si les sources encore actives couvrent explicitement tout le `TargetCoverage` futur. Une perte sans plage sûre, une coverage insuffisante ou un gap encore ouvert reste terminal. Le Worker ne respawn jamais lui-même une source Transport.
Un inventaire privé `source_key -> latest processing/source state`, borné à 32 entrées, agrège la projection run-local. La frontier agrégée reste conservative : elle n'expose un `processing_frontier_slot` que lorsque toutes les sources en possèdent un, choisit le minimum des frontiers connus et le plus ancien pending. Les sources reference-bearing partagent en plus un registre global d'hydration borné : une même clé `(network, signature, commitment)` ne déclenche qu'un leader HTTP, puis chaque signal source conserve sa propre provenance lors de la finalisation.
## Contrat de source Standard Logs + HTTP
`RawTransactionIngestStandardLogsSource::new` reçoit :
```text
WsEndpointSettings kind solana_standard
SolanaLogsSubscribeFilter
SolanaCommitment Confirmed ou Finalized
HttpTransportPool
HttpRoleName d'hydration
```
La construction est sans I/O. Elle refuse un endpoint WS invalide ou non standard, `Processed`, un réseau/provenance non représentable et l'absence de route HTTP `getTransaction` compatible sur le même réseau. Le filtre `All`, `AllWithVotes` ou `Mentions(pubkey)` participe uniquement à une empreinte privée ; le pubkey d'un filtre `Mentions` n'est pas recopié dans `Debug`, snapshot ou provenance textuelle.
Au runtime, le Worker ouvre `SolanaStandardWsSession::connect`, puis `logs_subscribe`. Les lignes de logs et `err` restent dans Transport et ne sont jamais stockées dans le signal Worker. Seuls `context.slot` et `signature` sont projetés vers l'hydration commune. Reconnect, resubscribe et backpressure de la subscription restent possédés par Transport.
## Contrat de source Standard Block direct RAW
`RawTransactionIngestStandardBlockSource::new` reçoit :
```text
WsEndpointSettings kind solana_standard
SolanaBlockSubscribeFilter
SolanaCommitment Confirmed ou Finalized
```
Le runtime ouvre la `SolanaStandardWsSession` existante et demande exactement `Base64`, `Full`, `maxSupportedTransactionVersion = 1` et `showRewards = false`. Aucun `HttpTransportPool` n'est attaché à cette source : les transactions dont la version est explicitement `Legacy`, `0` ou `1` sont transformées directement en `RawTransactionMaterial` à partir du wire Base64 du bloc, avec signature embarquée, slot, block time, meta, version et index de transaction.
La qualification est fermée : une version omise/nulle ou supérieure à `1`, une transaction non Base64, `block: null`, une erreur distante de notification, un slot de contexte incohérent ou un champ transactions absent/nul termine la source par une erreur sûre. Aucun de ces cas n'est converti en bloc vide, en succès silencieux ou en progression artificielle de frontier.
Pour un bloc multi-transaction, le slot n'est projeté settled qu'après l'admission réussie de toutes ses transactions. Cette voie RAW-direct n'incrémente pas `hydration_pending`; un stop ou une erreur au milieu du bloc ne produit aucune progression artificielle. Reconnect, resubscribe et backpressure WebSocket restent possédés par Transport.
## Contrat de source Helius Transaction + HTTP
`RawTransactionIngestHeliusTransactionSource::new` reçoit :
```text
WsEndpointSettings kind helius_laserstream
HeliusTransactionSubscribeFilter
SolanaCommitment Confirmed ou Finalized
HttpTransportPool
HttpRoleName d'hydration
```
La construction est sans I/O. Elle réutilise le contrat Transport existant et impose `Full`, `Base64`, `showRewards = false` et `maxSupportedTransactionVersion = 1`. Le Worker ne lit pas Config, ne lit pas `KSP_SECRET_HELIUS_API_KEY` et ne code aucun tier provider ; l'URL résolue et le credential restent dans l'endpoint Transport fourni par le caller.
Au runtime, `HeliusLaserStreamWsSession::connect` puis `transaction_subscribe` sont utilisés. Seule une notification `Full` conforme au mode demandé est admise ; une forme `Signature`, `Unknown` ou future devient une faute source sûre. Le payload Helius `transaction` n'est jamais copié dans l'état Worker : la projection conserve uniquement signature, slot et transaction index, puis réutilise le coordinateur d'hydration commun `getTransaction observed`.
La clé logique Helius inclut réseau, identités provider/endpoint sûres, commitment et empreinte privée du filtre. Les listes de pubkeys du filtre sont normalisées avant hash afin que leur ordre ne crée pas artificiellement deux sources logiques ; le rôle HTTP d'hydration reste exclu de l'identité live.
## Contrat de source HTTP Block Polling
`RawTransactionIngestHttpBlockPollingSource::new` reçoit :
```text
HttpTransportPool
HttpRoleName de polling
SolanaCommitment Confirmed ou Finalized
```
La construction est sans I/O et vérifie que le rôle HTTP possède, sur un seul réseau, les capacités `getSlot`, `getBlocksWithLimit` et `getBlock`. La cadence est bornée entre 100 ms et 30 s, avec 1 s par défaut ; la découverte est bornée entre 1 et 1024 blocs par cycle, avec 128 par défaut. Ces réglages de cadence ne font pas partie de l'identité logique de la source.
Au démarrage, le premier `getSlot` fixe la borne inférieure du run. Le Worker ne demande aucun slot antérieur. Chaque cycle relit le tip, découvre les blocs disponibles avec `getBlocksWithLimit`, puis matérialise chaque slot listé par `getBlock observed` en `Full + Base64 + maxSupportedTransactionVersion = 1 + showRewards = false`. Seules les transactions Legacy/V0/V1 explicitement qualifiées entrent directement dans Common RAW.
Un slot listé dont `getBlock` retourne `null` reste la tête de reprise du cycle suivant ; il n'est ni considéré vide ni marqué settled. Les slots absents de la liste de découverte sont traités comme non produits/skipped pour ce run. Le slot n'est settled qu'après admission réussie de toutes ses transactions. Le polling reste run-local : aucun checkpoint durable, aucun scan avant la borne initiale et aucun Backfill implicite ne sont créés. Les retries/reroutages HTTP restent possédés par `ksp-onchain-transport-lib`.
## Runtime et lifecycle
Le Worker s'exécute sur le runtime Tokio courant du caller. Il ne crée pas de runtime global et n'expose aucun `JoinHandle` public.
`RawTransactionIngestHandle` permet de :
- demander un stop coopératif et idempotent ;
- obtenir une source de snapshots concrets latest-value ;
- utiliser cette même source via `WorkerSnapshotSource` ;
- attendre le terminal après drain/abort+join des tâches possédées.
- lire une source de snapshots concrets latest-value ;
- utiliser la même source via `WorkerSnapshotSource` ;
- attendre le terminal après drain et join des tâches possédées.
La destruction du dernier handle de contrôle ferme aussi la voie de contrôle privée ; le runtime termine alors selon les mêmes règles de shutdown.
Le shutdown est borné par `shutdown_drain_timeout`. Le supervisor multi-source relaie le stop à toutes les sources et les rejoint avant de rendre son résultat au supervisor Worker ; les tâches source, hydration et persistence possédées sont ensuite drainées ou abort+join avant publication terminale. Si la deadline expire, l'abort du wrapper source détruit aussi son `JoinSet` interne et annule ses tâches imbriquées avant le terminal. Une faute déjà observée n'est pas remplacée par un stop concurrent, sauf le `drain_timeout` terminal lorsqu'une récupération bornée dépasse sa deadline. L'abandon terminal d'une hydration retire son pending run-local sans le convertir artificiellement en travail `settled`.
## Admission et Common RAW
## Admission, coalescence et backpressure
La queue centrale est un `tokio::sync::mpsc` privé borné par `admission_queue_capacity`. Les sources internes doivent subir la backpressure du channel ; aucune queue non bornée ni silent drop n'est autorisé.
La queue centrale est un `tokio::sync::mpsc` privé borné par `admission_queue_capacity`. Les sources internes subissent la backpressure ; aucune queue non bornée ni silent drop n'est autorisé.
Chaque ingress admis est :
Le Worker possède des coordinateurs source-neutral Yellowstone, Standard Logs et Helius Transaction branchés sur un registre global d'hydration partagé. Standard Block et HTTP Block Polling n'entrent pas dans ce registre lorsqu'une transaction est direct-qualified.
1. vérifié contre le réseau attendu ;
2. canonicalisé exclusivement par `ksp-raw-transaction-lib` ;
3. associé à une observation key déterministe sous le domaine `ksp.raw_transaction_ingest.observation.v1` ;
4. assemblé en acquisition RAW commune ;
5. remis à la persistence Store.
Les sources reference-bearing reçoivent des quotas déterministes dont la somme reste exactement dans les bornes techniques configurées :
La crate ne possède pas un second format RAW et ne duplique pas le canonicaliseur commun.
```text
somme pending source signals <= admission_queue_capacity
somme hydration tasks in flight <= persistence_concurrency
source reference-bearing active => quota pending >= 1 et quota in-flight >= 1
```
Pour garantir simultanément ces bornes et l'absence de starvation structurelle, le démarrage échoue avant spawn si le nombre de sources reference-bearing dépasse `admission_queue_capacity` ou `persistence_concurrency`. Un sémaphore global protège en plus l'ouverture effective des hydrations HTTP.
Les signaux partageant le même `(network, signature, commitment)` sont coalescés cross-source avant le fan-out HTTP. La publication du résultat partagé notifie les followers sous le verrou de registry avant de retirer la clé : une nouvelle génération de leader ne peut donc pas s'intercaler entre retrait et notification. Après canonicalisation, une cache run-local bornée sérialise les acquisitions de même `(network, signature)` : la première passe par l'écriture atomique entity + observation, les suivantes de contenu canonique identique ajoutent uniquement leur observation déterministe. Une divergence de slot, block time, format ou hash canonique devient un content conflict terminal ; aucune majorité, préférence provider ou overwrite n'est appliqué. Le Store conserve son guard durable final.
Les retries/reroutages HTTP appartiennent à `ksp-onchain-transport-lib`. Le Worker ne possède pas une seconde boucle de retry autour de `getTransaction`.
Le trafic nominal et le trafic de réparation partagent le même registre global d'hydration, les mêmes permits, la même admission et la même persistence. Un gate de fairness privé alterne les deux classes lorsqu'elles attendent simultanément, sans réserver une fraction fixe de capacité ; une capacité existante de `1` doit donc encore permettre la progression des deux classes.
## Processing frontier run-local
Le snapshot expose :
```text
hydration_pending
processing_frontier_slot
oldest_pending_slot
```
Cette frontier mesure uniquement le traitement des signaux réellement observés pendant le run courant. Elle n'est ni un checkpoint durable, ni une preuve de complétude blockchain, ni un curseur de Backfill.
Un signal transactionnel devient pending après validation de sa clé d'hydration et insertion dans le coordinateur. Il devient settled pour la source lorsque :
```text
getTransaction -> Missing
ou
getTransaction -> Available puis ingress envoyé avec succès vers l'admission centrale
```
Un `BlockMeta` ou `Slot` continuity-only est settled localement sans produire de RAW. La frontier n'avance jamais à travers le plus ancien pending connu.
## Reconnect, replay, gaps et réparation run-local
Le reconnect/replay Yellowstone appartient à Transport. Le Worker n'écrit pas `from_slot`, ne traite pas directement `SubscribeReplayInfo` et ne transforme jamais une simple reconnexion en preuve de continuité.
Le Worker maintient séparément la processing frontier et une continuity frontier gap-aware. Les gaps sont des intervalles inclusifs bornés du run courant ; ils ne constituent ni une campagne historique ni une liste présumée de transactions manquantes. Les bornes internes sont :
```text
open gaps <= 64
range d'un gap <= 4096 slots
discovery HTTP par fenêtre <= 512 slots
getBlock logiques concurrents <= 4
repair actif simultané <= 1
```
Les mécanismes admissibles restent conservatifs : replay Transport lorsqu'il est réellement adressable, preuve de coverage d'une autre source compatible, scan HTTP borné, récupération de bloc produit et hydration d'une référence connue. Une réponse `getTransaction = null` reste une obligation manquante et `getBlock = null` pour un slot prouvé produit ne devient jamais une preuve d'absence.
Les types publics source-neutral `RawTransactionIngestGapId`, `RawTransactionIngestGapState`, `RawTransactionIngestGapReason`, `RawTransactionIngestRepairMethod` et `RawTransactionIngestGapSnapshot` permettent d'observer les gaps sans exposer `source_key`, provider, endpoint, filtre, signature ou payload. Le snapshot concret expose notamment :
```text
gaps()
open_gap_count()
repairing_gap_count()
repaired_gap_total()
unresolved_gap_total()
replay_repair_total()
redundant_coverage_repair_total()
http_scan_repair_total()
repair_block_fetch_total()
repair_transaction_hydration_total()
oldest_open_gap_start_slot()
```
La liste détaillée reste bornée ; tous les gaps ouverts sont retenus et les entrées réparées récentes peuvent occuper la capacité restante. Les compteurs utilisent une arithmétique checked.
Le snapshot conserve également les informations source-neutral suivantes :
```text
source_total
source_active
source_reconnecting
source_failed
source_state
source_failure_total
backpressure_wait_total
source_reconnect_total
source_replay_attempt_total
source_continuity_gap_total
```
`RawTransactionIngestSourceState` distingue `Active`, `Reconnecting`, `Closing`, `Closed` et `Failed`. Un replay attempt, une redelivery de frontière et une coverage d'intervalle restent des preuves différentes.
La health publique est volontairement stricte dès que la policy de continuité est active : reconnect en cours, gap ouvert, continuity frontier différente de la processing frontier ou `TargetCoverage` futur non couvert donnent `Unhealthy`. `Healthy` exige toutes les sources attendues actives et aucune lacune de continuité. `Degraded` n'est permis qu'après perte de source explicitement réconciliée, lorsque les sources restantes couvrent encore tout le `TargetCoverage` futur.
Une source perdue peut donc rester absente uniquement si sa perte est bornée, si ses gaps sont fermés, si la continuity frontier rejoint la processing frontier et si la coverage future reste prouvée. Dans tous les autres cas, le Worker fault avec une erreur source sûre. Il ne lance jamais `ksp-job-backfill-lib` ni une campagne historique automatique.
## Persistence Store
La persistance passe uniquement par `ksp-store-lib` avec `default-features = false` dans la crate Worker. Aucun backend physique n'est imposé ou importé directement.
La persistance passe exclusivement par `ksp-store-lib` avec `default-features = false`. Aucun backend physique n'est importé directement.
L'écriture utilise le mode normal d'acquisition atomique `RawTransaction + RawTransactionObservation`. Les outcomes distingués sont notamment :
L'écriture utilise le mode normal atomique `RawTransaction + RawTransactionObservation`. Les outcomes distingués incluent :
```text
entity inserted
@@ -72,21 +283,13 @@ content conflict
store failure
```
`persistence_concurrency` borne le nombre d'écritures Store simultanées. Un conflit de contenu est terminal et n'est jamais converti en succès idempotent.
Un content conflict est terminal et n'est jamais converti en succès idempotent.
## Shutdown et faults
## Snapshots et erreurs
Le shutdown possède une deadline bornée par `shutdown_drain_timeout`.
`RawTransactionIngestSnapshotSource` est latest-value : les lecteurs peuvent rater des transitions intermédiaires mais récupèrent toujours la dernière projection complète et monotone.
Avant publication terminale, le supervisor :
- arrête les nouvelles admissions ;
- signale le stop aux sources privées ;
- draine le travail déjà admis tant que la deadline le permet ;
- récolte les persistences et sources possédées ;
- en cas de timeout, abort les tâches restantes puis les rejoint avant le terminal.
Les codes d'erreur publics du Worker sont :
Les codes Worker publics sont :
```text
worker_raw_transaction_ingest.settings_invalid
@@ -98,26 +301,7 @@ worker_raw_transaction_ingest.source_failed
worker_raw_transaction_ingest.drain_timeout
```
Les diagnostics ne recopient pas de payload RAW, URL, credential, signature hostile ou texte backend/provider arbitraire.
## Snapshots
`RawTransactionIngestSnapshotSource` est latest-value. Les listeners peuvent rater des états intermédiaires mais obtiennent toujours une valeur complète et monotone par `WorkerSnapshotSequence`.
Le snapshot concret expose notamment :
```text
lifecycle / health / activity
admission_queue_capacity / admission_queue_depth
persistence_concurrency / in_flight_persistence
admitted_total / canonicalized_total / persisted_total
entity_inserted_total / entity_already_present_total / entity_skipped_purged_total
observation_inserted_total / observation_already_present_total
content_conflict_total / store_failure_total / source_failure_total
backpressure_wait_total
```
Les compteurs ne wrapent jamais silencieusement.
Les diagnostics et `Debug` ne recopient pas de payload RAW, signature, URL, credential, filtre provider, texte backend/provider arbitraire ou client inférieur. Les agrégations de compteurs de continuité multi-source utilisent une arithmétique vérifiée ; un overflow devient `counter_exhausted` au lieu d'être saturé silencieusement.
## Dépendances
@@ -126,6 +310,7 @@ Les dépendances normales sont exactement :
```text
ksp-core-lib
ksp-logging-lib
ksp-onchain-transport-lib
ksp-raw-transaction-lib
ksp-store-lib (default-features = false)
ksp-worker-api
@@ -133,25 +318,23 @@ sha2
tokio (macros, rt, sync, time)
```
La crate ne dépend pas de Config, Job, `ksp-store-api` directement, backend Store concret, Transport, Tauri ou SDK provider.
La crate ne dépend pas de Config, Job, `ksp-store-api` directement, backend Store concret, `reqwest`, `tonic`, `yellowstone-grpc-proto` ou Tauri.
## Hors périmètre de la fondation source-neutral
## Hors périmètre
Cette surface ne possède pas encore :
La verticale actuelle ne possède pas :
- adapter HTTP/WS/Yellowstone productif ;
- sélection Config de sources/endpoints/credentials ;
- discovery/hydration/replay de continuité live ;
- hot reconfiguration de listeners/sources ;
- sélection Config interne, reconfiguration dynamique ou policy de failover entre sources ;
- checkpoint persistent de processing frontier ;
- campagne de réparation historique automatique ;
- application Desk ou process autonome ;
- campagne historique/backfill ;
- décodage STRUCTURAL/DECODED/DOMAIN.
Ces extensions doivent conserver la séparation avec `ksp-job-backfill-lib` et réutiliser les mêmes contrats RAW/Store.
Les futures extensions doivent conserver la séparation avec `ksp-job-backfill-lib` et réutiliser les mêmes contrats Common RAW/Store.
## Documentation
- [`USAGE.md`](USAGE.md) — utilisation de la façade publique actuelle ;
- [`USAGE.md`](USAGE.md) — utilisation de la façade publique ;
- [`../ksp-worker-api/README.md`](../ksp-worker-api/README.md) — contrats Worker génériques ;
- [`../ksp-raw-transaction-lib/README.md`](../ksp-raw-transaction-lib/README.md) — canonicalisation RAW commune ;
- [`../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md`](../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md) — séparation Worker/Job ;

View File

@@ -1,9 +1,9 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md -->
<!-- version: 1 -->
<!-- version: 12 -->
# Utilisation de ksp-worker-raw-transaction-ingest-lib
Cette page décrit l'utilisation de la façade publique source-neutral de `ksp-worker-raw-transaction-ingest-lib`. Le caller possède la composition du runtime Tokio et du `Store` ; la crate Worker ne construit ni Config, ni backend physique, ni Transport.
Cette page décrit la façade publique de `ksp-worker-raw-transaction-ingest-lib`. Le caller possède la composition du runtime Tokio, du `Store` et des ressources Transport ; le Worker ne lit pas Config, ne construit pas un backend physique et ne lit pas de secret depuis l'environnement.
## Construire les settings
@@ -19,20 +19,14 @@ fn worker_settings() -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_l
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings::with_defaults(
network,
worker_id,
));
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings::with_defaults(network, worker_id));
}
```
Le Worker kind est fixe :
```rust
assert_eq!(
ksp_worker_raw_transaction_ingest_lib::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE,
"raw_transaction_ingest",
);
assert_eq!(ksp_worker_raw_transaction_ingest_lib::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE, "raw_transaction_ingest");
```
Pour des limites explicites :
@@ -57,22 +51,222 @@ shutdown_drain_timeout 100 ms ..= 30 s défaut 5 s
Une valeur hors borne retourne `worker_raw_transaction_ingest.settings_invalid` avec uniquement le nom stable du champ invalide.
## Démarrer sur le runtime du caller
## Choisir le mode de démarrage
`RawTransactionIngestWorker::start` doit être appelé depuis un contexte possédant déjà un runtime Tokio courant. Le `Store` est passé dans un `Arc` et doit cibler exactement le même `RawNetworkId` que les settings.
### Fondation sans source productive
`RawTransactionIngestWorker::start(settings, store)` démarre la fondation runtime sans source Transport. Ce mode reste utile aux tests/compositions qui veulent uniquement le lifecycle, les snapshots et le contrat de shutdown.
```rust
async fn start_worker(
settings: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings,
store: std::sync::Arc<ksp_store_lib::Store>,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle> {
return ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start(settings, store);
let handle = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start(settings, store) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
```
### Source Yellowstone productive
Pour l'ingestion live, le caller compose d'abord les ressources via les crates propriétaires, puis construit :
```rust
fn runtime_resources(
yellowstone_channel: ksp_onchain_transport_lib::YellowstoneGrpcChannel,
subscribe_request: ksp_onchain_transport_lib::YellowstoneSubscribeRequest,
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
hydration_role: ksp_onchain_transport_lib::HttpRoleName,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestYellowstoneSource::new(
yellowstone_channel,
subscribe_request,
http_pool,
hydration_role,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::new(source));
}
```
Le démarrage retourne avant la fin du Worker. Aucun `JoinHandle` Tokio n'est exposé au caller.
Puis :
Un appel hors runtime Tokio courant retourne une erreur `worker_raw_transaction_ingest.runtime_invalid`. Un mismatch réseau Store/settings est également rejeté avant le spawn.
```rust
let handle = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start_with_runtime_resources(
settings,
store,
runtime_resources,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
```
Les deux entrées exigent un runtime Tokio courant et un `Store` portant exactement le même `RawNetworkId` que les settings. Le démarrage avec ressources exige également que chaque source composée cible ce même réseau. L'agrégat accepte 1 à 32 sources logiques et les lance simultanément ; les doublons d'identité et les mélanges de réseaux sont refusés avant spawn. Il n'existe pas de source primaire, standby ou fallback implicite : toute source configurée fait partie du run.
### Composer plusieurs sources simultanées
Une fois un premier `RawTransactionIngestRuntimeResources` construit, le caller ajoute les autres sources avec les méthodes `try_push_*` correspondant à leur capability. Exemple conceptuel :
```rust
let mut resources = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::new(yellowstone_source);
if let std::result::Result::Err(error) = resources.try_push_standard_logs_source(standard_logs_source) {
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) = resources.try_push_http_block_polling_source(http_polling_source) {
return std::result::Result::Err(error);
}
```
La validation est transactionnelle à chaque ajout : limite globale 32, réseau unique et `source_key` logique unique. Au démarrage, toutes les sources présentes sont supervisées ensemble. Une défaillance de source ne permet la continuation des siblings que lorsqu'une plage de perte sûre est disponible, que cette perte est entièrement réconciliée et que les sources encore actives prouvent tout le `TargetCoverage` futur. Sinon le Worker arrête et joint les autres sources. Aucune équivalence n'est déduite du seul provider, protocole ou nom de filtre.
### Source Standard Logs productive
Une source standard Solana WS se compose ainsi :
```rust
fn standard_logs_runtime_resources(
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
filter: ksp_onchain_transport_lib::SolanaLogsSubscribeFilter,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
hydration_role: ksp_onchain_transport_lib::HttpRoleName,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource::new(
ws_endpoint,
filter,
commitment,
http_pool,
hydration_role,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_logs_source(source));
}
```
`ws_endpoint` doit être un endpoint Transport valide de kind `solana_standard`. Le commitment doit être explicitement `Confirmed` ou `Finalized`. Le pool HTTP doit exposer `getTransaction` via le rôle indiqué sur le même réseau. `All`, `AllWithVotes` et `Mentions(pubkey)` sont acceptés par le contrat Transport ; la valeur du filtre reste privée dans le Worker.
### Source Standard Block productive
Une source `blockSubscribe` standard se compose sans pool HTTP :
```rust
fn standard_block_runtime_resources(
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
filter: ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource::new(ws_endpoint, filter, commitment) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source(source));
}
```
Le Worker demande `Base64`, `Full`, `maxSupportedTransactionVersion = 1` et `showRewards = false`. Une transaction n'est RAW-direct que si sa version est explicitement `Legacy`, `0` ou `1`. Une version omise/nulle ou supérieure, `block: null`, une erreur de bloc, un champ transactions absent/nul ou une transaction non Base64 provoque une faute sûre ; ces cas ne sont jamais assimilés à une progression vide.
### Source Helius Transaction productive
Une source Helius LaserStream `transactionSubscribe` se compose avec le même pattern caller-owned :
```rust
fn helius_transaction_runtime_resources(
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
filter: ksp_onchain_transport_lib::HeliusTransactionSubscribeFilter,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
hydration_role: ksp_onchain_transport_lib::HttpRoleName,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource::new(
ws_endpoint,
filter,
commitment,
http_pool,
hydration_role,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_helius_transaction_source(source));
}
```
`ws_endpoint` doit être un endpoint Transport de kind `helius_laserstream`. Le caller supérieur résout éventuellement `KSP_SECRET_HELIUS_API_KEY` via Config avant de construire l'endpoint ; le Worker ne lit jamais l'environnement ni Config. Le commitment est limité à `Confirmed`/`Finalized` et la route HTTP doit supporter `getTransaction` sur le même réseau.
Le Worker demande la forme Helius `Full` avec `Base64`, `showRewards = false` et `maxSupportedTransactionVersion = 1`, mais ne fait pas confiance au nested payload pour construire directement le Common RAW. Il conserve seulement signature/slot/index et hydrate par `getTransaction observed`. Une notification d'une autre forme est fail-closed.
### Source HTTP Block Polling productive
Une source HTTP live peut être composée sans WebSocket ni gRPC :
```rust
fn http_block_polling_runtime_resources(
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
polling_role: ksp_onchain_transport_lib::HttpRoleName,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource::new(
http_pool,
polling_role,
commitment,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_http_block_polling_source(source));
}
```
Le rôle HTTP doit disposer, sur un même réseau, de `getSlot`, `getBlocksWithLimit` et `getBlock`. Le commitment est limité à `Confirmed`/`Finalized`. Par défaut, le Worker interroge toutes les secondes et borne la découverte à 128 blocs par cycle. `new_with_limits` permet de choisir une cadence entre 100 ms et 30 s et une limite entre 1 et 1024 blocs par cycle ; les limites de débit physiques restent celles de Transport.
Au démarrage du run, la première valeur `getSlot(commitment)` devient la borne inférieure stricte du poller. Il ne demande jamais de slot antérieur. `getBlocksWithLimit` détermine les slots réellement matérialisables, puis `getBlock observed` produit directement le Common RAW en Full/Base64 pour Legacy/V0/V1. Un slot listé dont `getBlock` retourne `null` reste la tête de reprise du prochain cycle et n'est jamais transformé en progression silencieuse.
## Préparer la source Yellowstone
La `YellowstoneSubscribeRequest` doit :
- être valide selon Transport ;
- contenir au moins une famille transaction-bearing admise par le Worker ;
- utiliser explicitement `Confirmed` ou `Finalized` ;
- rester compatible avec le réseau du `YellowstoneGrpcChannel`.
Le `HttpTransportPool` doit posséder au moins une route compatible avec le rôle d'hydration et `getTransaction` sur le même réseau. La construction de `RawTransactionIngestYellowstoneSource` vérifie ces invariants sans ouvrir la connexion réseau.
Le caller ne passe pas de signature, `program_id`, plage de slots ou limite historique au Worker. Ces paramètres appartiennent à un Job Backfill, pas au service continu.
## Comprendre la pipeline live
Les familles productives sont traitées ainsi :
```text
Yellowstone Transaction -> signal -> HTTP getTransaction -> Common RAW -> admission
Yellowstone TransactionStatus -> signal -> HTTP getTransaction -> Common RAW -> admission
Yellowstone Block -> un signal par transaction -> HTTP getTransaction -> Common RAW -> admission
Standard WS logsSubscribe -> context.slot + signature -> HTTP getTransaction -> Common RAW -> admission
Standard WS blockSubscribe -> Full/Base64 Legacy|V0|V1 -> Common RAW direct par transaction -> admission
Helius transactionSubscribe -> Full envelope -> signature/slot/index -> HTTP getTransaction -> Common RAW -> admission
HTTP live block polling -> getSlot -> getBlocksWithLimit -> getBlock observed -> Common RAW direct Legacy|V0|V1 -> admission
Yellowstone BlockMeta -> continuity-only
Yellowstone Slot -> continuity-only
Yellowstone Account/Ping/Pong/Entry -> sans RAW Transaction dans cette verticale
```
Les signaux de même `(network, signature, commitment)` sont coalescés globalement avant l'hydration HTTP. Si plusieurs sources produisent ensuite la même transaction canonique, le Worker conserve une seule entité RAW et enregistre séparément les observations déterministes propres à chaque source. Le Worker ne possède pas une boucle de retry HTTP : reroutage/retry/backoff restent dans `ksp-onchain-transport-lib`.
Les sources qui nécessitent `getTransaction` partagent des quotas bornés et déterministes. Pour une composition valide :
```text
nombre de sources avec hydration <= admission_queue_capacity
nombre de sources avec hydration <= persistence_concurrency
somme de leurs pending <= admission_queue_capacity
somme de leurs tâches d'hydration actives <= persistence_concurrency
```
Ces contraintes garantissent au moins une part à chaque source reference-bearing sans introduire de scheduler pondéré. Si les settings ne permettent pas cette répartition, le démarrage échoue avant spawn avec `runtime_invalid`; le caller doit augmenter la capacité concernée ou réduire le nombre de sources nécessitant une hydration.
Pour une même identité `(network, signature)`, une divergence canonique de slot, block time, format ou hash est un `content_conflict`. Le Worker ne choisit ni majorité ni provider préféré et n'écrase pas un contenu divergent ; le Store reste l'autorité durable finale du conflit.
## Observer le snapshot concret
@@ -84,6 +278,14 @@ let sequence = current.worker_snapshot().sequence();
let state = current.worker_snapshot().state();
let queue_depth = current.admission_queue_depth();
let in_flight = current.in_flight_persistence();
let hydration_pending = current.hydration_pending();
let frontier = current.processing_frontier_slot();
let oldest_pending = current.oldest_pending_slot();
let source_total = current.source_total();
let source_active = current.source_active();
let source_reconnecting = current.source_reconnecting();
let source_failed = current.source_failed();
let source_state = current.source_state();
```
Pour attendre une valeur plus récente :
@@ -94,7 +296,7 @@ let newer = source.wait_for_change(observed).await;
assert!(newer.worker_snapshot().sequence().is_after(observed));
```
Le flux est latest-value : les transitions intermédiaires peuvent être coalescées. Ne pas l'utiliser comme journal d'événements exhaustif.
Le flux est latest-value : les transitions intermédiaires peuvent être coalescées. Ne pas l'utiliser comme journal exhaustif.
## Utiliser la projection Worker API
@@ -108,11 +310,9 @@ let newer = ksp_worker_api::WorkerSnapshotSource::wait_for_change(&source, obser
assert!(newer.sequence().is_after(observed));
```
Cette projection commune ne contient que les dimensions génériques Worker. Les compteurs d'admission/persistence restent disponibles sur `RawTransactionIngestSnapshot`.
La projection commune contient seulement les dimensions génériques Worker. Les compteurs et frontiers spécifiques restent sur `RawTransactionIngestSnapshot`.
## Lire les compteurs concrets
Les compteurs principaux sont monotones :
## Lire les compteurs
```rust
let snapshot = handle.snapshot_source().current();
@@ -128,11 +328,53 @@ let conflicts = snapshot.content_conflict_total();
let store_failures = snapshot.store_failure_total();
let source_failures = snapshot.source_failure_total();
let backpressure = snapshot.backpressure_wait_total();
let reconnects = snapshot.source_reconnect_total();
let replay_attempts = snapshot.source_replay_attempt_total();
let proven_gaps = snapshot.source_continuity_gap_total();
let gaps = snapshot.gaps();
let open_gaps = snapshot.open_gap_count();
let repairing_gaps = snapshot.repairing_gap_count();
let repaired_gaps = snapshot.repaired_gap_total();
let unresolved_gaps = snapshot.unresolved_gap_total();
let oldest_gap = snapshot.oldest_open_gap_start_slot();
```
`admission_queue_depth()` et `in_flight_persistence()` sont des gauges latest-value, pas des totaux cumulés.
`admission_queue_depth()`, `in_flight_persistence()`, `hydration_pending()`, `source_total()`, `source_active()`, `source_reconnecting()` et `source_failed()` sont des gauges latest-value. Les compteurs cumulés et les agrégats multi-source ne wrapent ni ne saturent silencieusement ; l'épuisement est terminal avec `worker_raw_transaction_ingest.counter_exhausted`.
L'épuisement d'un compteur ou de la séquence est terminal et utilise `worker_raw_transaction_ingest.counter_exhausted` au lieu de wrapper silencieusement.
## Interpréter la processing frontier
`processing_frontier_slot()` est la plus haute slot de travail source réellement observé qui n'est pas bloquée par un pending plus ancien connu. `oldest_pending_slot()` expose ce plus ancien pending lorsqu'il existe.
Cette frontier est strictement run-local :
```text
elle ne prouve pas que toutes les transactions blockchain d'une slot ont été observées
elle ne prouve pas la persistence durable des ingress déjà envoyés à l'admission
elle n'est pas persistée entre deux runs
elle n'est pas un checkpoint Backfill
```
Un `Missing` HTTP règle le signal du point de vue source-processing sans créer de RAW. Un ingress `Available` n'est réglé qu'après envoi réussi vers l'admission centrale. Une hydration abandonnée au shutdown est retirée des pending sans faire avancer artificiellement la frontier.
## Interpréter reconnect et replay
`source_state()` peut retourner `Active`, `Reconnecting`, `Closing`, `Closed` ou `Failed` comme état agrégé source-neutral. Les gauges `source_total()`, `source_active()`, `source_reconnecting()` et `source_failed()` permettent d'interpréter une composition multi-source sans exposer provider, endpoint, filtre ou `source_key`.
Lorsque la policy de continuité est active, la health commune est plus stricte qu'une simple lecture des états source. Pendant `Running`, reconnect en cours, gap ouvert, continuity frontier différente de la processing frontier ou `TargetCoverage` futur non couvert donnent `Unhealthy`. Toutes les sources attendues `Active` avec continuité réconciliée permettent `Healthy`. Une source `Failed` ne donne `Degraded` que si sa perte est entièrement réconciliée et si les sources restantes couvrent encore tout le `TargetCoverage` futur. Une transition Worker `Faulted` reste `Unhealthy`.
Les compteurs ont des sémantiques distinctes :
```text
source_reconnect_total reconnects automatiques réussis observés
source_replay_attempt_total tentatives de reconnect portant une demande de replay
source_continuity_gap_total gaps de rétention prouvés par Transport
```
Une tentative de replay n'est pas une preuve de continuité. Le Worker ne choisit pas `from_slot` et ne traite pas directement `SubscribeReplayInfo` ; ces mécanismes appartiennent à Transport.
Un gap est projeté via `gaps()` avec un état `Pending`, `Repairing`, `Repaired` ou `Unresolved`, une raison source-neutral et éventuellement la dernière méthode de réparation. Les mécanismes publics décrits par `RawTransactionIngestRepairMethod` sont `Replay`, `RedundantCoverage`, `HttpScan`, `BlockFetch` et `TransactionHydration`.
Une perte source ou un gap de rétention ne déclenche jamais `ksp-job-backfill-lib`. Le Worker continue seulement lorsqu'il peut prouver la réconciliation passée et la coverage future dans les bornes du run courant ; sinon il fault.
## Demander un stop et attendre le terminal
@@ -152,55 +394,37 @@ if accepted {
}
```
`request_stop()` est idempotent. Il exprime une intention coopérative ; le terminal n'est publié qu'après traitement du drain et des tâches possédées.
`request_stop()` est idempotent. Le terminal n'est publié qu'après le drain borné et la récupération des tâches possédées. Si la deadline de drain expire, toutes les tâches source/persistence encore possédées sont abortées puis jointes avant publication terminale ; une persistence libérée après ce terminal ne peut donc pas produire une complétion tardive. Une faute source déjà observée reste prioritaire face à un stop concurrent, sauf si le drain lui-même expire et devient le terminal `drain_timeout`.
Le shutdown est borné par `shutdown_drain_timeout`. Si les tâches ne peuvent pas être drainées à temps, le Worker les abort puis les rejoint avant de publier `worker_raw_transaction_ingest.drain_timeout`.
## Interpréter les terminaux faultés
Les codes Worker publics sont :
## Interpréter les faults
```text
settings_invalid settings techniques hors contrat
runtime_invalid invariant runtime/lifecycle impossible
store_failed erreur Store non-conflict classifiée
content_conflict transaction canonique incompatible avec le contenu durable
content_conflict contenu canonique incompatible avec l'identité durable
counter_exhausted compteur/séquence monotone arrivé à sa borne
source_failed tâche source privée terminée en erreur
drain_timeout drain du shutdown hors deadline
source_failed source/replay/hydration terminée par une erreur classifiée
drain_timeout drain de shutdown hors deadline
```
Un consumer doit traiter le `ErrorCode` comme contrat stable et ne pas dépendre d'un texte backend/provider arbitraire.
## Frontière d'admission
La queue d'admission et le type ingress sont volontairement privés. La façade publique actuelle ne propose ni `send`, ni `enqueue`, ni registration d'un adapter réseau.
Les adapters d'acquisition qui alimentent cette queue appartiennent à l'implémentation du Worker et doivent conserver :
```text
backpressure mpsc bornée
stop prioritaire sur send bloqué
canonicalisation via ksp-raw-transaction-lib
persistance via ksp-store-lib
aucun backend physique direct
aucun provider dans l'identité canonique
```
Le caller ne doit pas contourner cette frontière en écrivant directement dans les structures privées du Worker.
Un consumer doit traiter l'`ErrorCode` comme contrat stable et ne pas dépendre d'un texte backend/provider arbitraire.
## Composition supérieure
Le pattern de composition attendu reste :
Le pattern attendu est :
```text
Config / application / service owner
-> résout endpoints, credentials et rôles
-> construit une source Transport Yellowstone, Standard Logs, Standard Block, Helius Transaction ou HTTP Block Polling
-> construit HttpTransportPool + rôle HTTP adapté à la source
-> construit le Store
-> choisit ultérieurement les sources/adapters supportés
-> construit RawTransactionIngestRuntimeResources
-> construit RawTransactionIngestSettings
-> démarre RawTransactionIngestWorker
-> démarre RawTransactionIngestWorker::start_with_runtime_resources
-> observe RawTransactionIngestSnapshotSource
-> demande stop lorsque nécessaire
```
Le Worker ne reçoit pas de requête historique métier. Une campagne `signature/program_id/plage/limite` appartient à `ksp-job-backfill-lib`, pas à ce runtime continu.
Le Worker ne reçoit pas de requête historique métier et ne dépend pas de Config. Une campagne `signature/program_id/plage/limite` appartient à `ksp-job-backfill-lib`.

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,33 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
// version: 8
// version: 33
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Source-neutral runtime foundation for continuous KSP RAW transaction ingestion.
//! Owned source, hydration and persistence tasks are bounded and joined before terminal publication.
//!
//! This tranche owns the concrete Worker family identity, validated technical settings
//! and the caller-runtime-owned lifecycle with private child-task supervision. This tranche also
//! owns bounded source-neutral admission, common RAW canonicalization/assembly and backend-neutral
//! Store persistence in normal mode plus concrete latest-value snapshots projected onto Worker API;
//! no live source or Transport dependency exists.
//! Store persistence in normal mode plus concrete latest-value snapshots projected onto Worker API. The bounded 1..32 caller-composed aggregate starts
//! all validated Yellowstone, Standard Logs, Standard Block, Helius Transaction and HTTP live block polling sources under one private supervisor. Reference-bearing
//! sources share bounded cross-source hydration and fairness budgets; direct-qualified Standard Block and HTTP polling transactions enter the existing central
//! admission path directly. Public snapshots expose only source-neutral aggregate counts/state and conservative Worker health. Yellowstone
//! Transaction/TransactionStatus/Block and standard logs notifications become signature/slot references; BlockMeta/Slot remain continuity-only signals.
//! Hydration is coalesced by network/signature/commitment under bounded in-flight and pending budgets. A bounded run-local processing frontier projects
//! hydration pending, oldest pending slot and highest unblocked actually observed slot. The productive source also projects safe Transport reconnect/replay
//! state. Run-local continuity contracts reconcile bounded source-loss and known-reference gaps only from explicit coverage epochs; a lost source may remain
//! absent only while currently active siblings preserve the configured TargetCoverage. Transport source respawn remains forbidden.
mod admission;
mod continuity;
mod error;
mod identity;
mod persistence;
mod runtime;
mod runtime_resources;
mod settings;
mod snapshot;
@@ -43,6 +53,32 @@ pub use self::runtime::RawTransactionIngestHandle;
pub use self::runtime::RawTransactionIngestTerminalFuture;
/// Entry point owning synchronous validation and task launch for one RAW transaction ingest Worker run.
pub use self::runtime::RawTransactionIngestWorker;
/// Default interval between HTTP live block polling cycles.
pub use self::runtime_resources::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL;
/// Default maximum number of confirmed blocks discovered during one HTTP live block polling cycle.
pub use self::runtime_resources::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE;
/// Maximum interval accepted between HTTP live block polling cycles.
pub use self::runtime_resources::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL;
/// Maximum number of confirmed blocks accepted during one HTTP live block polling cycle.
pub use self::runtime_resources::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE;
/// Maximum number of logical live sources accepted by one runtime-resource aggregate.
pub use self::runtime_resources::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES;
/// Minimum interval accepted between HTTP live block polling cycles.
pub use self::runtime_resources::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL;
/// Minimum number of confirmed blocks accepted during one HTTP live block polling cycle.
pub use self::runtime_resources::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE;
/// Validated Helius `transactionSubscribe` + HTTP hydration source contract owned by the continuous RAW transaction ingest Worker.
pub use self::runtime_resources::RawTransactionIngestHeliusTransactionSource;
/// Validated standard Solana HTTP live block polling source contract owned by the continuous RAW transaction ingest Worker.
pub use self::runtime_resources::RawTransactionIngestHttpBlockPollingSource;
/// Caller-composed bounded runtime resources for supported continuous RAW transaction live-source families.
pub use self::runtime_resources::RawTransactionIngestRuntimeResources;
/// Validated standard Solana `blockSubscribe` direct RAW source contract owned by the continuous RAW transaction ingest Worker.
pub use self::runtime_resources::RawTransactionIngestStandardBlockSource;
/// Validated standard Solana `logsSubscribe` + HTTP hydration source contract owned by the continuous RAW transaction ingest Worker.
pub use self::runtime_resources::RawTransactionIngestStandardLogsSource;
/// Validated Yellowstone + HTTP source contract owned by the continuous RAW transaction ingest Worker.
pub use self::runtime_resources::RawTransactionIngestYellowstoneSource;
/// Default bounded admission queue capacity for one RAW transaction ingest Worker.
pub use self::settings::DEFAULT_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY;
/// Default number of concurrent Store persistence operations for one RAW transaction ingest Worker.
@@ -63,17 +99,43 @@ pub use self::settings::MIN_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY;
pub use self::settings::MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT;
/// Validated source-neutral runtime settings for one continuous RAW transaction ingest Worker.
pub use self::settings::RawTransactionIngestSettings;
/// Stable source-neutral identifier of one run-local continuity gap.
pub use self::snapshot::RawTransactionIngestGapId;
/// Source-neutral reason why one run-local continuity gap was opened.
pub use self::snapshot::RawTransactionIngestGapReason;
/// Safe source-neutral snapshot of one recent or still-open run-local continuity gap.
pub use self::snapshot::RawTransactionIngestGapSnapshot;
/// Source-neutral lifecycle state of one run-local continuity gap.
pub use self::snapshot::RawTransactionIngestGapState;
/// Source-neutral mechanism last used while attempting to close one continuity gap.
pub use self::snapshot::RawTransactionIngestRepairMethod;
/// Complete safe latest-value snapshot of one continuous RAW transaction ingest Worker.
pub use self::snapshot::RawTransactionIngestSnapshot;
/// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot.
pub use self::snapshot::RawTransactionIngestSnapshotFuture;
/// Cloneable latest-value source exposing concrete and common Worker snapshots from one shared watch state.
pub use self::snapshot::RawTransactionIngestSnapshotSource;
/// Source-neutral lifecycle state of the productive RAW transaction ingest source.
pub use self::snapshot::RawTransactionIngestSourceState;
/// Receiver-side owner of the private bounded RAW transaction admission queue.
pub(crate) use self::admission::RawTransactionAdmission;
/// Crate-private source-neutral ingress sent through the bounded central admission queue.
pub(crate) use self::admission::RawTransactionIngress;
/// Maximum number of slots admitted by one private continuity HTTP discovery window.
pub(crate) use self::continuity::MAX_RAW_TRANSACTION_INGEST_CONTINUITY_DISCOVERY_WINDOW_SLOTS;
/// Private source continuity-capability descriptor prepared without network or Store I/O.
pub(crate) use self::continuity::RawTransactionIngestContinuityCapabilityDescriptor;
/// Private run-local continuity aggregate containing capabilities, TargetCoverage and the gap ledger.
pub(crate) use self::continuity::RawTransactionIngestContinuityContracts;
/// Private provider-neutral coverage scope used by run-local continuity proof contracts.
pub(crate) use self::continuity::RawTransactionIngestCoverageScope;
/// Private run-local obligation for one transaction reference already observed by a live source.
pub(crate) use self::continuity::RawTransactionIngestKnownReferenceObligation;
/// Private supervisor decision after one live source becomes terminal.
pub(crate) use self::continuity::RawTransactionIngestSourceLossDecision;
/// Private run-local WebSocket incident anchor built only from observed source slots and safe Transport counters.
pub(crate) use self::continuity::RawTransactionIngestWebSocketIncidentAnchor;
/// Creates one terminal content-conflict error without copying conflicting material into diagnostics.
pub(crate) use self::error::content_conflict_error;
/// Creates one terminal counter-exhaustion error without exposing runtime material.
@@ -88,11 +150,19 @@ pub(crate) use self::error::store_error;
pub(crate) use self::persistence::RawTransactionIngestEntityPersistence;
/// Observation disposition produced by one successful Worker Store persistence attempt.
pub(crate) use self::persistence::RawTransactionIngestObservationPersistence;
/// Private bounded run-local cache serializing repeated canonical identities before Store writes.
pub(crate) use self::persistence::RawTransactionIngestPersistenceConvergence;
/// Classified successful outcome of one atomic Worker Store persistence attempt.
pub(crate) use self::persistence::RawTransactionIngestPersistenceOutcome;
/// Private backend-neutral Store persistence port used by the Worker and deterministic tests.
pub(crate) use self::persistence::RawTransactionIngestPersistencePort;
/// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode.
pub(crate) use self::persistence::persist_raw_transaction_ingest_acquisition;
/// Persists one canonical acquisition through the bounded cross-source convergence cache.
pub(crate) use self::persistence::persist_raw_transaction_ingest_converged_acquisition;
/// Private bounded continuity-observability projection carried into the concrete Worker snapshot.
pub(crate) use self::snapshot::RawTransactionIngestContinuitySnapshotProjection;
/// Private latest-value processing-frontier projection emitted by the productive source task.
pub(crate) use self::snapshot::RawTransactionIngestProcessingFrontierProjection;
/// Private latest-value publisher and checked counter owner shared by the Worker supervisor.
pub(crate) use self::snapshot::RawTransactionIngestSnapshotPublisher;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
// version: 1
// version: 4
/// Canonical entity disposition produced by one Worker Store persistence attempt.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -56,6 +56,12 @@ pub(crate) trait RawTransactionIngestPersistencePort: std::marker::Send + std::m
observation: ksp_store_lib::RawTransactionObservation,
mode: ksp_store_lib::RawTransactionAcquisitionMode,
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>>;
/// Records one additional observation for an already durable canonical RAW transaction.
fn record_observation<'a>(
&'a self,
observation: ksp_store_lib::RawTransactionObservation,
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>>;
}
impl crate::RawTransactionIngestPersistencePort for ksp_store_lib::Store {
@@ -71,6 +77,77 @@ impl crate::RawTransactionIngestPersistencePort for ksp_store_lib::Store {
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>> {
return ksp_store_lib::RawTransactionWrite::persist_raw_transaction_acquisition(self, transaction, observation, mode);
}
fn record_observation<'a>(
&'a self,
observation: ksp_store_lib::RawTransactionObservation,
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>> {
return ksp_store_lib::RawTransactionObservationWrite::record_raw_transaction_observation(self, observation);
}
}
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
struct RawTransactionIngestPersistenceKey {
network: ksp_store_lib::RawNetworkId,
signature: ksp_store_lib::RawTransactionSignature,
}
#[derive(Clone, Eq, PartialEq)]
struct RawTransactionIngestCanonicalState {
block_time: std::option::Option<ksp_store_lib::RawTimestamp>,
content_hash: ksp_store_lib::RawContentHash,
format_id: ksp_store_lib::RawFormatId,
format_version: u32,
slot: u64,
}
/// Private bounded run-local cache serializing repeated canonical identities before Store writes.
pub(crate) struct RawTransactionIngestPersistenceConvergence {
max_entries: usize,
entries: std::sync::Mutex<
std::collections::BTreeMap<
RawTransactionIngestPersistenceKey,
std::sync::Arc<tokio::sync::Mutex<std::option::Option<RawTransactionIngestCanonicalState>>>,
>,
>,
}
impl crate::RawTransactionIngestPersistenceConvergence {
/// Creates one bounded run-local convergence cache using the caller-provided effective runtime bound.
#[must_use]
pub(crate) fn new(max_entries: usize) -> Self {
return Self { max_entries, entries: std::sync::Mutex::new(std::collections::BTreeMap::new()) };
}
fn entry(
&self,
key: RawTransactionIngestPersistenceKey,
) -> std::option::Option<std::sync::Arc<tokio::sync::Mutex<std::option::Option<RawTransactionIngestCanonicalState>>>> {
let mut entries = match self.entries.lock() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(poisoned) => poisoned.into_inner(),
};
if let std::option::Option::Some(entry) = entries.get(&key) {
return std::option::Option::Some(std::sync::Arc::clone(entry));
}
if entries.len() >= self.max_entries {
let removable = entries.iter().find_map(|(candidate, entry)| {
if std::sync::Arc::strong_count(entry) == 1 {
return std::option::Option::Some(candidate.clone());
}
return std::option::Option::None;
});
if let std::option::Option::Some(removable) = removable {
entries.remove(&removable);
}
}
if entries.len() >= self.max_entries {
return std::option::Option::None;
}
let entry = std::sync::Arc::new(tokio::sync::Mutex::new(std::option::Option::None));
entries.insert(key, std::sync::Arc::clone(&entry));
return std::option::Option::Some(entry);
}
}
/// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode.
@@ -96,6 +173,62 @@ where
};
}
/// Persists one canonical acquisition through the bounded cross-source convergence cache.
pub(crate) async fn persist_raw_transaction_ingest_converged_acquisition<P>(
port: &P,
acquisition: ksp_raw_transaction_lib::RawTransactionAcquisition,
convergence: &crate::RawTransactionIngestPersistenceConvergence,
) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome>
where
P: crate::RawTransactionIngestPersistencePort + ?Sized,
{
let reference = acquisition.transaction().reference().clone();
if !port.network_matches(reference.network()) {
return std::result::Result::Err(crate::runtime_error("persistence.store_network_mismatch"));
}
if acquisition.observation().transaction() != &reference {
return std::result::Result::Err(crate::runtime_error("persistence.acquisition_reference_mismatch"));
}
let canonical_state = canonical_state(acquisition.transaction());
let key = RawTransactionIngestPersistenceKey { network: reference.network().clone(), signature: reference.signature() };
let entry = convergence.entry(key);
let entry = match entry {
std::option::Option::Some(value) => value,
std::option::Option::None => return crate::persist_raw_transaction_ingest_acquisition(port, acquisition).await,
};
let mut known_state = entry.lock().await;
if let std::option::Option::Some(known_state_value) = known_state.as_ref() {
if known_state_value != &canonical_state {
return std::result::Result::Err(crate::content_conflict_error());
}
let (_transaction, observation) = acquisition.into_parts();
let result = port.record_observation(observation).await;
return match result {
std::result::Result::Ok(outcome) => map_additional_observation_outcome(outcome),
std::result::Result::Err(error) => map_store_error(error),
};
}
let outcome = crate::persist_raw_transaction_ingest_acquisition(port, acquisition).await;
let outcome = match outcome {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if outcome.entity() != crate::RawTransactionIngestEntityPersistence::SkippedPurged {
*known_state = std::option::Option::Some(canonical_state);
}
return std::result::Result::Ok(outcome);
}
fn canonical_state(transaction: &ksp_store_lib::RawTransaction) -> RawTransactionIngestCanonicalState {
return RawTransactionIngestCanonicalState {
block_time: transaction.block_time(),
content_hash: transaction.payload().content_hash(),
format_id: transaction.payload().format_id().clone(),
format_version: transaction.payload().format_version(),
slot: transaction.slot(),
};
}
fn map_store_error(error: ksp_core_lib::Error) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
if error.code() == ksp_store_lib::ERROR_CODE_RAW_CONFLICT {
return std::result::Result::Err(crate::content_conflict_error());
@@ -133,6 +266,25 @@ fn map_store_outcome(outcome: ksp_store_lib::RawAcquisitionWriteOutcome) -> ksp_
return std::result::Result::Err(crate::runtime_error("persistence.store_outcome_invalid"));
}
fn map_additional_observation_outcome(
outcome: ksp_store_lib::RawObservationWriteOutcome,
) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
return match outcome {
ksp_store_lib::RawObservationWriteOutcome::Inserted => std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
observation: crate::RawTransactionIngestObservationPersistence::Inserted,
}),
ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent => std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
observation: crate::RawTransactionIngestObservationPersistence::AlreadyPresent,
}),
ksp_store_lib::RawObservationWriteOutcome::NotRecorded => {
std::result::Result::Err(crate::runtime_error("persistence.additional_observation_not_recorded"))
},
_ => std::result::Result::Err(crate::runtime_error("persistence.additional_observation_outcome_invalid")),
};
}
#[cfg(test)]
#[path = "../unit_tests/persistence.rs"]
mod tests;

View File

@@ -1,8 +1,9 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
// version: 7
// version: 18
type PersistencePort = std::sync::Arc<dyn crate::RawTransactionIngestPersistencePort + 'static>;
type PersistenceTasks = tokio::task::JoinSet<ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome>>;
type ProcessingFrontierReceiver = tokio::sync::watch::Receiver<crate::RawTransactionIngestProcessingFrontierProjection>;
type SourceTasks = tokio::task::JoinSet<ksp_core_lib::Result<()>>;
/// Runtime-neutral boxed future resolving after one RAW transaction ingest Worker has fully reached a terminal lifecycle state.
@@ -95,6 +96,45 @@ impl crate::RawTransactionIngestWorker {
}
return start_foundation(settings, runtime, std::option::Option::Some(store));
}
/// Starts one Worker with caller-composed runtime resources.
///
/// Every validated source in the bounded runtime-resource aggregate is started concurrently under one private source supervisor. Any configured source
/// failure remains terminal for the Worker because this release does not infer equivalent coverage or failover between source families.
pub fn start_with_runtime_resources(
settings: crate::RawTransactionIngestSettings,
store: std::sync::Arc<ksp_store_lib::Store>,
runtime_resources: crate::RawTransactionIngestRuntimeResources,
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle> {
let runtime = match current_runtime_handle() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store_snapshot = store.runtime_snapshot();
if let std::result::Result::Err(error) = validate_store_network(&settings, store_snapshot.network()) {
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) = runtime_resources.validate_network(settings.network()) {
return std::result::Result::Err(error);
}
let source_total = runtime_resources.source_count();
let source_settings = settings.clone();
let (processing_frontier_sender, processing_frontier_receiver) =
tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty());
let port: PersistencePort = store;
return start_foundation_with_port_source_spawner_and_frontier(
settings,
runtime,
std::option::Option::Some(port),
std::option::Option::Some(processing_frontier_receiver),
source_total,
move |children, stop_receiver, admission_sender| {
let _abort_handle = children.spawn(async move {
return runtime_resources.run_live_sources(source_settings, stop_receiver, admission_sender, processing_frontier_sender).await;
});
},
);
}
}
fn begin_stopping(
@@ -151,6 +191,7 @@ async fn drain_admission_and_persistence(
lifecycle: &ksp_worker_api::WorkerLifecycle,
admission: &mut crate::RawTransactionAdmission,
persistence: &mut PersistenceTasks,
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
port: &std::option::Option<PersistencePort>,
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
) -> std::option::Option<ksp_core_lib::ErrorCode> {
@@ -169,7 +210,7 @@ async fn drain_admission_and_persistence(
let backpressure_wait_observed = admission.take_backpressure_wait_observed();
match received {
std::result::Result::Ok(std::option::Option::Some(acquisition)) => {
let spawned = spawn_persistence(persistence, port, acquisition);
let spawned = spawn_persistence(persistence, persistence_convergence, port, acquisition);
let published = snapshots.record_admission_success(lifecycle.state(), admission.queue_depth(), persistence.len(), backpressure_wait_observed);
if !spawned && fault.is_none() {
fault = std::option::Option::Some(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
@@ -209,11 +250,12 @@ async fn drain_owned_work(
children: &mut SourceTasks,
admission: &mut crate::RawTransactionAdmission,
persistence: &mut PersistenceTasks,
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
port: &std::option::Option<PersistencePort>,
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
) -> std::option::Option<ksp_core_lib::ErrorCode> {
let drain = async {
let mut fault = drain_admission_and_persistence(settings, lifecycle, admission, persistence, port, snapshots).await;
let mut fault = drain_admission_and_persistence(settings, lifecycle, admission, persistence, persistence_convergence, port, snapshots).await;
let child_fault = drain_children(lifecycle.state(), children, snapshots).await;
fault = merge_fault(fault, child_fault);
return fault;
@@ -301,13 +343,16 @@ fn source_completion(
in_flight_persistence: usize,
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
) -> std::option::Option<ksp_core_lib::ErrorCode> {
match joined {
let source_code = match joined {
std::result::Result::Ok(std::result::Result::Ok(())) => return std::option::Option::None,
std::result::Result::Ok(std::result::Result::Err(_)) | std::result::Result::Err(_) => {},
}
std::result::Result::Ok(std::result::Result::Err(error)) if error.code() == crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED => {
crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED
},
std::result::Result::Ok(std::result::Result::Err(_)) | std::result::Result::Err(_) => crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED,
};
let published = snapshots.record_source_failure(state, admission_queue_depth, in_flight_persistence);
return match published {
std::result::Result::Ok(()) => std::option::Option::Some(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED),
std::result::Result::Ok(()) => std::option::Option::Some(source_code),
std::result::Result::Err(error) => std::option::Option::Some(error.code()),
};
}
@@ -318,6 +363,7 @@ async fn run_supervisor<Spawner>(
port: std::option::Option<PersistencePort>,
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
mut snapshots: crate::RawTransactionIngestSnapshotPublisher,
mut processing_frontier_receiver: std::option::Option<ProcessingFrontierReceiver>,
source_spawner: Spawner,
) where
Spawner:
@@ -341,6 +387,9 @@ async fn run_supervisor<Spawner>(
}
let mut children = SourceTasks::new();
let mut persistence = PersistenceTasks::new();
let persistence_convergence = std::sync::Arc::new(crate::RawTransactionIngestPersistenceConvergence::new(
settings.admission_queue_capacity().max(settings.persistence_concurrency()),
));
let (source_stop_sender, source_stop_receiver) = tokio::sync::watch::channel(false);
let (mut admission, admission_sender) = crate::RawTransactionAdmission::new(settings.admission_queue_capacity());
source_spawner(&mut children, source_stop_receiver, admission_sender);
@@ -352,14 +401,17 @@ async fn run_supervisor<Spawner>(
&mut children,
&mut admission,
&mut persistence,
&persistence_convergence,
&port,
&mut snapshots,
&mut processing_frontier_receiver,
)
.await;
source_stop_sender.send_replace(true);
let stopping_fault = begin_stopping(&mut lifecycle, &mut snapshots, admission.queue_depth(), persistence.len());
fault = merge_fault(fault, stopping_fault);
let drain_fault = drain_owned_work(&settings, &lifecycle, &mut children, &mut admission, &mut persistence, &port, &mut snapshots).await;
let drain_fault =
drain_owned_work(&settings, &lifecycle, &mut children, &mut admission, &mut persistence, &persistence_convergence, &port, &mut snapshots).await;
match drain_fault {
std::option::Option::Some(code) if code == crate::ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT => {
fault = std::option::Option::Some(code);
@@ -378,6 +430,7 @@ async fn run_supervisor<Spawner>(
fn spawn_persistence(
persistence: &mut PersistenceTasks,
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
port: &std::option::Option<PersistencePort>,
acquisition: ksp_raw_transaction_lib::RawTransactionAcquisition,
) -> bool {
@@ -385,8 +438,9 @@ fn spawn_persistence(
std::option::Option::Some(value) => std::sync::Arc::clone(value),
std::option::Option::None => return false,
};
let persistence_convergence = std::sync::Arc::clone(persistence_convergence);
let _abort_handle = persistence.spawn(async move {
return crate::persist_raw_transaction_ingest_acquisition(port.as_ref(), acquisition).await;
return crate::persist_raw_transaction_ingest_converged_acquisition(port.as_ref(), acquisition, persistence_convergence.as_ref()).await;
});
return true;
}
@@ -405,6 +459,21 @@ fn start_foundation_with_port_and_source_spawner<Spawner>(
port: std::option::Option<PersistencePort>,
source_spawner: Spawner,
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle>
where
Spawner:
FnOnce(&mut SourceTasks, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>) + std::marker::Send + 'static,
{
return start_foundation_with_port_source_spawner_and_frontier(settings, runtime, port, std::option::Option::None, 0, source_spawner);
}
fn start_foundation_with_port_source_spawner_and_frontier<Spawner>(
settings: crate::RawTransactionIngestSettings,
runtime: tokio::runtime::Handle,
port: std::option::Option<PersistencePort>,
processing_frontier_receiver: std::option::Option<ProcessingFrontierReceiver>,
source_total: usize,
source_spawner: Spawner,
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle>
where
Spawner:
FnOnce(&mut SourceTasks, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>) + std::marker::Send + 'static,
@@ -419,9 +488,9 @@ where
}
let stop_token = ksp_worker_api::WorkerStopToken::new();
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
let (snapshots, snapshot_source) = crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle);
let (snapshots, snapshot_source) = crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle, source_total);
let handle = crate::RawTransactionIngestHandle { snapshots: snapshot_source, stop_sender, stop_token };
std::mem::drop(runtime.spawn(run_supervisor(settings, lifecycle, port, stop_receiver, snapshots, source_spawner)));
std::mem::drop(runtime.spawn(run_supervisor(settings, lifecycle, port, stop_receiver, snapshots, processing_frontier_receiver, source_spawner)));
return std::result::Result::Ok(handle);
}
@@ -453,8 +522,10 @@ async fn supervise_until_stop(
children: &mut SourceTasks,
admission: &mut crate::RawTransactionAdmission,
persistence: &mut PersistenceTasks,
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
port: &std::option::Option<PersistencePort>,
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
processing_frontier_receiver: &mut std::option::Option<ProcessingFrontierReceiver>,
) -> std::option::Option<ksp_core_lib::ErrorCode> {
let mut admission_open = true;
loop {
@@ -478,6 +549,25 @@ async fn supervise_until_stop(
break;
}
}
processing_frontier = wait_processing_frontier(processing_frontier_receiver) => {
match processing_frontier {
std::option::Option::Some(projection) => {
let published = snapshots.record_processing_frontier(
lifecycle.state(),
admission.queue_depth(),
persistence.len(),
projection,
);
if let std::result::Result::Err(error) = published {
source_stop_sender.send_replace(true);
return std::option::Option::Some(error.code());
}
},
std::option::Option::None => {
*processing_frontier_receiver = std::option::Option::None;
},
}
}
joined = children.join_next(), if !children.is_empty() => {
let source_fault = match joined {
std::option::Option::Some(value) => source_completion(value, lifecycle.state(), admission.queue_depth(), persistence.len(), snapshots),
@@ -504,7 +594,7 @@ async fn supervise_until_stop(
match received {
std::result::Result::Ok(std::option::Option::Some(acquisition)) => {
let backpressure_wait_observed = admission.take_backpressure_wait_observed();
let spawned = spawn_persistence(persistence, port, acquisition);
let spawned = spawn_persistence(persistence, persistence_convergence, port, acquisition);
let published = snapshots.record_admission_success(
lifecycle.state(),
admission.queue_depth(),
@@ -545,6 +635,21 @@ async fn supervise_until_stop(
return std::option::Option::None;
}
async fn wait_processing_frontier(
receiver: &mut std::option::Option<ProcessingFrontierReceiver>,
) -> std::option::Option<crate::RawTransactionIngestProcessingFrontierProjection> {
let receiver = match receiver.as_mut() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::future::pending::<std::option::Option<crate::RawTransactionIngestProcessingFrontierProjection>>().await;
},
};
if receiver.changed().await.is_err() {
return std::option::Option::None;
}
return std::option::Option::Some(receiver.borrow_and_update().clone());
}
fn validate_store_network(settings: &crate::RawTransactionIngestSettings, store_network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> {
if settings.network() != store_network {
return std::result::Result::Err(crate::runtime_error("start.store_network_mismatch"));

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,502 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
// version: 2
// version: 9
/// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot.
pub type RawTransactionIngestSnapshotFuture<'a> =
std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = crate::RawTransactionIngestSnapshot> + std::marker::Send + 'a>>;
/// Source-neutral lifecycle state of the productive RAW transaction ingest source.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawTransactionIngestSourceState {
/// The productive source is active.
Active,
/// Transport is performing one bounded reconnect/replay attempt.
Reconnecting,
/// Cooperative source shutdown has started.
Closing,
/// The productive source closed cleanly.
Closed,
/// The productive source reached a terminal failure.
Failed,
}
/// Stable source-neutral identifier of one run-local continuity gap.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RawTransactionIngestGapId(u64);
impl crate::RawTransactionIngestGapId {
/// Creates one internal gap identifier after the continuity ledger validated monotonicity.
pub(crate) const fn new(value: u64) -> Self {
return Self(value);
}
/// Returns the run-local numeric identifier.
#[must_use]
pub const fn value(self) -> u64 {
return self.0;
}
}
/// Source-neutral lifecycle state of one run-local continuity gap.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawTransactionIngestGapState {
/// The gap is known and waiting for an admissible recovery proof.
Pending,
/// One bounded recovery mechanism is actively processing the gap.
Repairing,
/// The complete required interval has been proven covered.
Repaired,
/// The gap remains unresolved after the admissible bounded mechanisms are exhausted.
Unresolved,
}
/// Source-neutral reason why one run-local continuity gap was opened.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawTransactionIngestGapReason {
/// A slot was proven produced but its block material remained unavailable.
HttpProducedBlockUnavailable,
/// A previously observed transaction reference could not yet be materialized.
KnownReferenceMissing,
/// A logical live source became terminal before continuity was proven.
SourceFailure,
/// Transport reported bounded notification loss or overflow.
TransportOverflow,
/// A WebSocket reconnect opened a bounded continuity incident.
WebSocketReconnect,
/// Yellowstone replay retention could not cover the requested boundary.
YellowstoneRetention,
}
/// Source-neutral mechanism last used while attempting to close one continuity gap.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawTransactionIngestRepairMethod {
/// Transport-owned replay supplied material later proven sufficient.
Replay,
/// A distinct live source supplied explicit interval coverage.
RedundantCoverage,
/// A bounded HTTP slot/block discovery scan supplied coverage evidence.
HttpScan,
/// A produced slot required direct block material retrieval.
BlockFetch,
/// A known transaction reference required observed transaction hydration.
TransactionHydration,
}
/// Safe source-neutral snapshot of one recent or still-open run-local continuity gap.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RawTransactionIngestGapSnapshot {
gap_id: crate::RawTransactionIngestGapId,
start_slot: u64,
end_slot: u64,
state: crate::RawTransactionIngestGapState,
reason: crate::RawTransactionIngestGapReason,
last_method: std::option::Option<crate::RawTransactionIngestRepairMethod>,
}
impl crate::RawTransactionIngestGapSnapshot {
/// Creates one safe gap projection from already validated continuity-ledger state.
pub(crate) const fn new(
gap_id: crate::RawTransactionIngestGapId,
start_slot: u64,
end_slot: u64,
state: crate::RawTransactionIngestGapState,
reason: crate::RawTransactionIngestGapReason,
last_method: std::option::Option<crate::RawTransactionIngestRepairMethod>,
) -> Self {
return Self { gap_id, start_slot, end_slot, state, reason, last_method };
}
/// Returns the run-local gap identifier.
#[must_use]
pub const fn gap_id(&self) -> crate::RawTransactionIngestGapId {
return self.gap_id;
}
/// Returns the inclusive first slot of the gap.
#[must_use]
pub const fn start_slot(&self) -> u64 {
return self.start_slot;
}
/// Returns the inclusive last slot of the gap.
#[must_use]
pub const fn end_slot(&self) -> u64 {
return self.end_slot;
}
/// Returns the source-neutral gap lifecycle state.
#[must_use]
pub const fn state(&self) -> crate::RawTransactionIngestGapState {
return self.state;
}
/// Returns the source-neutral reason that opened the gap.
#[must_use]
pub const fn reason(&self) -> crate::RawTransactionIngestGapReason {
return self.reason;
}
/// Returns the last bounded recovery mechanism recorded for this gap, when any.
#[must_use]
pub const fn last_method(&self) -> std::option::Option<crate::RawTransactionIngestRepairMethod> {
return self.last_method;
}
}
/// Private bounded continuity-observability projection carried into the public latest-value Worker snapshot.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct RawTransactionIngestContinuitySnapshotProjection {
gaps: std::vec::Vec<crate::RawTransactionIngestGapSnapshot>,
open_gap_count: usize,
repairing_gap_count: usize,
repaired_gap_total: u64,
unresolved_gap_total: u64,
replay_repair_total: u64,
redundant_coverage_repair_total: u64,
http_scan_repair_total: u64,
repair_block_fetch_total: u64,
repair_transaction_hydration_total: u64,
oldest_open_gap_start_slot: std::option::Option<u64>,
}
impl crate::RawTransactionIngestContinuitySnapshotProjection {
/// Returns the empty continuity-observability projection before any gap exists.
pub(crate) fn empty() -> Self {
return Self {
gaps: std::vec::Vec::new(),
open_gap_count: 0,
repairing_gap_count: 0,
repaired_gap_total: 0,
unresolved_gap_total: 0,
replay_repair_total: 0,
redundant_coverage_repair_total: 0,
http_scan_repair_total: 0,
repair_block_fetch_total: 0,
repair_transaction_hydration_total: 0,
oldest_open_gap_start_slot: std::option::Option::None,
};
}
/// Returns a copy carrying bounded gap details and current open-gap aggregates.
pub(crate) fn with_gap_state(
mut self,
gaps: std::vec::Vec<crate::RawTransactionIngestGapSnapshot>,
open_gap_count: usize,
repairing_gap_count: usize,
oldest_open_gap_start_slot: std::option::Option<u64>,
) -> Self {
self.gaps = gaps;
self.open_gap_count = open_gap_count;
self.repairing_gap_count = repairing_gap_count;
self.oldest_open_gap_start_slot = oldest_open_gap_start_slot;
return self;
}
/// Returns a copy carrying cumulative source-neutral gap recovery totals.
pub(crate) fn with_recovery_totals(
mut self,
repaired_gap_total: u64,
unresolved_gap_total: u64,
replay_repair_total: u64,
redundant_coverage_repair_total: u64,
http_scan_repair_total: u64,
) -> Self {
self.repaired_gap_total = repaired_gap_total;
self.unresolved_gap_total = unresolved_gap_total;
self.replay_repair_total = replay_repair_total;
self.redundant_coverage_repair_total = redundant_coverage_repair_total;
self.http_scan_repair_total = http_scan_repair_total;
return self;
}
/// Returns a copy carrying cumulative bounded material-recovery totals.
pub(crate) fn with_material_totals(mut self, repair_block_fetch_total: u64, repair_transaction_hydration_total: u64) -> Self {
self.repair_block_fetch_total = repair_block_fetch_total;
self.repair_transaction_hydration_total = repair_transaction_hydration_total;
return self;
}
/// Returns the bounded recent/open gap projections.
pub(crate) fn gaps(&self) -> &[crate::RawTransactionIngestGapSnapshot] {
return self.gaps.as_slice();
}
/// Returns the current number of open gaps.
pub(crate) const fn open_gap_count(&self) -> usize {
return self.open_gap_count;
}
/// Returns the current number of actively recovering gaps.
pub(crate) const fn repairing_gap_count(&self) -> usize {
return self.repairing_gap_count;
}
/// Returns the cumulative repaired-gap total.
pub(crate) const fn repaired_gap_total(&self) -> u64 {
return self.repaired_gap_total;
}
/// Returns the cumulative unresolved-gap total.
pub(crate) const fn unresolved_gap_total(&self) -> u64 {
return self.unresolved_gap_total;
}
/// Returns the cumulative replay recovery total.
pub(crate) const fn replay_repair_total(&self) -> u64 {
return self.replay_repair_total;
}
/// Returns the cumulative distinct-source coverage recovery total.
pub(crate) const fn redundant_coverage_repair_total(&self) -> u64 {
return self.redundant_coverage_repair_total;
}
/// Returns the cumulative bounded HTTP scan recovery total.
pub(crate) const fn http_scan_repair_total(&self) -> u64 {
return self.http_scan_repair_total;
}
/// Returns the cumulative direct block material recovery total.
pub(crate) const fn repair_block_fetch_total(&self) -> u64 {
return self.repair_block_fetch_total;
}
/// Returns the cumulative transaction hydration recovery total.
pub(crate) const fn repair_transaction_hydration_total(&self) -> u64 {
return self.repair_transaction_hydration_total;
}
/// Returns the oldest first slot among currently open gaps.
pub(crate) const fn oldest_open_gap_start_slot(&self) -> std::option::Option<u64> {
return self.oldest_open_gap_start_slot;
}
}
/// Private latest-value source-processing projection emitted by the productive source task.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct RawTransactionIngestProcessingFrontierProjection {
continuity_frontier_slot: std::option::Option<u64>,
continuity_has_open_gaps: bool,
continuity_policy_observed: bool,
failed_source_losses_reconciled: bool,
future_target_coverage: bool,
continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection,
hydration_pending: usize,
processing_frontier_slot: std::option::Option<u64>,
oldest_pending_slot: std::option::Option<u64>,
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
source_total: usize,
source_active: usize,
source_reconnecting: usize,
source_failed: usize,
source_reconnect_total: u64,
source_replay_attempt_total: u64,
source_continuity_gap_total: u64,
}
impl crate::RawTransactionIngestProcessingFrontierProjection {
/// Returns the empty run-local processing projection used before the source observes work.
pub(crate) fn empty() -> Self {
return Self {
continuity_frontier_slot: std::option::Option::None,
continuity_has_open_gaps: false,
continuity_policy_observed: false,
failed_source_losses_reconciled: false,
future_target_coverage: false,
continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection::empty(),
hydration_pending: 0,
processing_frontier_slot: std::option::Option::None,
oldest_pending_slot: std::option::Option::None,
source_state: std::option::Option::None,
source_total: 0,
source_active: 0,
source_reconnecting: 0,
source_failed: 0,
source_reconnect_total: 0,
source_replay_attempt_total: 0,
source_continuity_gap_total: 0,
};
}
/// Creates one run-local processing projection from bounded source-owned state.
pub(crate) fn new(hydration_pending: usize, processing_frontier_slot: std::option::Option<u64>, oldest_pending_slot: std::option::Option<u64>) -> Self {
return Self {
continuity_frontier_slot: std::option::Option::None,
continuity_has_open_gaps: false,
continuity_policy_observed: false,
failed_source_losses_reconciled: false,
future_target_coverage: false,
continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection::empty(),
hydration_pending,
processing_frontier_slot,
oldest_pending_slot,
source_state: std::option::Option::None,
source_total: 0,
source_active: 0,
source_reconnecting: 0,
source_failed: 0,
source_reconnect_total: 0,
source_replay_attempt_total: 0,
source_continuity_gap_total: 0,
};
}
/// Returns the number of source signals still pending hydration/admission processing.
pub(crate) const fn hydration_pending(&self) -> usize {
return self.hydration_pending;
}
/// Returns the highest actually observed slot not blocked by older pending source work.
pub(crate) const fn processing_frontier_slot(&self) -> std::option::Option<u64> {
return self.processing_frontier_slot;
}
/// Returns the oldest actually observed slot that still owns pending source work.
pub(crate) const fn oldest_pending_slot(&self) -> std::option::Option<u64> {
return self.oldest_pending_slot;
}
/// Returns a copy carrying the latest source reconnect/replay projection.
pub(crate) fn with_source_continuity(
mut self,
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
source_reconnect_total: u64,
source_replay_attempt_total: u64,
source_continuity_gap_total: u64,
) -> Self {
self.source_state = source_state;
self.source_reconnect_total = source_reconnect_total;
self.source_replay_attempt_total = source_replay_attempt_total;
self.source_continuity_gap_total = source_continuity_gap_total;
return self;
}
/// Returns a copy carrying source-neutral multi-source lifecycle counts.
pub(crate) fn with_source_counts(mut self, source_total: usize, source_active: usize, source_reconnecting: usize, source_failed: usize) -> Self {
self.source_total = source_total;
self.source_active = source_active;
self.source_reconnecting = source_reconnecting;
self.source_failed = source_failed;
return self;
}
/// Returns a copy carrying source-neutral run-local continuity evidence used only for Worker health classification.
pub(crate) fn with_continuity_health(
mut self,
continuity_frontier_slot: std::option::Option<u64>,
continuity_has_open_gaps: bool,
future_target_coverage: bool,
) -> Self {
self.continuity_frontier_slot = continuity_frontier_slot;
self.continuity_has_open_gaps = continuity_has_open_gaps;
self.continuity_policy_observed = true;
self.future_target_coverage = future_target_coverage;
return self;
}
/// Returns a copy carrying bounded source-neutral continuity observability.
pub(crate) fn with_continuity_snapshot(mut self, continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection) -> Self {
self.continuity_snapshot = continuity_snapshot;
return self;
}
/// Returns the bounded source-neutral continuity observability carried by this projection.
pub(crate) const fn continuity_snapshot(&self) -> &crate::RawTransactionIngestContinuitySnapshotProjection {
return &self.continuity_snapshot;
}
/// Returns whether a run-local continuity policy projection has been observed.
pub(crate) const fn continuity_policy_observed(&self) -> bool {
return self.continuity_policy_observed;
}
/// Returns the gap-aware continuity frontier carried by this private projection.
pub(crate) const fn continuity_frontier_slot(&self) -> std::option::Option<u64> {
return self.continuity_frontier_slot;
}
/// Returns whether at least one run-local continuity gap remains unresolved.
pub(crate) const fn continuity_has_open_gaps(&self) -> bool {
return self.continuity_has_open_gaps;
}
/// Returns whether currently Active sources still cover the complete configured future `TargetCoverage`.
pub(crate) const fn future_target_coverage(&self) -> bool {
return self.future_target_coverage;
}
/// Returns a copy carrying whether every terminal Failed source has a reconciled source-loss gap.
pub(crate) fn with_failed_source_losses_reconciled(mut self, failed_source_losses_reconciled: bool) -> Self {
self.failed_source_losses_reconciled = failed_source_losses_reconciled;
return self;
}
/// Returns whether every terminal Failed source has a reconciled source-loss gap.
pub(crate) const fn failed_source_losses_reconciled(&self) -> bool {
return self.failed_source_losses_reconciled;
}
/// Returns the configured logical source count carried by this private projection.
pub(crate) const fn source_total(&self) -> usize {
return self.source_total;
}
/// Returns the number of sources currently projected Active.
pub(crate) const fn source_active(&self) -> usize {
return self.source_active;
}
/// Returns the number of sources currently projected Reconnecting.
pub(crate) const fn source_reconnecting(&self) -> usize {
return self.source_reconnecting;
}
/// Returns the number of sources currently projected Failed.
pub(crate) const fn source_failed(&self) -> usize {
return self.source_failed;
}
/// Returns the latest source-neutral lifecycle state carried by this private projection.
pub(crate) const fn source_state(&self) -> std::option::Option<crate::RawTransactionIngestSourceState> {
return self.source_state;
}
/// Returns successful reconnects carried by this private projection.
pub(crate) const fn source_reconnect_total(&self) -> u64 {
return self.source_reconnect_total;
}
/// Returns replay-bearing reconnect attempts carried by this private projection.
pub(crate) const fn source_replay_attempt_total(&self) -> u64 {
return self.source_replay_attempt_total;
}
/// Returns proven replay-retention gaps carried by this private projection.
pub(crate) const fn source_continuity_gap_total(&self) -> u64 {
return self.source_continuity_gap_total;
}
}
/// Complete safe latest-value snapshot of one continuous RAW transaction ingest Worker.
#[derive(Clone, Eq, PartialEq)]
pub struct RawTransactionIngestSnapshot {
worker: ksp_worker_api::WorkerSnapshot,
continuity_frontier_slot: std::option::Option<u64>,
continuity_has_open_gaps: bool,
continuity_policy_observed: bool,
failed_source_losses_reconciled: bool,
future_target_coverage: bool,
gaps: std::vec::Vec<crate::RawTransactionIngestGapSnapshot>,
open_gap_count: usize,
repairing_gap_count: usize,
repaired_gap_total: u64,
unresolved_gap_total: u64,
replay_repair_total: u64,
redundant_coverage_repair_total: u64,
http_scan_repair_total: u64,
repair_block_fetch_total: u64,
repair_transaction_hydration_total: u64,
oldest_open_gap_start_slot: std::option::Option<u64>,
admission_queue_capacity: usize,
admission_queue_depth: usize,
persistence_concurrency: usize,
@@ -25,6 +513,17 @@ pub struct RawTransactionIngestSnapshot {
store_failure_total: u64,
source_failure_total: u64,
backpressure_wait_total: u64,
hydration_pending: usize,
processing_frontier_slot: std::option::Option<u64>,
oldest_pending_slot: std::option::Option<u64>,
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
source_total: usize,
source_active: usize,
source_reconnecting: usize,
source_failed: usize,
source_reconnect_total: u64,
source_replay_attempt_total: u64,
source_continuity_gap_total: u64,
}
impl crate::RawTransactionIngestSnapshot {
@@ -129,6 +628,140 @@ impl crate::RawTransactionIngestSnapshot {
pub const fn backpressure_wait_total(&self) -> u64 {
return self.backpressure_wait_total;
}
/// Returns the latest number of source signals pending hydration/admission processing.
#[must_use]
pub const fn hydration_pending(&self) -> usize {
return self.hydration_pending;
}
/// Returns the run-local processing frontier over actually observed source work.
#[must_use]
pub const fn processing_frontier_slot(&self) -> std::option::Option<u64> {
return self.processing_frontier_slot;
}
/// Returns the oldest actually observed slot that still owns pending source work.
#[must_use]
pub const fn oldest_pending_slot(&self) -> std::option::Option<u64> {
return self.oldest_pending_slot;
}
/// Returns the bounded source-neutral gap projections retained in this latest-value snapshot.
///
/// Every open gap is retained while capacity remains bounded; recent repaired entries may fill unused projection capacity.
#[must_use]
pub fn gaps(&self) -> &[crate::RawTransactionIngestGapSnapshot] {
return self.gaps.as_slice();
}
/// Returns the number of currently open run-local continuity gaps.
#[must_use]
pub const fn open_gap_count(&self) -> usize {
return self.open_gap_count;
}
/// Returns the number of currently active gap recoveries.
#[must_use]
pub const fn repairing_gap_count(&self) -> usize {
return self.repairing_gap_count;
}
/// Returns the number of gaps cumulatively proven repaired during this run.
#[must_use]
pub const fn repaired_gap_total(&self) -> u64 {
return self.repaired_gap_total;
}
/// Returns the number of gaps cumulatively classified unresolved during this run.
#[must_use]
pub const fn unresolved_gap_total(&self) -> u64 {
return self.unresolved_gap_total;
}
/// Returns the number of gaps whose latest successful recovery proof used replay.
#[must_use]
pub const fn replay_repair_total(&self) -> u64 {
return self.replay_repair_total;
}
/// Returns the number of gaps whose latest successful recovery proof used distinct-source coverage.
#[must_use]
pub const fn redundant_coverage_repair_total(&self) -> u64 {
return self.redundant_coverage_repair_total;
}
/// Returns the number of gaps whose latest successful recovery proof used a bounded HTTP scan.
#[must_use]
pub const fn http_scan_repair_total(&self) -> u64 {
return self.http_scan_repair_total;
}
/// Returns the number of repaired gaps whose last recorded mechanism was direct block material retrieval.
#[must_use]
pub const fn repair_block_fetch_total(&self) -> u64 {
return self.repair_block_fetch_total;
}
/// Returns the number of repaired gaps whose last recorded mechanism was transaction hydration.
#[must_use]
pub const fn repair_transaction_hydration_total(&self) -> u64 {
return self.repair_transaction_hydration_total;
}
/// Returns the oldest first slot among currently open continuity gaps.
#[must_use]
pub const fn oldest_open_gap_start_slot(&self) -> std::option::Option<u64> {
return self.oldest_open_gap_start_slot;
}
/// Returns the configured number of logical live sources for this Worker run.
#[must_use]
pub const fn source_total(&self) -> usize {
return self.source_total;
}
/// Returns the latest number of sources projected Active.
#[must_use]
pub const fn source_active(&self) -> usize {
return self.source_active;
}
/// Returns the latest number of sources projected Reconnecting.
#[must_use]
pub const fn source_reconnecting(&self) -> usize {
return self.source_reconnecting;
}
/// Returns the latest number of sources projected Failed.
#[must_use]
pub const fn source_failed(&self) -> usize {
return self.source_failed;
}
/// Returns the latest source-neutral lifecycle state when the productive source has started.
#[must_use]
pub const fn source_state(&self) -> std::option::Option<crate::RawTransactionIngestSourceState> {
return self.source_state;
}
/// Returns successful automatic Yellowstone reconnects observed by this Worker run.
#[must_use]
pub const fn source_reconnect_total(&self) -> u64 {
return self.source_reconnect_total;
}
/// Returns Yellowstone replay-bearing reconnect attempts observed by this Worker run.
#[must_use]
pub const fn source_replay_attempt_total(&self) -> u64 {
return self.source_replay_attempt_total;
}
/// Returns replay retention gaps conservatively proven by Transport during this Worker run.
#[must_use]
pub const fn source_continuity_gap_total(&self) -> u64 {
return self.source_continuity_gap_total;
}
}
impl std::fmt::Debug for crate::RawTransactionIngestSnapshot {
@@ -136,6 +769,17 @@ impl std::fmt::Debug for crate::RawTransactionIngestSnapshot {
return formatter
.debug_struct("RawTransactionIngestSnapshot")
.field("worker", &self.worker)
.field("gaps", &self.gaps)
.field("open_gap_count", &self.open_gap_count)
.field("repairing_gap_count", &self.repairing_gap_count)
.field("repaired_gap_total", &self.repaired_gap_total)
.field("unresolved_gap_total", &self.unresolved_gap_total)
.field("replay_repair_total", &self.replay_repair_total)
.field("redundant_coverage_repair_total", &self.redundant_coverage_repair_total)
.field("http_scan_repair_total", &self.http_scan_repair_total)
.field("repair_block_fetch_total", &self.repair_block_fetch_total)
.field("repair_transaction_hydration_total", &self.repair_transaction_hydration_total)
.field("oldest_open_gap_start_slot", &self.oldest_open_gap_start_slot)
.field("admission_queue_capacity", &self.admission_queue_capacity)
.field("admission_queue_depth", &self.admission_queue_depth)
.field("persistence_concurrency", &self.persistence_concurrency)
@@ -152,6 +796,17 @@ impl std::fmt::Debug for crate::RawTransactionIngestSnapshot {
.field("store_failure_total", &self.store_failure_total)
.field("source_failure_total", &self.source_failure_total)
.field("backpressure_wait_total", &self.backpressure_wait_total)
.field("hydration_pending", &self.hydration_pending)
.field("processing_frontier_slot", &self.processing_frontier_slot)
.field("oldest_pending_slot", &self.oldest_pending_slot)
.field("source_state", &self.source_state)
.field("source_total", &self.source_total)
.field("source_active", &self.source_active)
.field("source_reconnecting", &self.source_reconnecting)
.field("source_failed", &self.source_failed)
.field("source_reconnect_total", &self.source_reconnect_total)
.field("source_replay_attempt_total", &self.source_replay_attempt_total)
.field("source_continuity_gap_total", &self.source_continuity_gap_total)
.finish();
}
}
@@ -238,6 +893,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
pub(crate) fn new(
settings: &crate::RawTransactionIngestSettings,
lifecycle: &ksp_worker_api::WorkerLifecycle,
source_total: usize,
) -> (crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource) {
let worker = ksp_worker_api::WorkerSnapshot::new(
lifecycle.id().clone(),
@@ -249,6 +905,22 @@ impl crate::RawTransactionIngestSnapshotPublisher {
);
let snapshot = crate::RawTransactionIngestSnapshot {
worker,
continuity_frontier_slot: std::option::Option::None,
continuity_has_open_gaps: false,
continuity_policy_observed: false,
failed_source_losses_reconciled: false,
future_target_coverage: false,
gaps: std::vec::Vec::new(),
open_gap_count: 0,
repairing_gap_count: 0,
repaired_gap_total: 0,
unresolved_gap_total: 0,
replay_repair_total: 0,
redundant_coverage_repair_total: 0,
http_scan_repair_total: 0,
repair_block_fetch_total: 0,
repair_transaction_hydration_total: 0,
oldest_open_gap_start_slot: std::option::Option::None,
admission_queue_capacity: settings.admission_queue_capacity(),
admission_queue_depth: 0,
persistence_concurrency: settings.persistence_concurrency(),
@@ -265,6 +937,17 @@ impl crate::RawTransactionIngestSnapshotPublisher {
store_failure_total: 0,
source_failure_total: 0,
backpressure_wait_total: 0,
hydration_pending: 0,
processing_frontier_slot: std::option::Option::None,
oldest_pending_slot: std::option::Option::None,
source_state: std::option::Option::None,
source_total,
source_active: 0,
source_reconnecting: 0,
source_failed: 0,
source_reconnect_total: 0,
source_replay_attempt_total: 0,
source_continuity_gap_total: 0,
};
let (sender, receiver) = tokio::sync::watch::channel(snapshot.clone());
return (Self { sender, snapshot }, crate::RawTransactionIngestSnapshotSource { receiver });
@@ -277,7 +960,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
self.snapshot.worker.kind().clone(),
self.snapshot.worker.sequence(),
state,
health_for_state(state, self.snapshot.worker.health()),
health_for_state(state, self.snapshot.worker.health(), &self.snapshot),
ksp_worker_api::WorkerActivity::Idle,
);
self.snapshot.worker = worker;
@@ -346,6 +1029,47 @@ impl crate::RawTransactionIngestSnapshotPublisher {
return self.publish(state, admission_queue_depth, in_flight_persistence);
}
/// Publishes one latest run-local processing-frontier projection emitted by the source task.
pub(crate) fn record_processing_frontier(
&mut self,
state: ksp_worker_api::WorkerState,
admission_queue_depth: usize,
in_flight_persistence: usize,
projection: crate::RawTransactionIngestProcessingFrontierProjection,
) -> ksp_core_lib::Result<()> {
self.snapshot.continuity_frontier_slot = projection.continuity_frontier_slot();
self.snapshot.continuity_has_open_gaps = projection.continuity_has_open_gaps();
self.snapshot.continuity_policy_observed = projection.continuity_policy_observed();
self.snapshot.failed_source_losses_reconciled = projection.failed_source_losses_reconciled();
self.snapshot.future_target_coverage = projection.future_target_coverage();
let continuity_snapshot = projection.continuity_snapshot();
self.snapshot.gaps = continuity_snapshot.gaps().to_vec();
self.snapshot.open_gap_count = continuity_snapshot.open_gap_count();
self.snapshot.repairing_gap_count = continuity_snapshot.repairing_gap_count();
self.snapshot.repaired_gap_total = continuity_snapshot.repaired_gap_total();
self.snapshot.unresolved_gap_total = continuity_snapshot.unresolved_gap_total();
self.snapshot.replay_repair_total = continuity_snapshot.replay_repair_total();
self.snapshot.redundant_coverage_repair_total = continuity_snapshot.redundant_coverage_repair_total();
self.snapshot.http_scan_repair_total = continuity_snapshot.http_scan_repair_total();
self.snapshot.repair_block_fetch_total = continuity_snapshot.repair_block_fetch_total();
self.snapshot.repair_transaction_hydration_total = continuity_snapshot.repair_transaction_hydration_total();
self.snapshot.oldest_open_gap_start_slot = continuity_snapshot.oldest_open_gap_start_slot();
self.snapshot.hydration_pending = projection.hydration_pending();
self.snapshot.processing_frontier_slot = projection.processing_frontier_slot();
self.snapshot.oldest_pending_slot = projection.oldest_pending_slot();
self.snapshot.source_state = projection.source_state();
if projection.source_total() > 0 {
self.snapshot.source_total = projection.source_total();
self.snapshot.source_active = projection.source_active();
self.snapshot.source_reconnecting = projection.source_reconnecting();
self.snapshot.source_failed = projection.source_failed();
}
self.snapshot.source_reconnect_total = projection.source_reconnect_total();
self.snapshot.source_replay_attempt_total = projection.source_replay_attempt_total();
self.snapshot.source_continuity_gap_total = projection.source_continuity_gap_total();
return self.publish(state, admission_queue_depth, in_flight_persistence);
}
/// Records one failed private source task without retaining provider-specific error material.
pub(crate) fn record_source_failure(
&mut self,
@@ -456,8 +1180,8 @@ impl crate::RawTransactionIngestSnapshotPublisher {
self.snapshot.worker.kind().clone(),
sequence,
state,
health_for_state(state, self.snapshot.worker.health()),
activity_for_state(state, admission_queue_depth, in_flight_persistence),
health_for_state(state, self.snapshot.worker.health(), &self.snapshot),
activity_for_state(state, admission_queue_depth, in_flight_persistence, self.snapshot.hydration_pending, self.snapshot.source_state),
);
self.snapshot.worker = worker;
self.snapshot.admission_queue_depth = admission_queue_depth;
@@ -467,8 +1191,22 @@ impl crate::RawTransactionIngestSnapshotPublisher {
}
}
fn activity_for_state(state: ksp_worker_api::WorkerState, admission_queue_depth: usize, in_flight_persistence: usize) -> ksp_worker_api::WorkerActivity {
if admission_queue_depth > 0 || in_flight_persistence > 0 {
fn activity_for_state(
state: ksp_worker_api::WorkerState,
admission_queue_depth: usize,
in_flight_persistence: usize,
hydration_pending: usize,
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
) -> ksp_worker_api::WorkerActivity {
if admission_queue_depth > 0
|| in_flight_persistence > 0
|| hydration_pending > 0
|| matches!(
source_state,
std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting)
| std::option::Option::Some(crate::RawTransactionIngestSourceState::Closing)
)
{
return ksp_worker_api::WorkerActivity::Active;
}
return match state {
@@ -494,8 +1232,41 @@ fn checked_optional_counter(current: u64, increment: bool, field: &'static str)
return checked_counter(current, field);
}
fn health_for_state(state: ksp_worker_api::WorkerState, previous: ksp_worker_api::WorkerHealth) -> ksp_worker_api::WorkerHealth {
fn health_for_state(
state: ksp_worker_api::WorkerState,
previous: ksp_worker_api::WorkerHealth,
snapshot: &crate::RawTransactionIngestSnapshot,
) -> ksp_worker_api::WorkerHealth {
if state == ksp_worker_api::WorkerState::Running && snapshot.continuity_policy_observed {
let source_total = snapshot.source_total;
let source_active = snapshot.source_active;
let source_failed = snapshot.source_failed;
if snapshot.source_reconnecting > 0
|| snapshot.continuity_has_open_gaps
|| snapshot.continuity_frontier_slot != snapshot.processing_frontier_slot
|| !snapshot.future_target_coverage
{
return ksp_worker_api::WorkerHealth::Unhealthy;
}
if source_total > 0 && snapshot.source_active == snapshot.source_total {
return ksp_worker_api::WorkerHealth::Healthy;
}
if source_total > 0
&& source_active < source_total
&& source_failed > 0
&& snapshot.failed_source_losses_reconciled
&& source_failed == source_total - source_active
{
return ksp_worker_api::WorkerHealth::Degraded;
}
return ksp_worker_api::WorkerHealth::Unhealthy;
}
return match state {
ksp_worker_api::WorkerState::Running if snapshot.source_failed > 0 => ksp_worker_api::WorkerHealth::Unhealthy,
ksp_worker_api::WorkerState::Running if snapshot.source_reconnecting > 0 => ksp_worker_api::WorkerHealth::Degraded,
ksp_worker_api::WorkerState::Running if snapshot.source_total > 0 && snapshot.source_active < snapshot.source_total => {
ksp_worker_api::WorkerHealth::Degraded
},
ksp_worker_api::WorkerState::Running => ksp_worker_api::WorkerHealth::Healthy,
ksp_worker_api::WorkerState::Stopping | ksp_worker_api::WorkerState::Stopped => previous,
ksp_worker_api::WorkerState::Faulted(_) => ksp_worker_api::WorkerHealth::Unhealthy,

View File

@@ -0,0 +1,489 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/cross_layer_completeness.rs
// version: 3
//! Cross-layer completeness and security canaries through `0.3.14-pre.013`.
fn dependency_names(manifest: &str) -> std::vec::Vec<&str> {
let mut section = "";
let mut names = std::vec::Vec::new();
for line in manifest.lines() {
let trimmed = line.trim();
if trimmed.starts_with('[') && trimmed.ends_with(']') {
section = trimmed;
continue;
}
if section != "[dependencies]" || trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
let name = match trimmed.split_once('=') {
std::option::Option::Some((name, _)) => name.trim().trim_end_matches(".workspace"),
std::option::Option::None => continue,
};
names.push(name);
}
names.sort_unstable();
return names;
}
#[test]
fn v0_3_12_pre_010_dependency_firewall_is_exact_across_transport_worker_common_raw_store() {
let transport = dependency_names(include_str!("../../ksp-onchain-transport-lib/Cargo.toml"));
let worker = dependency_names(include_str!("../Cargo.toml"));
let common_raw = dependency_names(include_str!("../../ksp-raw-transaction-lib/Cargo.toml"));
let store_api = dependency_names(include_str!("../../ksp-store-api/Cargo.toml"));
let store = dependency_names(include_str!("../../ksp-store-lib/Cargo.toml"));
assert_eq!(
transport,
std::vec![
"futures-util",
"http",
"ksp-core-lib",
"ksp-logging-lib",
"reqwest",
"serde",
"serde_json",
"tokio",
"tokio-tungstenite",
"tonic",
"tonic-prost",
"yellowstone-grpc-proto",
]
);
assert_eq!(
worker,
std::vec![
"ksp-core-lib",
"ksp-logging-lib",
"ksp-onchain-transport-lib",
"ksp-raw-transaction-lib",
"ksp-store-lib",
"ksp-worker-api",
"sha2",
"tokio",
]
);
assert_eq!(common_raw, std::vec!["base64", "ksp-core-lib", "ksp-store-api", "serde_json", "sha2"]);
assert_eq!(store_api, std::vec!["ksp-core-lib"]);
assert_eq!(store, std::vec!["ksp-logging-lib", "ksp-store-api", "ksp-store-postgres-lib"]);
for forbidden in ["ksp-config-lib", "ksp-job-api", "ksp-job-backfill-lib", "ksp-store-lib", "ksp-worker-api", "ksp-worker-raw-transaction-ingest-lib"] {
assert!(!transport.contains(&forbidden), "Transport crossed upward dependency boundary: {forbidden}");
}
for forbidden in [
"ksp-config-lib",
"ksp-job-api",
"ksp-job-backfill-lib",
"ksp-store-api",
"ksp-store-postgres-lib",
"reqwest",
"tonic",
"yellowstone-grpc-proto",
] {
assert!(!worker.contains(&forbidden), "Worker crossed direct lower/backend dependency boundary: {forbidden}");
}
for forbidden in [
"ksp-config-lib",
"ksp-job-api",
"ksp-job-backfill-lib",
"ksp-onchain-transport-lib",
"ksp-store-lib",
"ksp-store-postgres-lib",
"ksp-worker-api",
] {
assert!(!common_raw.contains(&forbidden), "Common RAW crossed dependency boundary: {forbidden}");
}
for forbidden in ["ksp-config-lib", "ksp-onchain-transport-lib", "ksp-raw-transaction-lib", "ksp-store-lib", "ksp-store-postgres-lib", "ksp-worker-api"] {
assert!(!store_api.contains(&forbidden), "Store API crossed dependency boundary: {forbidden}");
}
for forbidden in ["ksp-config-lib", "ksp-job-api", "ksp-job-backfill-lib", "ksp-onchain-transport-lib", "ksp-raw-transaction-lib", "ksp-worker-api"] {
assert!(!store.contains(&forbidden), "Store facade crossed dependency boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_010_legacy_v0_fixture_matrix_is_exact_across_transport_worker_and_common_raw() {
let worker = include_str!("../unit_tests/runtime_resources.rs");
let common_raw_wire = include_str!("../../ksp-raw-transaction-lib/unit_tests/wire.rs");
let transport = include_str!("../../ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs");
for required in [
"pre_004_observed_get_transaction_closes_signal_to_common_raw_ingress_with_composite_provenance",
"pre_004_finalized_hydration_preserves_request_and_provenance_commitment",
"pre_004_v0_null_and_omitted_wire_fields_preserve_common_raw_semantics",
r#"\"version\":\"legacy\""#,
r#"\"version\":0"#,
] {
assert!(worker.contains(required), "Worker Legacy/V0 fixture missing: {required}");
}
for required in [
"LEGACY_GOLDEN_BASE64",
"V0_GOLDEN_BASE64",
"pre_006_legacy_v0_and_v1_wire_goldens_are_exact",
"RawSolanaMessageVersion::Legacy",
"RawSolanaMessageVersion::V0",
] {
assert!(common_raw_wire.contains(required), "Common RAW Legacy/V0 golden missing: {required}");
}
for required in [
"confirmed_transaction_fixture_preserves_meta_version_and_transaction_index_states",
"typed_get_transaction_modern_covers_all_agave_v4_2_1_encoding_labels_and_response_shapes",
"SolanaTransactionVersion::Legacy",
] {
assert!(transport.contains(required), "Transport getTransaction fixture missing: {required}");
}
return;
}
#[test]
fn v0_3_12_pre_010_redaction_scanner_matrix_is_present_across_all_layers() {
let transport_subscribe = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let transport_unary = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_unary.rs");
let worker = include_str!("hardening.rs");
let common_raw = include_str!("../../ksp-raw-transaction-lib/unit_tests/canonical.rs");
let store_api = include_str!("../../ksp-store-api/tests/security_hardening.rs");
let store = include_str!("../../ksp-store-lib/tests/hardening_completeness.rs");
assert!(transport_subscribe.contains("yellowstone_subscribe_debug_omits_filter_names_and_future_payloads"));
assert!(transport_unary.contains("yellowstone_remote_status_does_not_copy_message_details_or_metadata"));
for required in [
"pre_010_debug_and_settings_errors_redact_worker_identity_and_invalid_values",
"v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload",
"v0_3_12_pre_004_hydration_provenance_and_remote_material_are_bounded_and_redacted",
"v0_3_12_pre_008_reconnect_projection_is_source_neutral_bounded_and_contains_no_replay_material",
"v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit",
] {
assert!(worker.contains(required), "Worker security scanner missing: {required}");
}
assert!(common_raw.contains("pre_002_material_and_wire_debug_do_not_render_transaction_or_meta_material"));
assert!(store_api.contains("pre_007_raw_debug_surfaces_do_not_render_payload_hash_signature_or_account_bytes"));
assert!(store.contains("pre_009_secret_canary_never_crosses_settings_or_pre_io_error_debug"));
return;
}
#[test]
fn v0_3_12_pre_010_public_roots_keep_implementation_modules_private() {
let roots = [
("transport", include_str!("../../ksp-onchain-transport-lib/src/lib.rs")),
("worker", include_str!("../src/lib.rs")),
("common_raw", include_str!("../../ksp-raw-transaction-lib/src/lib.rs")),
("store_api", include_str!("../../ksp-store-api/src/lib.rs")),
("store", include_str!("../../ksp-store-lib/src/lib.rs")),
];
for (layer, root) in roots {
assert!(!root.contains("pub mod "), "public implementation module leaked from {layer}");
}
let worker = include_str!("../src/lib.rs");
for line in worker.lines() {
let trimmed = line.trim();
if !trimmed.starts_with("pub use ") {
continue;
}
for forbidden in [
"RawTransactionAdmission",
"RawTransactionIngress",
"RawTransactionIngestPersistencePort",
"JoinHandle",
"JoinSet",
"HttpTransportPool",
"SolanaYellowstoneGrpcSubscribeSession",
] {
assert!(!trimmed.contains(forbidden), "Worker implementation type leaked publicly: {forbidden}");
}
}
return;
}
#[test]
fn v0_3_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline() {
let transport_root = include_str!("../../ksp-onchain-transport-lib/src/lib.rs");
let transport_http_transactions = include_str!("../../ksp-onchain-transport-lib/src/rpc_transactions.rs");
let transport_http_blocks = include_str!("../../ksp-onchain-transport-lib/src/rpc_blocks.rs");
let transport_ws_protocol = include_str!("../../ksp-onchain-transport-lib/src/ws_protocol_session.rs");
let transport_grpc_stream = include_str!("../../ksp-onchain-transport-lib/src/grpc_stream.rs");
let worker_resources = include_str!("../src/runtime_resources.rs");
let worker_persistence = include_str!("../src/persistence.rs");
let common_raw_root = include_str!("../../ksp-raw-transaction-lib/src/lib.rs");
let store_root = include_str!("../../ksp-store-lib/src/lib.rs");
let store = include_str!("../../ksp-store-lib/src/store.rs");
for required in [
"HeliusTransaction(crate::RawTransactionIngestHeliusTransactionSource)",
"HttpBlockPolling(crate::RawTransactionIngestHttpBlockPollingSource)",
"StandardBlock(crate::RawTransactionIngestStandardBlockSource)",
"StandardLogs(crate::RawTransactionIngestStandardLogsSource)",
"Yellowstone(crate::RawTransactionIngestYellowstoneSource)",
"RawTransactionIngestGlobalHydrationRegistry",
"run_live_sources",
"RawTransactionIngress",
] {
assert!(worker_resources.contains(required), "pre.012 Worker source/pipeline contract missing: {required}");
}
for required in ["persist_raw_transaction_ingest_converged_acquisition", "RawTransactionIngestPersistenceConvergence", "record_observation"] {
assert!(worker_persistence.contains(required), "pre.012 Worker persistence contract missing: {required}");
}
for required in ["SolanaStandardWsSession", "HeliusLaserStreamWsSession"] {
assert!(transport_ws_protocol.contains(required), "pre.012 WS Transport facade missing: {required}");
}
assert!(transport_grpc_stream.contains("pub struct SolanaYellowstoneGrpcSubscribeSession"));
assert!(transport_http_transactions.contains("pub async fn get_transaction_observed("));
assert!(transport_http_blocks.contains("pub async fn get_block_observed("));
assert!(transport_http_blocks.contains("pub async fn get_blocks_with_limit("));
for required in ["RawTransactionAcquisition", "RawSolanaMessageVersion", "RawSolanaTransactionWire"] {
assert!(common_raw_root.contains(required), "pre.012 Common RAW contract missing: {required}");
}
for required in ["RawTransactionWrite", "RawTransactionObservationWrite", "RawPayload"] {
assert!(store_root.contains(required), "pre.012 Store facade contract missing: {required}");
}
for required in ["persist_raw_transaction_acquisition", "record_raw_transaction_observation"] {
assert!(store.contains(required), "pre.012 Store dispatch contract missing: {required}");
}
assert!(!transport_root.contains("ksp_raw_transaction_lib"));
return;
}
#[test]
fn v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store() {
let transport_grpc = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let transport_blocks = include_str!("../../ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs");
let worker = include_str!("../unit_tests/runtime_resources.rs");
let worker_resources = include_str!("../src/runtime_resources.rs");
let worker_persistence = include_str!("../src/persistence.rs");
let common_raw_wire = include_str!("../../ksp-raw-transaction-lib/unit_tests/wire.rs");
let common_raw_canonical = include_str!("../../ksp-raw-transaction-lib/unit_tests/canonical.rs");
let store_api = include_str!("../../ksp-store-api/tests/public_api.rs");
let store = include_str!("../../ksp-store-lib/src/store.rs");
assert!(transport_grpc.contains("yellowstone_transaction_update_decodes_current_storage_wire_including_v1_config_and_meta"));
assert!(transport_blocks.contains("typed_get_block_modern_full_config_preserves_rich_wire_and_simd_fields"));
for required in [
"v0_3_13_pre_004_standard_block_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions",
"v0_3_13_pre_004_legacy_v0_v1_block_materials_are_direct_common_raw_with_exact_version_and_index",
"v0_3_13_pre_006_http_block_polling_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions",
"RawSolanaMessageVersion::Legacy",
"RawSolanaMessageVersion::V0",
"RawSolanaMessageVersion::V1",
] {
assert!(worker.contains(required), "pre.012 Worker Legacy/V0/V1 proof missing: {required}");
}
assert!(worker_resources.contains("SolanaGetTransactionConfig::new"));
assert!(worker_resources.contains("std::option::Option::Some(1),"));
for required in [
"pre_006_legacy_v0_and_v1_wire_goldens_are_exact",
"pre_006_v1_structural_guards_reject_alt_duplicates_and_missing_config",
"RawSolanaMessageVersion::V1",
] {
assert!(common_raw_wire.contains(required), "pre.012 Common RAW V1 proof missing: {required}");
}
assert!(common_raw_canonical.contains("pre_002_canonical_raw_v1_golden_bytes_and_hash_are_exact"));
for required in ["block_time", "content_hash", "format_id", "format_version", "slot"] {
assert!(worker_persistence.contains(required), "pre.012 canonical Store identity field missing: {required}");
}
assert!(store_api.contains("public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_root"));
assert!(store_api.contains("RawPayload::try_new(format, 1"));
assert!(store.contains("persist_raw_transaction_acquisition"));
assert!(store.contains("record_raw_transaction_observation"));
return;
}
#[test]
fn v0_3_13_pre_012_yellowstone_non_regression_covers_identity_v1_reconnect_gap_and_worker_hydration() {
let transport_subscribe = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let transport_stream = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_stream.rs");
let transport_public = include_str!("../../ksp-onchain-transport-lib/tests/public_api.rs");
let worker = include_str!("../unit_tests/runtime_resources.rs");
let worker_hardening = include_str!("hardening.rs");
for required in [
"yellowstone_subscribe_request_identity_is_order_stable_exact_and_debug_redacted",
"yellowstone_transaction_update_decodes_current_storage_wire_including_v1_config_and_meta",
"yellowstone_transaction_status_update_preserves_error_and_rejects_malformed_signature",
"yellowstone_block_update_reuses_transaction_account_entry_dtos_and_preserves_server_counts",
] {
assert!(transport_subscribe.contains(required), "pre.012 Yellowstone Transport subscribe proof missing: {required}");
}
for required in [
"yellowstone_reconnect_replays_from_last_observed_slot_and_counts_duplicate_identity",
"yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossless_claim",
"yellowstone_shutdown_interrupts_reconnect_backoff_and_mutation_is_rejected_during_reconnect",
] {
assert!(transport_stream.contains(required), "pre.012 Yellowstone Transport lifecycle proof missing: {required}");
}
assert!(transport_public.contains("public_v0_3_13_pre_002_yellowstone_subscribe_identity_is_opaque_and_available_from_crate_root"));
for required in [
"pre_002_source_accepts_matching_confirmed_transaction_and_get_transaction_route_without_io",
"v0_3_13_pre_002_live_source_key_is_stable_and_request_sensitive",
"pre_003_transaction_signal_fixture_preserves_exact_source_neutral_identity",
"pre_005_block_projects_transaction_signals_in_exact_source_order",
"pre_005_block_meta_and_slot_project_continuity_only_without_remote_dead_error",
"pre_004_observed_get_transaction_closes_signal_to_common_raw_ingress_with_composite_provenance",
"pre_008_reconnect_replay_and_proven_gap_are_distinct_monotone_and_frontier_preserving",
"v0_3_13_pre_008_global_hydration_registry_coalesces_cross_source_key_to_one_leader",
] {
assert!(worker.contains(required), "pre.012 Yellowstone Worker non-regression proof missing: {required}");
}
for required in [
"v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload",
"v0_3_12_pre_008_reconnect_projection_is_source_neutral_bounded_and_contains_no_replay_material",
] {
assert!(worker_hardening.contains(required), "pre.012 Yellowstone Worker hardening proof missing: {required}");
}
return;
}
#[test]
fn v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_layers() {
let transport_subscribe = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let transport_unary = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_unary.rs");
let transport_ws = include_str!("../../ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs");
let worker = include_str!("hardening.rs");
let common_raw = include_str!("../../ksp-raw-transaction-lib/unit_tests/canonical.rs");
let store_api = include_str!("../../ksp-store-api/tests/security_hardening.rs");
let store = include_str!("../../ksp-store-lib/tests/hardening_completeness.rs");
let postgres = include_str!("../../ksp-store-postgres-lib/tests/hardening_completeness.rs");
for required in [
"yellowstone_subscribe_debug_omits_filter_names_and_future_payloads",
"yellowstone_transaction_filters_encode_complete_current_wire_and_redact_selectors",
] {
assert!(transport_subscribe.contains(required), "pre.012 Transport gRPC redaction proof missing: {required}");
}
assert!(transport_unary.contains("yellowstone_remote_status_does_not_copy_message_details_or_metadata"));
assert!(transport_ws.contains("helius_transaction_notification_debug_omits_raw_provider_payloads"));
for required in [
"v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload",
"v0_3_13_pre_003_standard_logs_redaction_and_reference_only_contract_are_explicit",
"v0_3_13_pre_004_standard_block_redaction_version_and_null_guards_are_explicit",
"v0_3_13_pre_005_helius_transaction_redaction_full_reference_and_tier_neutrality_are_explicit",
"v0_3_13_pre_006_http_block_polling_is_bounded_run_local_stop_preemptible_and_redacted",
"v0_3_13_pre_007_multi_source_supervisor_is_fail_closed_joined_and_does_not_publish_source_identity",
"v0_3_13_pre_008_cross_source_convergence_is_bounded_conflict_checked_and_private",
"v0_3_13_pre_009_duplicate_storm_disagreement_and_starvation_guards_are_explicit",
"v0_3_13_pre_010_multi_source_health_is_conservative_counted_and_redacted",
"v0_3_13_pre_011_shutdown_races_are_bounded_joined_atomic_and_counter_safe",
] {
assert!(worker.contains(required), "pre.012 Worker security proof missing: {required}");
}
assert!(common_raw.contains("pre_002_material_and_wire_debug_do_not_render_transaction_or_meta_material"));
assert!(store_api.contains("pre_007_raw_debug_surfaces_do_not_render_payload_hash_signature_or_account_bytes"));
assert!(store.contains("pre_009_secret_canary_never_crosses_settings_or_pre_io_error_debug"));
assert!(postgres.contains("pre_009_backend_error_bridge_cannot_retain_external_error_or_secret_text"));
return;
}
#[test]
fn v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline() {
let transport_transactions = include_str!("../../ksp-onchain-transport-lib/src/rpc_transactions.rs");
let transport_blocks = include_str!("../../ksp-onchain-transport-lib/src/rpc_blocks.rs");
let worker_root = include_str!("../src/lib.rs");
let worker_resources = include_str!("../src/runtime_resources.rs");
let worker_runtime = include_str!("../src/runtime.rs");
let worker_persistence = include_str!("../src/persistence.rs");
let common_raw_root = include_str!("../../ksp-raw-transaction-lib/src/lib.rs");
let store_api_root = include_str!("../../ksp-store-api/src/lib.rs");
let store = include_str!("../../ksp-store-lib/src/store.rs");
assert!(transport_transactions.contains("pub async fn get_transaction_observed("));
assert!(transport_blocks.contains("pub async fn get_block_observed("));
for required in [
"RawTransactionIngestContinuityContracts",
"RawTransactionIngestGlobalHydrationRegistry",
"RawTransactionAdmission",
"persist_raw_transaction_ingest_converged_acquisition",
] {
assert!(
worker_root.contains(required) || worker_resources.contains(required) || worker_runtime.contains(required),
"pre.013 Worker single-pipeline proof missing: {required}"
);
}
assert!(worker_persistence.contains("ksp_store_lib::RawTransactionWrite::persist_raw_transaction_acquisition"));
assert!(common_raw_root.contains("pub use self::acquisition::RawTransactionAcquisition;"));
assert!(common_raw_root.contains("pub use self::wire::RawSolanaMessageVersion;"));
assert!(store_api_root.contains("pub use self::model::raw_transaction::RawTransaction;"));
assert!(store_api_root.contains("pub use self::model::raw_transaction::RawTransactionObservation;"));
assert!(store.contains("persist_raw_transaction_acquisition"));
assert!(store.contains("record_raw_transaction_observation"));
for forbidden in [
"ksp_store_postgres_lib::",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"reqwest::",
"tokio_tungstenite::connect_async",
"yellowstone_grpc_proto::",
] {
assert!(
!worker_runtime.contains(forbidden) && !worker_resources.contains(forbidden) && !worker_persistence.contains(forbidden),
"pre.013 Worker bypassed the single facade pipeline: {forbidden}"
);
}
return;
}
#[test]
fn v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths() {
let transport_subscribe = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let transport_unary = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_unary.rs");
let worker_hardening = include_str!("hardening.rs");
let worker_root = include_str!("../src/lib.rs");
let common_raw = include_str!("../../ksp-raw-transaction-lib/unit_tests/canonical.rs");
let store_api = include_str!("../../ksp-store-api/tests/security_hardening.rs");
let store = include_str!("../../ksp-store-lib/tests/hardening_completeness.rs");
let postgres = include_str!("../../ksp-store-postgres-lib/tests/hardening_completeness.rs");
assert!(transport_subscribe.contains("yellowstone_subscribe_debug_omits_filter_names_and_future_payloads"));
assert!(transport_unary.contains("yellowstone_remote_status_does_not_copy_message_details_or_metadata"));
for required in [
"v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral",
"v0_3_14_pre_010_repair_fairness_shares_existing_bounds_without_second_pipeline",
"v0_3_14_pre_011_gap_observability_is_bounded_checked_and_redacted",
"v0_3_14_pre_012_shutdown_races_are_bounded_across_source_and_worker_drains",
] {
assert!(worker_hardening.contains(required), "pre.013 Worker security closure proof missing: {required}");
}
for forbidden in ["source_key", "provider_gap", "endpoint_gap", "signature_gap", "payload_gap"] {
assert!(!worker_root.contains(forbidden), "pre.013 public Worker root leaked sensitive gap material: {forbidden}");
}
assert!(common_raw.contains("pre_002_material_and_wire_debug_do_not_render_transaction_or_meta_material"));
assert!(store_api.contains("pre_007_raw_debug_surfaces_do_not_render_payload_hash_signature_or_account_bytes"));
assert!(store.contains("pre_009_secret_canary_never_crosses_settings_or_pre_io_error_debug"));
assert!(postgres.contains("pre_009_backend_error_bridge_cannot_retain_external_error_or_secret_text"));
return;
}
#[test]
fn v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening() {
let transport = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let worker = include_str!("../unit_tests/runtime_resources.rs");
let common_raw = include_str!("../../ksp-raw-transaction-lib/unit_tests/wire.rs");
let store_api = include_str!("../../ksp-store-api/tests/public_api.rs");
for required in [
"yellowstone_transaction_update_decodes_current_storage_wire_including_v1_config_and_meta",
"yellowstone_block_update_reuses_transaction_account_entry_dtos_and_preserves_server_counts",
] {
assert!(transport.contains(required), "pre.013 Transport Legacy/V0/V1 proof missing: {required}");
}
for required in [
"v0_3_13_pre_004_legacy_v0_v1_block_materials_are_direct_common_raw_with_exact_version_and_index",
"v0_3_13_pre_006_http_block_polling_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions",
"v0_3_14_pre_008_proven_full_ledger_epoch_survives_filtered_peer_loss_without_worker_respawn",
"v0_3_14_pre_012_source_fault_preserves_first_fault_and_aborts_non_cooperative_sibling",
] {
assert!(worker.contains(required), "pre.013 Worker Legacy/V0/V1 or continuity proof missing: {required}");
}
for required in [
"pre_006_legacy_v0_and_v1_wire_goldens_are_exact",
"RawSolanaMessageVersion::Legacy",
"RawSolanaMessageVersion::V0",
"RawSolanaMessageVersion::V1",
] {
assert!(common_raw.contains(required), "pre.013 Common RAW version proof missing: {required}");
}
assert!(store_api.contains("public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_root"));
return;
}
#[test]
fn v0_3_14_pre_013_worker_and_backfill_remain_independent_producers() {
let worker = dependency_names(include_str!("../Cargo.toml"));
let backfill = dependency_names(include_str!("../../ksp-job-backfill-lib/Cargo.toml"));
assert!(!worker.contains(&"ksp-job-api"));
assert!(!worker.contains(&"ksp-job-backfill-lib"));
assert!(!backfill.contains(&"ksp-worker-api"));
assert!(!backfill.contains(&"ksp-worker-raw-transaction-ingest-lib"));
for shared in ["ksp-onchain-transport-lib", "ksp-raw-transaction-lib", "ksp-store-lib"] {
assert!(worker.contains(&shared), "Worker common RAW producer edge missing: {shared}");
assert!(backfill.contains(&shared), "Backfill common RAW producer edge missing: {shared}");
}
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
// version: 8
// version: 31
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
@@ -8,10 +8,23 @@ fn pre_002_manifest_dependency_surface_is_exact() {
let manifest = include_str!("../Cargo.toml");
let dependencies = dependency_section(manifest);
let names = manifest_dependency_names(dependencies);
assert_eq!(names, vec!["ksp-core-lib", "ksp-logging-lib", "ksp-raw-transaction-lib", "ksp-store-lib", "ksp-worker-api", "sha2", "tokio",],);
assert_eq!(
names,
vec![
"ksp-core-lib",
"ksp-logging-lib",
"ksp-onchain-transport-lib",
"ksp-raw-transaction-lib",
"ksp-store-lib",
"ksp-worker-api",
"sha2",
"tokio",
],
);
for required in [
"ksp-core-lib = { path = \"../ksp-core-lib\" }",
"ksp-logging-lib = { path = \"../ksp-logging-lib\" }",
"ksp-onchain-transport-lib = { path = \"../ksp-onchain-transport-lib\" }",
"ksp-raw-transaction-lib = { path = \"../ksp-raw-transaction-lib\" }",
"ksp-store-lib = { path = \"../ksp-store-lib\", default-features = false }",
"ksp-worker-api = { path = \"../ksp-worker-api\" }",
@@ -24,7 +37,7 @@ fn pre_002_manifest_dependency_surface_is_exact() {
}
#[test]
fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() {
fn v0_3_12_pre_002_manifest_opens_only_the_onchain_transport_live_source_edge() {
let manifest = include_str!("../Cargo.toml");
let dependencies = dependency_section(manifest);
for forbidden in [
@@ -33,7 +46,6 @@ fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() {
"ksp-interface-lib",
"ksp-job-api",
"ksp-job-backfill-lib",
"ksp-onchain-transport-lib",
"ksp-program-api",
"ksp-store-api",
"ksp-store-postgres-lib",
@@ -52,12 +64,313 @@ fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() {
}
#[test]
fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_source() {
fn v0_3_12_pre_003_transaction_and_status_adapters_are_private_and_transport_facade_only() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionUpdate",
"RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate",
"YellowstoneTransactionUpdate::transaction(self).signature()",
"YellowstoneTransactionUpdate::transaction(self).index()",
"YellowstoneTransactionStatusUpdate::signature(self)",
"YellowstoneTransactionStatusUpdate::index(self)",
"RawTransactionSignature::new",
"matched_filter_fingerprint",
"yellowstone_provider_unrepresentable",
"yellowstone_endpoint_unrepresentable",
] {
assert!(resources.contains(required), "required pre.003 signal-adapter contract missing: {required}");
}
for forbidden in [
"pub struct RawTransactionIngestSourceSignal",
"pub(crate) struct RawTransactionIngestSourceSignal",
"pub use self::runtime_resources::RawTransactionIngestSourceSignal",
"ksp_store_postgres_lib::",
"reqwest::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.003 crossed a private/offline boundary: {forbidden}");
}
let status_impl =
match resources.split_once("impl RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::convert::From<") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => tail,
},
std::option::Option::None => "",
};
assert!(!status_impl.contains("::error(self)"), "TransactionStatus remote error material must not enter the private signal");
return;
}
#[test]
fn v0_3_12_pre_004_hydration_contract_uses_only_transport_facade_common_raw_and_existing_ingress() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"get_transaction_observed",
"SolanaGetTransactionConfig::new",
"SolanaTransactionEncoding::Base64",
"format_raw_transaction_signature",
"extract_raw_transaction_signature_from_binary_base64",
"RawTransactionMaterial::binary_base64",
"RawTransactionIngress",
"yellowstone_http",
"transaction_get_transaction",
"status_get_transaction",
"RawAcquisitionOrigin::Live",
"composite_provenance_codes",
] {
assert!(resources.contains(required), "required pre.004 hydration contract missing: {required}");
}
assert!(resources.contains("fn finalize_hydration"));
assert!(resources.contains("fn fetch_hydration"));
for forbidden in ["ksp_config_lib::", "ksp_job_backfill_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.004 crossed a forbidden boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_005_block_and_continuity_adapters_remain_private_and_transport_facade_only() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestYellowstoneBlockView",
"YellowstoneBlockUpdate",
"project_yellowstone_block_signals",
"RawTransactionIngestYellowstoneContinuityView",
"YellowstoneBlockMetaUpdate",
"YellowstoneSlotUpdate",
"RawTransactionIngestContinuitySignal",
"block_get_transaction",
] {
assert!(resources.contains(required), "required pre.005 private adapter contract missing: {required}");
}
for forbidden in ["ksp_config_lib::", "ksp_job_backfill_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.005 crossed a forbidden boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission() {
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in ["children.spawn", ".run_live_sources(source_settings, stop_receiver, admission_sender, processing_frontier_sender)"] {
assert!(runtime.contains(required), "required pre.006 supervisor wiring missing: {required}");
}
for required in [
"open_standard_subscribe",
"next_update",
"RawTransactionIngestHydrationCoordinator",
"std::collections::BTreeMap",
"tokio::task::JoinSet",
"RawTransactionIngestHydrationCoordinator::with_global_registry(",
"hydration_pending_limit,",
"hydration_in_flight_limit,",
"get_transaction_observed",
"admission_sender.send(ingress)",
"tasks.abort_all()",
"session.close().await",
"commitment: hydration.commitment.as_str()",
"network: signal.network.clone()",
"signature: signal.signature",
] {
assert!(resources.contains(required), "required pre.006 productive-source contract missing: {required}");
}
for forbidden in [
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tonic::",
"yellowstone_grpc_proto::",
"unbounded_channel",
] {
assert!(!runtime.contains(forbidden) && !resources.contains(forbidden), "pre.006 crossed a forbidden productive-source boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_006_http_block_polling_reuses_transport_facades_without_becoming_backfill() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestHttpBlockPollingSource",
"get_slot(&self.polling_role",
"get_blocks_with_limit(",
"get_block_observed(&self.polling_role",
"let mut next_scan_slot = start_slot",
"next_scan_slot = slot",
"tokio::time::sleep(self.poll_interval)",
"RawAcquisitionOrigin::Live",
"block_polling_get_block",
"SolanaTransactionEncoding::Base64",
"SolanaTransactionDetails::Full",
"std::option::Option::Some(1)",
] {
assert!(resources.contains(required), "required pre.006 HTTP polling contract missing: {required}");
}
let source_impl = match resources.split_once("impl crate::RawTransactionIngestHttpBlockPollingSource {") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::fmt::Debug for crate::RawTransactionIngestHttpBlockPollingSource") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
assert_eq!(source_impl.matches("get_block_observed(").count(), 1);
for forbidden in [
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
"unbounded_channel",
] {
assert!(!source_impl.contains(forbidden) && !root.contains(forbidden), "pre.006 HTTP polling crossed a forbidden boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_003_standard_logs_source_reuses_transport_facades_and_common_hydration_only() {
let root = include_str!("../src/lib.rs");
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestStandardLogsSource",
"SolanaStandardWsSession::connect",
".logs_subscribe(",
"SolanaLogsSubscribeFilter",
"SolanaCommitmentConfig::new",
"project_standard_logs_signal",
"RawTransactionIngestSourceFamily::Logs",
"logs_get_transaction",
"solana_ws_http",
"get_transaction_observed",
"RawTransactionIngestHydrationCoordinator::with_global_registry(",
"hydration_pending_limit,",
"hydration_in_flight_limit,",
"std::option::Option::Some(1)",
] {
assert!(resources.contains(required), "required pre.003 standard logs contract missing: {required}");
}
assert!(runtime.contains("run_live_sources"));
for forbidden in [
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
"unbounded_channel",
] {
assert!(
!runtime.contains(forbidden) && !resources.contains(forbidden) && !root.contains(forbidden),
"pre.003 crossed a forbidden boundary: {forbidden}",
);
}
return;
}
#[test]
fn v0_3_13_pre_004_standard_block_source_is_direct_raw_and_transport_facade_only() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestStandardBlockSource",
"SolanaStandardWsSession::connect",
".block_subscribe(",
"SolanaBlockSubscribeConfig::new",
"SolanaTransactionEncoding::Base64",
"SolanaTransactionDetails::Full",
"std::option::Option::Some(1)",
"std::option::Option::Some(false)",
"build_standard_block_material",
"RawTransactionMaterial::binary_base64_with_embedded_signature",
"RawTransactionVersion::Number(1)",
"block_subscribe",
"source.standard_block_missing",
"source.standard_block_remote_error",
] {
assert!(resources.contains(required), "required pre.004 standard block contract missing: {required}");
}
for forbidden in [
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
"unbounded_channel",
] {
assert!(
!runtime_contains(forbidden) && !resources.contains(forbidden) && !root.contains(forbidden),
"pre.004 crossed a forbidden standard-block boundary: {forbidden}",
);
}
return;
}
#[test]
fn v0_3_13_pre_005_helius_transaction_source_reuses_transport_facade_and_common_hydration_only() {
let root = include_str!("../src/lib.rs");
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestHeliusTransactionSource",
"HeliusLaserStreamWsSession::connect",
".transaction_subscribe(",
"HeliusTransactionSubscribeOptions::new",
"HeliusTransactionSubscribeEncoding::Base64",
"SolanaTransactionDetails::Full",
"std::option::Option::Some(1)",
"project_helius_transaction_signal",
"helius_ws_http",
"get_transaction_observed",
"RawTransactionIngestHydrationCoordinator::with_global_registry(",
"hydration_pending_limit,",
"hydration_in_flight_limit,",
] {
assert!(resources.contains(required), "required pre.005 Helius transaction contract missing: {required}");
}
assert_eq!(resources.matches("get_transaction_observed(").count(), 1);
for forbidden in [
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tokio_tungstenite::",
"helius_sdk",
"unbounded_channel",
] {
assert!(
!runtime.contains(forbidden) && !resources.contains(forbidden) && !root.contains(forbidden),
"pre.005 crossed a forbidden Helius transaction boundary: {forbidden}",
);
}
return;
}
fn runtime_contains(value: &str) -> bool {
return include_str!("../src/runtime.rs").contains(value);
}
#[test]
fn v0_3_12_pre_006_source_surface_hardens_shutdown_and_faults_without_backend_edges() {
let root = include_str!("../src/lib.rs");
let runtime = include_str!("../src/runtime.rs");
let admission = include_str!("../src/admission.rs");
let persistence = include_str!("../src/persistence.rs");
let snapshot = include_str!("../src/snapshot.rs");
let runtime_resources = include_str!("../src/runtime_resources.rs");
for required in [
"tokio::sync::mpsc::channel",
"canonicalize_raw_transaction",
@@ -81,7 +394,8 @@ fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_so
|| runtime.contains(required)
|| admission.contains(required)
|| persistence.contains(required)
|| snapshot.contains(required),
|| snapshot.contains(required)
|| runtime_resources.contains(required),
"required pre.009 hardening contract missing: {required}"
);
}
@@ -91,7 +405,6 @@ fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_so
"pub use self::persistence::RawTransactionIngestPersistencePort",
"unbounded_channel",
"ksp_store_postgres_lib::",
"ksp_onchain_transport_lib::",
"ForceRehydrate",
"ksp_config_lib::",
"reqwest::",
@@ -101,11 +414,22 @@ fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_so
&& !runtime.contains(forbidden)
&& !admission.contains(forbidden)
&& !persistence.contains(forbidden)
&& !snapshot.contains(forbidden),
&& !snapshot.contains(forbidden)
&& !runtime_resources.contains(forbidden),
"pre.009 crossed a forbidden runtime boundary: {forbidden}"
);
}
assert_eq!(runtime.matches("tokio::sync::watch::channel").count(), 2, "runtime retains only the external-control and private-source stop watches");
assert_eq!(
runtime.matches("tokio::sync::watch::channel(false)").count(),
2,
"runtime retains exactly the external-control and private-source stop watches"
);
assert_eq!(
runtime.matches("tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty())").count(),
1,
"runtime retains exactly one run-local processing-frontier latest-value watch",
);
assert_eq!(runtime.matches("tokio::sync::watch::channel").count(), 3, "runtime watch inventory is two stop watches plus one processing-frontier watch");
assert_eq!(snapshot.matches("tokio::sync::watch::channel").count(), 1, "exactly one shared concrete snapshot watch is allowed");
return;
}
@@ -145,3 +469,325 @@ fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
names.sort_unstable();
return names;
}
#[test]
fn v0_3_13_pre_007_multi_source_supervisor_and_inventory_remain_private_bounded_and_source_neutral() {
let root = include_str!("../src/lib.rs");
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestSourceInventory",
"RawTransactionIngestSourceInventoryPublisher",
"for (entry_index, source) in self.sources.into_iter().enumerate()",
"tokio::task::JoinSet::new()",
"supervise_live_source_tasks",
"drain_live_source_tasks",
"source_stop_sender.send_replace(true)",
"processing_frontier_sender.clone()",
"source.configured_source_closed",
] {
assert!(resources.contains(required), "required pre.007 multi-source supervisor contract missing: {required}");
}
assert!(runtime.contains("run_live_sources"));
assert!(!runtime.contains("validate_single_source_activation"));
assert!(!resources.contains("multi_source_activation_pending"));
for forbidden in [
"pub struct RawTransactionIngestSourceInventory",
"pub struct RawTransactionIngestSourceInventoryPublisher",
"unbounded_channel",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(
!root.contains(forbidden) && !runtime.contains(forbidden) && !resources.contains(forbidden),
"pre.007 crossed a private/bounded boundary: {forbidden}"
);
}
return;
}
#[test]
fn v0_3_12_pre_007_processing_frontier_remains_run_local_and_backend_neutral_after_continuity_extension() {
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
let snapshot = include_str!("../src/snapshot.rs");
for required in [
"RawTransactionIngestProcessingFrontierProjection",
"processing_frontier_sender",
"wait_processing_frontier",
"record_processing_frontier",
"hydration_pending",
"processing_frontier_slot",
"oldest_pending_slot",
"RawTransactionIngestProcessingFrontier",
"settle_pending",
"observe_settled",
] {
assert!(
runtime.contains(required) || resources.contains(required) || snapshot.contains(required),
"required pre.007 frontier contract missing: {required}"
);
}
let frontier = match resources.split_once("struct RawTransactionIngestProcessingFrontier {") {
std::option::Option::Some((_, tail)) => match tail.split_once("struct RawTransactionIngestProcessingFrontierReporter") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
for forbidden in ["SubscribeReplayInfo", "from_slot", "repair", "ksp_job_backfill_lib::", "ksp_store_postgres_lib::"] {
assert!(!frontier.contains(forbidden), "pre.007 processing frontier absorbed replay/backend responsibility: {forbidden}");
}
for forbidden in ["ksp_job_backfill_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
assert!(
!runtime.contains(forbidden) && !resources.contains(forbidden) && !snapshot.contains(forbidden),
"Worker crossed backend boundary: {forbidden}"
);
}
return;
}
#[test]
fn v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on_proven_retention_gap() {
let resources = include_str!("../src/runtime_resources.rs");
let transport = include_str!("../../ksp-onchain-transport-lib/src/grpc_stream.rs");
for required in [
"snapshot_source()",
"wait_yellowstone_session_snapshot",
"source_reconnect_total",
"source_replay_attempt_total",
"source_continuity_gap_total",
"source.continuity_gap_proven",
"RawTransactionIngestSourceState::Reconnecting",
] {
assert!(resources.contains(required), "required pre.008 Worker continuity token missing: {required}");
}
for forbidden in ["subscribe_replay_info(", "set_from_slot(", "YellowstoneReplayInfo", "last_requested_from_slot()", "ksp_job_backfill_lib::"] {
assert!(!resources.contains(forbidden), "Worker took ownership of replay/repair responsibility: {forbidden}");
}
for required in ["SubscribeReplayInfo", "set_from_slot(effective_from_slot)", "first_available > requested", "replay_attempt_count"] {
assert!(transport.contains(required), "Transport replay ownership token missing: {required}");
}
return;
}
#[test]
fn v0_3_13_pre_008_cross_source_convergence_reuses_store_and_transport_facades_only() {
let root = include_str!("../src/lib.rs");
let persistence = include_str!("../src/persistence.rs");
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestPersistenceConvergence",
"persist_raw_transaction_ingest_converged_acquisition",
"RawTransactionIngestPersistencePort",
"record_raw_transaction_observation",
"RawTransactionObservationWrite",
"payload().content_hash()",
"content_conflict_error()",
] {
assert!(persistence.contains(required) || root.contains(required), "required pre.008 persistence convergence contract missing: {required}");
}
for required in [
"RawTransactionIngestGlobalHydrationRegistry",
"fetch_hydration_shared",
"subscribe_or_lead",
"get_transaction_observed",
"settings.admission_queue_capacity()",
] {
assert!(resources.contains(required), "required pre.008 hydration convergence contract missing: {required}");
}
assert!(runtime.contains("settings.admission_queue_capacity().max(settings.persistence_concurrency())"));
for forbidden in ["ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::", "ksp_job_backfill_lib::", "unbounded_channel"] {
assert!(
!persistence.contains(forbidden) && !runtime.contains(forbidden) && !resources.contains(forbidden),
"pre.008 convergence crossed a dependency/boundedness boundary: {forbidden}"
);
}
return;
}
#[test]
fn v0_3_13_pre_009_global_bounds_and_fairness_stay_inside_worker_facades() {
let admission = include_str!("../src/admission.rs");
let persistence = include_str!("../src/persistence.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"validate_hydration_fairness_capacity",
"let hydration_pending_budget = settings.admission_queue_capacity();",
"let hydration_in_flight_budget = settings.persistence_concurrency();",
"hydration_pending_limit",
"hydration_in_flight_limit",
"tokio::sync::Semaphore",
"acquire_owned",
"RawTransactionIngestHydrationLeaderGuard",
"RawTransactionIngestCanonicalState",
"content_conflict_error()",
] {
assert!(
admission.contains(required) || persistence.contains(required) || resources.contains(required),
"required pre.009 global-bound contract missing: {required}"
);
}
for forbidden in [
"unbounded_channel",
"ksp_store_postgres_lib::",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(
!admission.contains(forbidden) && !persistence.contains(forbidden) && !resources.contains(forbidden),
"pre.009 crossed a Worker facade boundary: {forbidden}"
);
}
return;
}
#[test]
fn v0_3_13_pre_010_multi_source_snapshot_health_remains_source_neutral_and_backend_free() {
let snapshot = include_str!("../src/snapshot.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in ["source_total", "source_active", "source_reconnecting", "source_failed", "WorkerHealth::Degraded", "with_source_counts"] {
assert!(snapshot.contains(required) || resources.contains(required), "required pre.010 source-neutral snapshot contract missing: {required}");
}
for forbidden in ["ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::", "SourceSnapshotByKey", "provider_health"] {
assert!(!snapshot.contains(forbidden), "pre.010 snapshot crossed source-neutral/backend boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_011_shutdown_race_hardening_stays_inside_worker_and_existing_facades() {
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"tokio::time::timeout(settings.shutdown_drain_timeout(), drain).await",
"persistence.abort_all()",
"children.abort_all()",
"while persistence.join_next().await.is_some() {}",
"while children.join_next().await.is_some() {}",
"ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED",
] {
assert!(runtime.contains(required), "required pre.011 runtime shutdown guard missing: {required}");
}
for required in [
"RawTransactionIngestHydrationLeaderGuard",
"publish_and_remove",
"pending.remove(key)",
"source.inventory_key_mismatch",
"checked_add(projection.hydration_pending())",
"checked_add(projection.source_reconnect_total())",
] {
assert!(resources.contains(required), "required pre.011 source-race guard missing: {required}");
}
for forbidden in [
"unbounded_channel",
"ksp_store_postgres_lib::",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(!runtime.contains(forbidden) && !resources.contains(forbidden), "pre.011 crossed a Worker/facade boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_012_cross_layer_closure_does_not_expand_worker_dependency_graph() {
let manifest = include_str!("../Cargo.toml");
let dependencies = dependency_section(manifest);
let names = manifest_dependency_names(dependencies);
assert_eq!(
names,
vec![
"ksp-core-lib",
"ksp-logging-lib",
"ksp-onchain-transport-lib",
"ksp-raw-transaction-lib",
"ksp-store-lib",
"ksp-worker-api",
"sha2",
"tokio",
],
);
let cross_layer = include_str!("cross_layer_completeness.rs");
for required in [
"v0_3_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline",
"v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store",
"v0_3_13_pre_012_yellowstone_non_regression_covers_identity_v1_reconnect_gap_and_worker_hydration",
"v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_layers",
] {
assert!(cross_layer.contains(required), "pre.012 cross-layer closure proof missing: {required}");
}
return;
}
#[test]
fn v0_3_14_pre_012_shutdown_hardening_stays_worker_local_and_facade_only() {
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"tokio::time::timeout(settings.shutdown_drain_timeout(), drain).await",
"tokio::time::timeout(shutdown_drain_timeout, drain).await",
"children.abort_all()",
"persistence.abort_all()",
] {
assert!(runtime.contains(required) || resources.contains(required), "required pre.012 bounded shutdown guard missing: {required}");
}
for forbidden in [
"ksp_store_postgres_lib::",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(!runtime.contains(forbidden) && !resources.contains(forbidden), "pre.012 added forbidden Worker dependency edge: {forbidden}");
}
return;
}
#[test]
fn v0_3_14_pre_013_cross_layer_closure_keeps_dependency_and_producer_boundaries_exact() {
let manifest = include_str!("../Cargo.toml");
let dependencies = dependency_section(manifest);
let names = manifest_dependency_names(dependencies);
assert_eq!(
names,
vec![
"ksp-core-lib",
"ksp-logging-lib",
"ksp-onchain-transport-lib",
"ksp-raw-transaction-lib",
"ksp-store-lib",
"ksp-worker-api",
"sha2",
"tokio",
],
);
let cross_layer = include_str!("cross_layer_completeness.rs");
for required in [
"v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline",
"v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths",
"v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening",
"v0_3_14_pre_013_worker_and_backfill_remain_independent_producers",
] {
assert!(cross_layer.contains(required), "pre.013 cross-layer closure proof missing: {required}");
}
for forbidden in ["ksp-config-lib", "ksp-job-api", "ksp-job-backfill-lib", "ksp-store-api", "ksp-store-postgres-lib"] {
assert!(!names.contains(&forbidden), "pre.013 Worker dependency boundary expanded: {forbidden}");
}
return;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
// version: 8
// version: 25
//! External public-surface proofs for the RAW transaction ingest Worker foundation.
@@ -122,6 +122,18 @@ fn pre_008_snapshot_surface_and_common_projection_are_public_and_stable() {
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshotSource::wait_for_change;
fn require_worker_source<T: ksp_worker_api::WorkerSnapshotSource + std::marker::Send + std::marker::Sync>() {}
require_worker_source::<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshotSource>();
let _state_type = std::any::type_name::<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState>();
let _states = [
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Active,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Reconnecting,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Closing,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Closed,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Failed,
];
let _source_state = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_state;
let _reconnect = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_reconnect_total;
let _replay = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_replay_attempt_total;
let _gap = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_continuity_gap_total;
assert_eq!(ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED.domain(), "worker_raw_transaction_ingest");
assert_eq!(ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED.code(), "counter_exhausted");
return;
@@ -135,3 +147,368 @@ fn pre_009_source_and_drain_timeout_error_codes_are_public_and_stable() {
assert_eq!(ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED.code(), "source_failed");
return;
}
#[test]
fn v0_3_12_pre_002_runtime_resource_types_are_consumable_without_client_escape_hatch() {
let _source_new: fn(
ksp_onchain_transport_lib::YellowstoneGrpcChannel,
ksp_onchain_transport_lib::YellowstoneSubscribeRequest,
ksp_onchain_transport_lib::HttpTransportPool,
ksp_onchain_transport_lib::HttpRoleName,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestYellowstoneSource> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestYellowstoneSource::new;
let _resources_new: fn(
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestYellowstoneSource,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::new;
let _start_with_resources: fn(
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings,
std::sync::Arc<ksp_store_lib::Store>,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start_with_runtime_resources;
let source = include_str!("../src/runtime_resources.rs");
for forbidden in [
"pub fn channel(",
"pub fn http_pool(",
"pub fn subscribe_request(",
"pub fn hydration_role(",
"pub fn enqueue(",
"pub fn send(",
"pub fn inner(",
] {
assert!(!source.contains(forbidden), "runtime-resource implementation escape hatch present: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_002_multi_source_runtime_resource_surface_is_bounded_and_source_neutral() {
let _source_count: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::source_count;
let _push_yellowstone: fn(
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestYellowstoneSource,
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_yellowstone_source;
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES, 32);
let root = include_str!("../src/lib.rs");
assert!(!root.contains("RawTransactionIngestLiveSource"));
assert!(!root.contains("source_key"));
return;
}
#[test]
fn v0_3_13_pre_003_standard_logs_runtime_resource_surface_is_typed_and_transport_owned() {
let _source_new: fn(
ksp_onchain_transport_lib::WsEndpointSettings,
ksp_onchain_transport_lib::SolanaLogsSubscribeFilter,
ksp_onchain_transport_lib::SolanaCommitment,
ksp_onchain_transport_lib::HttpTransportPool,
ksp_onchain_transport_lib::HttpRoleName,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource::new;
let _resources_from_logs: fn(
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_logs_source;
let _push_logs: fn(
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource,
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_standard_logs_source;
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
assert!(root.contains("RawTransactionIngestStandardLogsSource"));
for forbidden in ["pub fn ws_endpoint(", "pub fn filter(", "pub fn http_pool(", "pub fn source_key("] {
assert!(!resources.contains(forbidden), "standard logs source implementation escape hatch present: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_005_helius_transaction_runtime_resource_surface_is_typed_and_transport_owned() {
let _source_new: fn(
ksp_onchain_transport_lib::WsEndpointSettings,
ksp_onchain_transport_lib::HeliusTransactionSubscribeFilter,
ksp_onchain_transport_lib::SolanaCommitment,
ksp_onchain_transport_lib::HttpTransportPool,
ksp_onchain_transport_lib::HttpRoleName,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource::new;
let _resources_from_helius: fn(
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_helius_transaction_source;
let _push_helius: fn(
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource,
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_helius_transaction_source;
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
assert!(root.contains("RawTransactionIngestHeliusTransactionSource"));
for forbidden in ["pub fn ws_endpoint(", "pub fn filter(", "pub fn http_pool(", "pub fn source_key("] {
assert!(!resources.contains(forbidden), "Helius transaction source implementation escape hatch present: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_004_standard_block_runtime_resource_surface_is_typed_and_transport_owned() {
let _source_new: fn(
ksp_onchain_transport_lib::WsEndpointSettings,
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource::new;
let _resources_from_block: fn(
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source;
let _push_block: fn(
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource,
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_standard_block_source;
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
assert!(root.contains("RawTransactionIngestStandardBlockSource"));
for forbidden in ["pub fn ws_endpoint(", "pub fn filter(", "pub fn source_key("] {
assert!(!resources.contains(forbidden), "standard block source implementation escape hatch present: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_006_http_block_polling_runtime_resource_surface_is_bounded_and_transport_owned() {
let _source_new: fn(
ksp_onchain_transport_lib::HttpTransportPool,
ksp_onchain_transport_lib::HttpRoleName,
ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource::new;
let _source_new_with_limits: fn(
ksp_onchain_transport_lib::HttpTransportPool,
ksp_onchain_transport_lib::HttpRoleName,
ksp_onchain_transport_lib::SolanaCommitment,
std::time::Duration,
u16,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource::new_with_limits;
let _resources_from_polling: fn(
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_http_block_polling_source;
let _push_polling: fn(
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource,
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_http_block_polling_source;
assert_eq!(ksp_worker_raw_transaction_ingest_lib::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL, std::time::Duration::from_secs(1));
assert_eq!(ksp_worker_raw_transaction_ingest_lib::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE, 128);
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL, std::time::Duration::from_millis(100));
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL, std::time::Duration::from_secs(30));
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE, 1);
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE, 1024);
let resources = include_str!("../src/runtime_resources.rs");
for forbidden in ["pub fn http_pool(", "pub fn polling_role(", "pub fn source_key(", "pub fn profile_fingerprint("] {
assert!(!resources.contains(forbidden), "HTTP polling source implementation escape hatch present: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only() {
let _hydration_pending: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::hydration_pending;
let _processing_frontier_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> std::option::Option<u64> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::processing_frontier_slot;
let _oldest_pending_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> std::option::Option<u64> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::oldest_pending_slot;
let root = include_str!("../src/lib.rs");
for forbidden in ["ReplayInfo", "from_slot", "repair", "backfill"] {
assert!(!root.contains(forbidden), "pre.007 public root overclaims continuity/replay: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_007_source_inventory_and_logical_keys_remain_private() {
let root = include_str!("../src/lib.rs");
for forbidden in ["RawTransactionIngestSourceInventory", "RawTransactionIngestSourceInventoryPublisher", "RawTransactionIngestLiveSource", "source_key"] {
assert!(!root.contains(forbidden), "pre.007 private multi-source implementation leaked through crate root: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_008_convergence_cache_registry_and_source_keys_remain_private() {
let root = include_str!("../src/lib.rs");
for forbidden in [
"pub use self::persistence::RawTransactionIngestPersistenceConvergence",
"pub use self::persistence::persist_raw_transaction_ingest_converged_acquisition",
"RawTransactionIngestGlobalHydrationRegistry",
"RawTransactionIngestHydrationKey",
"source_key",
] {
assert!(!root.contains(forbidden), "pre.008 convergence implementation leaked through public crate root: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_009_fairness_budgets_semaphore_and_canonical_state_remain_private() {
let root = include_str!("../src/lib.rs");
for private_name in [
"RawTransactionIngestCanonicalState",
"RawTransactionIngestGlobalHydrationRegistry",
"RawTransactionIngestHydrationLeaderGuard",
"validate_hydration_fairness_capacity",
"hydration_pending_limit",
"hydration_in_flight_limit",
] {
assert!(!root.contains(&std::format!("pub use self::runtime_resources::{private_name}")));
assert!(!root.contains(&std::format!("pub use self::persistence::{private_name}")));
}
return;
}
#[test]
fn v0_3_13_pre_010_multi_source_snapshot_counts_are_public_and_source_neutral() {
let _source_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_total;
let _source_active: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_active;
let _source_reconnecting: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_reconnecting;
let _source_failed: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_failed;
let root = include_str!("../src/lib.rs");
for forbidden in ["SourceHealthByKey", "SourceSnapshotByKey", "provider_health", "endpoint_health"] {
assert!(!root.contains(forbidden), "pre.010 public root leaked source-specific health material: {forbidden}");
}
return;
}
#[test]
fn v0_3_13_pre_011_race_hardening_adds_no_public_runtime_or_source_identity_surface() {
let root = include_str!("../src/lib.rs");
for forbidden in [
"pub use self::runtime_resources::RawTransactionIngestSourceInventory",
"pub use self::runtime_resources::RawTransactionIngestSourceInventoryPublisher",
"pub use self::runtime_resources::RawTransactionIngestGlobalHydrationRegistry",
"pub use self::runtime_resources::RawTransactionIngestHydrationLeaderGuard",
"publish_and_remove",
"source.inventory_key_mismatch",
] {
assert!(!root.contains(forbidden), "pre.011 private race-hardening implementation leaked publicly: {forbidden}");
}
assert_eq!(
ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED,
ksp_core_lib::ErrorCode::new("worker_raw_transaction_ingest", "counter_exhausted")
);
assert_eq!(
ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT,
ksp_core_lib::ErrorCode::new("worker_raw_transaction_ingest", "drain_timeout")
);
return;
}
#[test]
fn v0_3_13_pre_012_completeness_closure_adds_no_public_implementation_surface() {
let root = include_str!("../src/lib.rs");
assert!(!root.contains("pub mod "));
for forbidden in [
"pub use self::runtime_resources::RawTransactionIngestLiveSource",
"pub use self::runtime_resources::RawTransactionIngestSourceInventory",
"pub use self::runtime_resources::RawTransactionIngestGlobalHydrationRegistry",
"pub use self::persistence::RawTransactionIngestCanonicalState",
"pub use self::persistence::RawTransactionIngestPersistenceConvergence",
] {
assert!(!root.contains(forbidden), "pre.012 private implementation leaked through public root: {forbidden}");
}
let cross_layer = include_str!("cross_layer_completeness.rs");
assert!(cross_layer.contains("v0_3_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline"));
assert!(cross_layer.contains("v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store"));
return;
}
#[test]
fn v0_3_14_pre_011_gap_observability_is_public_bounded_and_source_neutral() {
let _gap_id_value: fn(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapId) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapId::value;
let _gap_id: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapId = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::gap_id;
let _start_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::start_slot;
let _end_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::end_slot;
let _state: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::state;
let _reason: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::reason;
let _last_method: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> std::option::Option<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::last_method;
let _gaps: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot,
) -> &[ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot] = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::gaps;
let _open_gap_count: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::open_gap_count;
let _repairing_gap_count: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repairing_gap_count;
let _repaired_gap_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repaired_gap_total;
let _unresolved_gap_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::unresolved_gap_total;
let _replay_repair_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::replay_repair_total;
let _redundant_coverage_repair_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::redundant_coverage_repair_total;
let _http_scan_repair_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::http_scan_repair_total;
let _repair_block_fetch_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repair_block_fetch_total;
let _repair_transaction_hydration_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repair_transaction_hydration_total;
let _oldest_open_gap_start_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> std::option::Option<u64> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::oldest_open_gap_start_slot;
let root = include_str!("../src/lib.rs");
for forbidden in ["SourceGapByKey", "provider_gap", "endpoint_gap", "signature_gap", "payload_gap", "source_key"] {
assert!(!root.contains(forbidden), "pre.011 public root leaked source-specific gap material: {forbidden}");
}
return;
}
#[test]
fn v0_3_14_pre_013_completeness_closure_adds_no_public_surface() {
let root = include_str!("../src/lib.rs");
let public_exports = root.lines().filter(|line| return line.trim().starts_with("pub use ")).count();
assert_eq!(public_exports, 43);
for required in [
"RawTransactionIngestGapId",
"RawTransactionIngestGapReason",
"RawTransactionIngestGapSnapshot",
"RawTransactionIngestGapState",
"RawTransactionIngestRepairMethod",
"RawTransactionIngestSnapshot",
"RawTransactionIngestWorker",
] {
assert!(root.contains(required), "pre.013 expected stable public Worker surface missing: {required}");
}
for forbidden in [
"RawTransactionIngestContinuityContracts",
"RawTransactionIngestGlobalHydrationRegistry",
"RawTransactionIngestSourceInventory",
"RawTransactionIngestTrafficClass",
"RawTransactionIngestFairTurnGate",
"RawTransactionIngestSourceLossDecision",
] {
assert!(
!root.lines().any(|line| return line.trim().starts_with("pub use ") && line.contains(forbidden)),
"pre.013 private type leaked publicly: {forbidden}"
);
}
assert!(!root.contains("pub mod "));
return;
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
// version: 1
// version: 32
//! Release-completeness canaries through the `pre.010` public/release/security hardening tranche.
//! Release-completeness canaries through the `v0.3.14-pre.013` cross-layer closure tranche.
#[test]
fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> {
@@ -32,12 +32,26 @@ fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> {
}
}
names.sort_unstable();
assert_eq!(names, std::vec!["admission.rs", "error.rs", "identity.rs", "lib.rs", "persistence.rs", "runtime.rs", "settings.rs", "snapshot.rs",]);
assert_eq!(
names,
std::vec![
"admission.rs",
"continuity.rs",
"error.rs",
"identity.rs",
"lib.rs",
"persistence.rs",
"runtime.rs",
"runtime_resources.rs",
"settings.rs",
"snapshot.rs",
]
);
return std::result::Result::Ok(());
}
#[test]
fn pre_010_public_root_export_inventory_is_exact() {
fn v0_3_14_pre_011_public_root_export_inventory_is_exact() {
let root = include_str!("../src/lib.rs");
let mut exports = std::vec::Vec::new();
for line in root.lines() {
@@ -57,6 +71,8 @@ fn pre_010_public_root_export_inventory_is_exact() {
exports,
std::vec![
"DEFAULT_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
"DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
"DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
"DEFAULT_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
"DEFAULT_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
"ERROR_CODE_RAW_TRANSACTION_INGEST_CONTENT_CONFLICT",
@@ -67,19 +83,36 @@ fn pre_010_public_root_export_inventory_is_exact() {
"ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED",
"ERROR_CODE_RAW_TRANSACTION_INGEST_STORE_FAILED",
"MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
"MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
"MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
"MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES",
"MAX_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
"MAX_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
"MIN_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
"MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
"MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
"MIN_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
"MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
"RAW_TRANSACTION_INGEST_WORKER_KIND_CODE",
"RawTransactionIngestGapId",
"RawTransactionIngestGapReason",
"RawTransactionIngestGapSnapshot",
"RawTransactionIngestGapState",
"RawTransactionIngestHandle",
"RawTransactionIngestHeliusTransactionSource",
"RawTransactionIngestHttpBlockPollingSource",
"RawTransactionIngestRepairMethod",
"RawTransactionIngestRuntimeResources",
"RawTransactionIngestSettings",
"RawTransactionIngestSnapshot",
"RawTransactionIngestSnapshotFuture",
"RawTransactionIngestSnapshotSource",
"RawTransactionIngestSourceState",
"RawTransactionIngestStandardBlockSource",
"RawTransactionIngestStandardLogsSource",
"RawTransactionIngestTerminalFuture",
"RawTransactionIngestWorker",
"RawTransactionIngestYellowstoneSource",
]
);
assert!(!root.contains("pub mod "));
@@ -92,22 +125,341 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
for required in [
"pre_010_external_error_codes_are_stable_unique_and_domain_scoped",
"pre_010_debug_and_settings_errors_redact_worker_identity_and_invalid_values",
"pre_010_manifest_dependency_surface_remains_exact_source_neutral_and_backend_neutral",
"v0_3_12_pre_002_manifest_dependency_surface_opens_only_transport_and_remains_backend_neutral",
"v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload",
"v0_3_12_pre_004_hydration_provenance_and_remote_material_are_bounded_and_redacted",
"v0_3_12_pre_005_block_and_continuity_shapes_are_bounded_redacted_and_raw_separated",
"pre_010_source_visibility_contract_uses_crate_root_for_shared_items",
"pre_010_production_surface_has_no_historical_backfill_or_retriever_contract",
"pre_010_production_sources_scan_clean_for_config_secrets_backend_and_transport",
"v0_3_12_pre_002_production_sources_keep_transport_confined_to_runtime_resources",
"v0_3_12_pre_006_runtime_resource_contract_opens_one_supervised_transport_source",
"v0_3_12_pre_006_source_coalescence_is_bounded_stop_preemptible_and_redacted",
"v0_3_12_pre_007_processing_frontier_is_bounded_processing_only_and_redacted",
"v0_3_12_pre_008_reconnect_projection_is_source_neutral_bounded_and_contains_no_replay_material",
"v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit",
"pre_010_lower_layers_have_no_dependency_return_to_concrete_worker",
"pre_010_public_root_exposes_no_runtime_backend_or_live_source_implementation_types",
"v0_3_12_pre_002_public_root_exposes_contract_types_without_transport_implementation_paths",
"v0_3_13_pre_003_standard_logs_redaction_and_reference_only_contract_are_explicit",
"v0_3_13_pre_004_standard_block_redaction_version_and_null_guards_are_explicit",
"v0_3_13_pre_005_helius_transaction_redaction_full_reference_and_tier_neutrality_are_explicit",
"v0_3_13_pre_006_http_block_polling_is_bounded_run_local_stop_preemptible_and_redacted",
"v0_3_13_pre_007_multi_source_supervisor_is_fail_closed_joined_and_does_not_publish_source_identity",
"v0_3_13_pre_008_cross_source_convergence_is_bounded_conflict_checked_and_private",
"v0_3_13_pre_009_duplicate_storm_disagreement_and_starvation_guards_are_explicit",
"v0_3_13_pre_010_multi_source_health_is_conservative_counted_and_redacted",
"v0_3_13_pre_011_shutdown_races_are_bounded_joined_atomic_and_counter_safe",
"v0_3_14_pre_002_continuity_contracts_are_private_bounded_and_io_free",
"v0_3_14_pre_003_websocket_continuity_observation_reuses_transport_snapshot_sources_only",
] {
assert!(hardening.contains(required), "required pre.010 hardening canary missing: {required}");
}
let dependency_boundary = include_str!("dependency_boundary.rs");
assert!(dependency_boundary.contains("pre_002_manifest_dependency_surface_is_exact"));
assert!(dependency_boundary.contains("pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_source"));
assert!(dependency_boundary.contains("v0_3_12_pre_006_source_surface_hardens_shutdown_and_faults_without_backend_edges"));
assert!(dependency_boundary.contains("v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission"));
assert!(dependency_boundary.contains("v0_3_12_pre_002_manifest_opens_only_the_onchain_transport_live_source_edge"));
assert!(dependency_boundary.contains("v0_3_12_pre_003_transaction_and_status_adapters_are_private_and_transport_facade_only"));
assert!(dependency_boundary.contains("v0_3_12_pre_004_hydration_contract_uses_only_transport_facade_common_raw_and_existing_ingress"));
assert!(dependency_boundary.contains("v0_3_12_pre_005_block_and_continuity_adapters_remain_private_and_transport_facade_only"));
assert!(dependency_boundary.contains("v0_3_12_pre_007_processing_frontier_remains_run_local_and_backend_neutral_after_continuity_extension"));
assert!(dependency_boundary.contains("v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on_proven_retention_gap"));
assert!(dependency_boundary.contains("v0_3_13_pre_003_standard_logs_source_reuses_transport_facades_and_common_hydration_only"));
assert!(dependency_boundary.contains("v0_3_13_pre_004_standard_block_source_is_direct_raw_and_transport_facade_only"));
assert!(dependency_boundary.contains("v0_3_13_pre_005_helius_transaction_source_reuses_transport_facade_and_common_hydration_only"));
assert!(dependency_boundary.contains("v0_3_13_pre_006_http_block_polling_reuses_transport_facades_without_becoming_backfill"));
assert!(dependency_boundary.contains("v0_3_13_pre_007_multi_source_supervisor_and_inventory_remain_private_bounded_and_source_neutral"));
assert!(dependency_boundary.contains("v0_3_13_pre_008_cross_source_convergence_reuses_store_and_transport_facades_only"));
assert!(dependency_boundary.contains("v0_3_13_pre_009_global_bounds_and_fairness_stay_inside_worker_facades"));
assert!(dependency_boundary.contains("v0_3_13_pre_010_multi_source_snapshot_health_remains_source_neutral_and_backend_free"));
assert!(dependency_boundary.contains("v0_3_13_pre_011_shutdown_race_hardening_stays_inside_worker_and_existing_facades"));
let public_api = include_str!("public_api.rs");
assert!(public_api.contains("pre_003_kind_code_and_settings_are_consumable_from_crate_root"));
assert!(public_api.contains("pre_004_start_handle_and_terminal_future_are_consumable_without_public_join_handle"));
assert!(public_api.contains("pre_008_snapshot_surface_and_common_projection_are_public_and_stable"));
assert!(public_api.contains("pre_009_source_and_drain_timeout_error_codes_are_public_and_stable"));
assert!(public_api.contains("v0_3_12_pre_002_runtime_resource_types_are_consumable_without_client_escape_hatch"));
assert!(public_api.contains("v0_3_13_pre_002_multi_source_runtime_resource_surface_is_bounded_and_source_neutral"));
assert!(public_api.contains("v0_3_13_pre_003_standard_logs_runtime_resource_surface_is_typed_and_transport_owned"));
assert!(public_api.contains("v0_3_13_pre_004_standard_block_runtime_resource_surface_is_typed_and_transport_owned"));
assert!(public_api.contains("v0_3_13_pre_006_http_block_polling_runtime_resource_surface_is_bounded_and_transport_owned"));
assert!(public_api.contains("v0_3_13_pre_007_source_inventory_and_logical_keys_remain_private"));
assert!(public_api.contains("v0_3_13_pre_008_convergence_cache_registry_and_source_keys_remain_private"));
assert!(public_api.contains("v0_3_13_pre_009_fairness_budgets_semaphore_and_canonical_state_remain_private"));
assert!(public_api.contains("v0_3_13_pre_010_multi_source_snapshot_counts_are_public_and_source_neutral"));
assert!(public_api.contains("v0_3_13_pre_011_race_hardening_adds_no_public_runtime_or_source_identity_surface"));
assert!(public_api.contains("v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only"));
assert!(public_api.contains("pre_008_snapshot_surface_and_common_projection_are_public_and_stable"));
return;
}
#[test]
fn v0_3_12_pre_010_cross_layer_completeness_suite_is_present_and_exact() -> std::io::Result<()> {
let test_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
let entries = match std::fs::read_dir(test_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!["cross_layer_completeness.rs", "dependency_boundary.rs", "hardening.rs", "public_api.rs", "release_completeness.rs"]);
let cross_layer = include_str!("cross_layer_completeness.rs");
for required in [
"v0_3_12_pre_010_dependency_firewall_is_exact_across_transport_worker_common_raw_store",
"v0_3_12_pre_010_legacy_v0_fixture_matrix_is_exact_across_transport_worker_and_common_raw",
"v0_3_12_pre_010_redaction_scanner_matrix_is_present_across_all_layers",
"v0_3_12_pre_010_public_roots_keep_implementation_modules_private",
] {
assert!(cross_layer.contains(required), "required pre.010 cross-layer canary missing: {required}");
}
return std::result::Result::Ok(());
}
#[test]
fn v0_3_13_pre_012_cross_layer_completeness_security_inventory_is_exact() {
let cross_layer = include_str!("cross_layer_completeness.rs");
for required in [
"v0_3_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline",
"v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store",
"v0_3_13_pre_012_yellowstone_non_regression_covers_identity_v1_reconnect_gap_and_worker_hydration",
"v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_layers",
] {
assert!(cross_layer.contains(required), "required pre.012 cross-layer canary missing: {required}");
}
let dependency_boundary = include_str!("dependency_boundary.rs");
let hardening = include_str!("hardening.rs");
let public_api = include_str!("public_api.rs");
assert!(dependency_boundary.contains("v0_3_13_pre_012_cross_layer_closure_does_not_expand_worker_dependency_graph"));
assert!(hardening.contains("v0_3_13_pre_012_cross_layer_security_closure_is_complete_and_source_neutral"));
assert!(public_api.contains("v0_3_13_pre_012_completeness_closure_adds_no_public_implementation_surface"));
return;
}
#[test]
fn v0_3_14_pre_004_native_replay_evidence_canaries_are_present_without_public_replay_ownership() {
let hardening = include_str!("hardening.rs");
let resources = include_str!("../src/runtime_resources.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
assert!(hardening.contains("v0_3_14_pre_004_yellowstone_replay_evidence_is_transport_owned_and_fail_closed_without_coverage_proof"));
assert!(resource_tests.contains("v0_3_14_pre_004_replay_delivery_is_not_coverage_and_unproven_coverage_fails_closed"));
assert!(resources.contains("source.replay_coverage_unproven"));
assert!(!root.contains("ReplayInfo"));
assert!(!root.contains("from_slot"));
assert!(!root.contains("set_from_slot"));
return;
}
#[test]
fn v0_3_14_pre_005_redundant_coverage_canaries_are_present_without_public_surface_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_005_scope_relations_are_exact_superset_and_cross_family_conservative",
"pre_005_coverage_epoch_requires_distinct_source_same_commitment_and_full_range",
"pre_005_cross_family_exact_scopes_never_become_redundant_by_fingerprint_alone",
"pre_005_coverage_epoch_ledger_is_bounded_monotone_and_capability_bound",
] {
assert!(continuity_tests.contains(required), "required pre.005 continuity proof canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_005_redundant_coverage_requires_exact_or_superset_proof_over_full_range"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestCoverageEpoch"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestCoverageRelation"));
return;
}
#[test]
fn v0_3_14_pre_006_bounded_http_discovery_canaries_are_present_without_backfill_or_public_surface_growth() {
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"v0_3_14_pre_006_http_scan_capabilities_prefer_closed_range_and_require_safe_fallback",
"v0_3_14_pre_006_http_scan_capability_detection_is_role_local_and_io_free",
"v0_3_14_pre_006_http_discovery_window_is_inclusive_bounded_and_overflow_safe",
"v0_3_14_pre_006_closed_range_discovery_proves_skipped_slots_only_inside_exact_window",
"v0_3_14_pre_006_with_limit_discovery_never_uses_tip_alone_as_tail_coverage_proof",
] {
assert!(resource_tests.contains(required), "required pre.006 HTTP discovery canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_006_http_discovery_is_bounded_prefers_closed_range_and_never_uses_tip_alone_as_coverage"));
assert!(!root.contains("pub use self::runtime_resources::RawTransactionIngestHttpDiscovery"));
assert!(!root.contains("backfill"));
return;
}
#[test]
fn v0_3_14_pre_007_known_reference_hydration_canaries_are_present_without_second_registry_or_public_surface_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_007_known_reference_obligation_preserves_exact_reference_slot_and_commitment",
"v0_3_14_pre_007_known_reference_hydration_reuses_global_registry_and_available_material",
"v0_3_14_pre_007_missing_known_reference_remains_open_without_block_capability",
"v0_3_14_pre_007_missing_known_reference_prefers_same_role_block_slot_when_supported",
] {
assert!(continuity_tests.contains(required) || resource_tests.contains(required), "required pre.007 canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_007_known_reference_hydration_reuses_one_registry_and_preserves_missing_obligation"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestKnownReferenceObligation"));
assert!(!root.contains("pub use self::runtime_resources::RawTransactionIngestKnownReference"));
return;
}
#[test]
fn v0_3_14_pre_008_reconciliation_canaries_are_present_without_public_surface_or_respawn_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_008_continuity_frontier_is_gap_aware_and_recovers_after_redundant_proof",
"pre_008_source_loss_requires_full_active_target_coverage",
"pre_008_open_gap_without_proven_epoch_blocks_redundant_source_continuation",
"pre_008_known_reference_missing_moves_to_continuity_ledger_and_reconciles_from_proven_full_ledger_epoch",
"v0_3_14_pre_008_proven_full_ledger_epoch_survives_filtered_peer_loss_without_worker_respawn",
"v0_3_14_pre_008_only_classified_source_loss_enters_coverage_decision",
] {
assert!(continuity_tests.contains(required) || resource_tests.contains(required), "required pre.008 canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_008_reconciliation_and_source_loss_are_target_coverage_gated_without_respawn"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestSourceLossDecision"));
return;
}
#[test]
fn v0_3_14_pre_009_health_policy_canaries_are_present_without_public_source_identity_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let snapshot_tests = include_str!("../unit_tests/snapshot.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_009_health_projection_distinguishes_future_coverage_from_open_gap_reconciliation",
"pre_009_health_projection_rejects_duplicate_and_unknown_active_sources",
"v0_3_14_pre_009_inventory_health_projection_tracks_reconciled_coverage",
"v0_3_14_pre_009_health_requires_present_and_future_coverage_before_healthy",
] {
assert!(
continuity_tests.contains(required) || resource_tests.contains(required) || snapshot_tests.contains(required),
"required pre.009 health canary missing: {required}"
);
}
assert!(hardening.contains("v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral"));
assert!(!root.contains("SourceHealthByKey"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestTargetCoverage"));
return;
}
#[test]
fn v0_3_14_pre_010_repair_fairness_canaries_are_present_without_public_or_pipeline_growth() {
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"v0_3_14_pre_010_fair_scheduler_alternates_nominal_and_repair_with_bounded_burst",
"v0_3_14_pre_010_fair_waiter_bound_is_checked_and_released_without_reserved_capacity",
"v0_3_14_pre_010_nominal_and_repair_hydration_share_one_capacity_one_permit_pool",
"v0_3_14_pre_010_repair_only_progresses_when_existing_capacity_is_one",
] {
assert!(resource_tests.contains(required), "required pre.010 fairness canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_010_repair_fairness_shares_existing_bounds_without_second_pipeline"));
assert!(!root.contains("RawTransactionIngestTrafficClass"));
assert!(!root.contains("RawTransactionIngestFairTurnGate"));
return;
}
#[test]
fn v0_3_14_pre_011_gap_observability_canaries_are_present_without_source_identity_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let public_api = include_str!("public_api.rs");
let snapshot_tests = include_str!("../unit_tests/snapshot.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_011_gap_observability_projects_open_and_recent_repaired_entries",
"v0_3_14_pre_011_snapshot_carries_checked_gap_observability",
"v0_3_14_pre_011_gap_observability_is_public_bounded_and_source_neutral",
] {
assert!(
continuity_tests.contains(required) || snapshot_tests.contains(required) || public_api.contains(required),
"required pre.011 observability canary missing: {required}"
);
}
assert!(hardening.contains("v0_3_14_pre_011_gap_observability_is_bounded_checked_and_redacted"));
for required in [
"RawTransactionIngestGapId",
"RawTransactionIngestGapReason",
"RawTransactionIngestGapSnapshot",
"RawTransactionIngestGapState",
"RawTransactionIngestRepairMethod",
] {
assert!(root.contains(required), "required pre.011 public type missing: {required}");
}
assert!(!root.contains("source_key"));
return;
}
#[test]
fn v0_3_14_pre_012_shutdown_race_canaries_cover_bounded_source_and_worker_drains() {
let dependency_boundary = include_str!("dependency_boundary.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"v0_3_14_pre_012_source_fault_preserves_first_fault_and_aborts_non_cooperative_sibling",
"v0_3_14_pre_012_stop_aborts_non_cooperative_source_after_bounded_drain",
] {
assert!(resource_tests.contains(required), "required pre.012 source shutdown canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_012_shutdown_races_are_bounded_across_source_and_worker_drains"));
assert!(dependency_boundary.contains("v0_3_14_pre_012_shutdown_hardening_stays_worker_local_and_facade_only"));
assert!(!root.contains("RawTransactionIngestShutdown"));
assert!(!root.contains("SourceDrain"));
return;
}
#[test]
fn v0_3_14_pre_013_cross_layer_closure_canaries_are_complete_without_behavior_or_surface_growth() {
let cross_layer = include_str!("cross_layer_completeness.rs");
let dependency_boundary = include_str!("dependency_boundary.rs");
let hardening = include_str!("hardening.rs");
let public_api = include_str!("public_api.rs");
for required in [
"v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline",
"v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths",
"v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening",
"v0_3_14_pre_013_worker_and_backfill_remain_independent_producers",
] {
assert!(cross_layer.contains(required), "required pre.013 cross-layer canary missing: {required}");
}
assert!(dependency_boundary.contains("v0_3_14_pre_013_cross_layer_closure_keeps_dependency_and_producer_boundaries_exact"));
assert!(hardening.contains("v0_3_14_pre_013_cross_layer_completeness_security_closure_is_exact_and_redacted"));
assert!(public_api.contains("v0_3_14_pre_013_completeness_closure_adds_no_public_surface"));
assert!(include_str!("../src/lib.rs").contains("RawTransactionIngestGapSnapshot"));
assert!(include_str!("../src/lib.rs").contains("RawTransactionIngestWorker"));
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
// version: 4
// version: 6
fn material(
network: &str,
@@ -262,3 +262,153 @@ async fn pre_009_full_queue_dequeue_marks_source_neutral_backpressure_observatio
assert!(!admission.take_backpressure_wait_observed());
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_008_distinct_source_keys_produce_distinct_idempotent_observation_keys() {
let (network, first_material) = match material("mainnet", 51, "AQID") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let (_, second_material) = match material("mainnet", 51, "AQID") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let first_provenance = match provenance() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let second_provenance = match provenance() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let (mut admission, sender) = crate::RawTransactionAdmission::new(2);
let first_ingress = crate::RawTransactionIngress {
material: first_material,
network: network.clone(),
provenance: first_provenance,
source_key: [11_u8; 32],
};
let second_ingress = crate::RawTransactionIngress {
material: second_material,
network: network.clone(),
provenance: second_provenance,
source_key: [12_u8; 32],
};
if sender.send(first_ingress).await.is_err() || sender.send(second_ingress).await.is_err() {
return;
}
std::mem::drop(sender);
let first = match admission.receive(&network).await {
std::result::Result::Ok(std::option::Option::Some(value)) => value,
_ => return,
};
let second = match admission.receive(&network).await {
std::result::Result::Ok(std::option::Option::Some(value)) => value,
_ => return,
};
assert_eq!(first.transaction().reference(), second.transaction().reference());
assert_eq!(first.transaction().payload().content_hash(), second.transaction().payload().content_hash());
assert_ne!(first.observation().observation_key(), second.observation().observation_key());
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_009_duplicate_storm_cannot_starve_second_ready_source_on_bounded_admission() {
let (network, initial_material) = match material("mainnet", 60, "AQID") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let (_, peer_material) = match material("mainnet", 99, "BwgJ") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let initial_provenance = match provenance() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let storm_provenance = match provenance() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let peer_provenance = match provenance() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let (mut admission, sender) = crate::RawTransactionAdmission::new(1);
assert!(
sender
.send(crate::RawTransactionIngress {
material: initial_material,
network: network.clone(),
provenance: initial_provenance,
source_key: [60_u8; 32],
})
.await
.is_ok()
);
let storm_sender = sender.clone();
let storm_network = network.clone();
let storm_task = tokio::spawn(async move {
for _ in 0..8 {
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
storm_network.clone(),
ksp_store_lib::RawTransactionSignature::new([61_u8; 64]),
42,
std::option::Option::Some(1_700_000_000),
"BAUG",
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
);
if storm_sender
.send(crate::RawTransactionIngress {
material,
network: storm_network.clone(),
provenance: storm_provenance.clone(),
source_key: [61_u8; 32],
})
.await
.is_err()
{
return false;
}
}
return true;
});
tokio::task::yield_now().await;
let peer_sender = sender.clone();
let peer_network = network.clone();
let peer_task = tokio::spawn(async move {
return peer_sender
.send(crate::RawTransactionIngress { material: peer_material, network: peer_network, provenance: peer_provenance, source_key: [99_u8; 32] })
.await
.is_ok();
});
tokio::task::yield_now().await;
assert_eq!(admission.queue_depth(), 1);
let first = match admission.receive(&network).await {
std::result::Result::Ok(std::option::Option::Some(value)) => value,
_ => return,
};
assert_eq!(first.transaction().reference().signature().as_bytes(), &[60_u8; 64]);
let second = match admission.receive(&network).await {
std::result::Result::Ok(std::option::Option::Some(value)) => value,
_ => return,
};
assert_eq!(second.transaction().reference().signature().as_bytes(), &[61_u8; 64]);
let third = match admission.receive(&network).await {
std::result::Result::Ok(std::option::Option::Some(value)) => value,
_ => return,
};
assert_eq!(third.transaction().reference().signature().as_bytes(), &[99_u8; 64]);
for _ in 0..7 {
let next = admission.receive(&network).await;
assert!(matches!(next, std::result::Result::Ok(std::option::Option::Some(_))));
assert!(admission.queue_depth() <= 1);
}
let peer_joined = peer_task.await;
assert!(matches!(peer_joined, std::result::Result::Ok(true)));
let storm_joined = storm_task.await;
assert!(matches!(storm_joined, std::result::Result::Ok(true)));
return;
}

View File

@@ -0,0 +1,727 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
// version: 8
fn network() -> std::option::Option<ksp_store_lib::RawNetworkId> {
return match ksp_store_lib::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None,
};
}
fn exact_scope(family: &'static str, fingerprint_byte: u8) -> crate::RawTransactionIngestCoverageScope {
return crate::RawTransactionIngestCoverageScope::exact_source_scope(family, [fingerprint_byte; 32]);
}
fn capability(
source_key_byte: u8,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
scope: crate::RawTransactionIngestCoverageScope,
) -> std::option::Option<crate::RawTransactionIngestContinuityCapabilityDescriptor> {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
return match crate::RawTransactionIngestContinuityCapabilityDescriptor::new(
[source_key_byte; 32],
network,
commitment,
scope,
true,
false,
false,
true,
false,
false,
) {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None,
};
}
fn gap(
gap_id: u64,
source_key_byte: u8,
start_slot: u64,
end_slot: u64,
state: super::RawTransactionIngestGapState,
) -> std::option::Option<super::RawTransactionIngestGap> {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let range = match super::RawTransactionIngestGapRange::new(start_slot, end_slot) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
return std::option::Option::Some(super::RawTransactionIngestGap {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
coverage_requirement: exact_scope("standard_logs", 9),
gap_id: super::RawTransactionIngestGapId(gap_id),
network,
range,
reason: super::RawTransactionIngestGapReason::WebSocketReconnect,
source_key: [source_key_byte; 32],
state,
last_method: std::option::Option::None,
});
}
#[test]
fn pre_002_repair_bounds_are_exact_and_run_local() {
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS, 64);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS, 1);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT, 4);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS, 512);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS, 4_096);
return;
}
#[test]
fn pre_002_gap_range_is_inclusive_bounded_and_overflow_safe() {
assert!(super::RawTransactionIngestGapRange::new(10, 9).is_err());
assert!(super::RawTransactionIngestGapRange::new(10, 10 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS - 1).is_ok());
assert!(super::RawTransactionIngestGapRange::new(10, 10 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS).is_err());
assert!(super::RawTransactionIngestGapRange::new(u64::MAX, u64::MAX).is_ok());
return;
}
#[test]
fn pre_002_gap_range_overlap_and_adjacency_are_explicit() {
let first = match super::RawTransactionIngestGapRange::new(100, 109) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected first range failure: {error}"),
};
let overlap = match super::RawTransactionIngestGapRange::new(105, 115) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected overlap range failure: {error}"),
};
let adjacent = match super::RawTransactionIngestGapRange::new(110, 120) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected adjacent range failure: {error}"),
};
let separate = match super::RawTransactionIngestGapRange::new(111, 120) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected separate range failure: {error}"),
};
assert!(first.overlaps_or_is_adjacent(overlap));
assert!(first.overlaps_or_is_adjacent(adjacent));
assert!(!first.overlaps_or_is_adjacent(separate));
let merged = first.try_merge(adjacent);
assert!(matches!(merged, std::result::Result::Ok(std::option::Option::Some(value)) if value.start_slot() == 100 && value.end_slot() == 120));
return;
}
#[test]
fn pre_002_target_coverage_deduplicates_exact_scopes_without_cross_commitment_broadening() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let finalized_full =
match capability(3, ksp_onchain_transport_lib::SolanaCommitment::Finalized, crate::RawTransactionIngestCoverageScope::full_ledger_transactions()) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("full capability fixture unavailable"),
};
let contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second, finalized_full]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert_eq!(contracts.capabilities.len(), 3);
assert_eq!(contracts.target_coverage.requirements.len(), 2);
assert!(contracts.target_coverage.requirements.iter().any(|requirement| {
return requirement.commitment == ksp_onchain_transport_lib::SolanaCommitment::Confirmed && requirement.scope == exact_scope("standard_logs", 7);
}));
assert!(contracts.target_coverage.requirements.iter().any(|requirement| {
return requirement.commitment == ksp_onchain_transport_lib::SolanaCommitment::Finalized
&& requirement.scope == crate::RawTransactionIngestCoverageScope::full_ledger_transactions();
}));
return;
}
#[test]
fn pre_002_full_ledger_scope_subsumes_only_same_commitment_targets() {
let exact = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("helius_transaction", 4)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("exact capability fixture unavailable"),
};
let full = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, crate::RawTransactionIngestCoverageScope::full_ledger_transactions())
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("full capability fixture unavailable"),
};
let contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![exact, full]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert_eq!(contracts.target_coverage.requirements.len(), 1);
assert_eq!(contracts.target_coverage.requirements[0].scope, crate::RawTransactionIngestCoverageScope::full_ledger_transactions());
assert_eq!(contracts.target_coverage.requirements[0].commitment, ksp_onchain_transport_lib::SolanaCommitment::Confirmed);
return;
}
#[test]
fn pre_002_known_references_can_never_be_configured_target_coverage() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("network fixture unavailable"),
};
let result = crate::RawTransactionIngestContinuityCapabilityDescriptor::new(
[1_u8; 32],
network,
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
crate::RawTransactionIngestCoverageScope::KnownReferences([2_u8; 32]),
true,
false,
false,
true,
false,
false,
);
assert!(result.is_err());
return;
}
#[test]
fn pre_002_gap_ledger_rejects_coalescible_open_ranges_and_active_overflow() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("network fixture unavailable"),
};
let first = match gap(1, 1, 100, 109, super::RawTransactionIngestGapState::Pending) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first gap fixture unavailable"),
};
let adjacent = match gap(2, 1, 110, 120, super::RawTransactionIngestGapState::Pending) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("adjacent gap fixture unavailable"),
};
let adjacent_ledger = super::RawTransactionIngestGapLedger { gaps: std::vec![first, adjacent], network: network.clone(), next_gap_id: 3 };
assert!(adjacent_ledger.validate_invariants().is_err());
let first_active = match gap(1, 1, 100, 109, super::RawTransactionIngestGapState::Repairing) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first active gap fixture unavailable"),
};
let second_active = match gap(2, 2, 200, 209, super::RawTransactionIngestGapState::Repairing) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second active gap fixture unavailable"),
};
let active_ledger = super::RawTransactionIngestGapLedger { gaps: std::vec![first_active, second_active], network, next_gap_id: 3 };
assert!(active_ledger.validate_invariants().is_err());
return;
}
#[test]
fn pre_002_gap_ledger_enforces_open_gap_bound_and_next_id_monotonicity() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("network fixture unavailable"),
};
let mut gaps = std::vec::Vec::new();
for index in 0..=super::MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS {
let gap_id = (index as u64) + 1;
let slot = (index as u64) * 2;
let gap = match gap(gap_id, 1, slot, slot, super::RawTransactionIngestGapState::Pending) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("gap fixture unavailable"),
};
gaps.push(gap);
}
let ledger = super::RawTransactionIngestGapLedger { gaps, network: network.clone(), next_gap_id: 66 };
assert!(ledger.validate_invariants().is_err());
let single = match gap(7, 1, 100, 100, super::RawTransactionIngestGapState::Unresolved) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("single gap fixture unavailable"),
};
let stale_next = super::RawTransactionIngestGapLedger { gaps: std::vec![single], network, next_gap_id: 7 };
assert!(stale_next.validate_invariants().is_err());
return;
}
#[test]
fn pre_003_websocket_incident_anchor_is_inclusive_monotone_and_bounded() {
let mut anchor = match crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 1, 0, true, false) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected anchor failure: {error}"),
};
assert_eq!(anchor.start_slot(), 100);
assert_eq!(anchor.end_slot(), std::option::Option::None);
assert!(anchor.saw_reconnect());
assert!(!anchor.saw_overflow());
assert!(anchor.extend(2, 1, true, true).is_ok());
assert!(anchor.saw_reconnect());
assert!(anchor.saw_overflow());
assert!(anchor.extend(1, 1, false, false).is_err());
assert!(anchor.close_at(100 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS - 1).is_ok());
assert_eq!(anchor.end_slot(), std::option::Option::Some(100 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS - 1));
assert!(anchor.extend(3, 1, true, false).is_err());
return;
}
#[test]
fn pre_003_websocket_incident_anchor_rejects_missing_reason_reversal_and_oversize() {
assert!(crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 0, 0, false, false).is_err());
let mut reversed = match crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 0, 1, false, true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected overflow anchor failure: {error}"),
};
assert!(reversed.close_at(99).is_err());
let mut oversized = match crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 1, 0, true, false) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected oversize anchor fixture failure: {error}"),
};
assert!(oversized.close_at(100 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS).is_err());
return;
}
#[test]
fn pre_005_scope_relations_are_exact_superset_and_cross_family_conservative() {
let exact = exact_scope("standard_logs", 7);
let same = exact_scope("standard_logs", 7);
let different_filter = exact_scope("standard_logs", 8);
let different_family = exact_scope("helius_transaction", 7);
let full = crate::RawTransactionIngestCoverageScope::full_ledger_transactions();
assert_eq!(exact.relation_to(&same), std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact));
assert_eq!(full.relation_to(&exact), std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Superset));
assert_eq!(full.relation_to(&full), std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact));
assert!(exact.relation_to(&full).is_none());
assert!(exact.relation_to(&different_filter).is_none());
assert!(exact.relation_to(&different_family).is_none());
assert!(crate::RawTransactionIngestCoverageScope::KnownReferences([7_u8; 32]).relation_to(&exact).is_none());
return;
}
#[test]
fn pre_005_coverage_epoch_requires_distinct_source_same_commitment_and_full_range() {
let target_requirement = super::RawTransactionIngestCoverageRequirement {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
scope: exact_scope("standard_logs", 9),
};
let gap = match super::RawTransactionIngestGapRange::new(100, 110) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected gap fixture failure: {error}"),
};
let full_range = match super::RawTransactionIngestCoverageRange::new(90, 120) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage range failure: {error}"),
};
let partial_range = match super::RawTransactionIngestCoverageRange::new(100, 109) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected partial coverage range failure: {error}"),
};
let exact_epoch = match super::RawTransactionIngestCoverageEpoch::new(
1,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("standard_logs", 9),
full_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected exact epoch failure: {error}"),
};
let full_epoch = match super::RawTransactionIngestCoverageEpoch::new(
2,
[3_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
crate::RawTransactionIngestCoverageScope::full_ledger_transactions(),
full_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected full epoch failure: {error}"),
};
let partial_epoch = match super::RawTransactionIngestCoverageEpoch::new(
3,
[4_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
crate::RawTransactionIngestCoverageScope::full_ledger_transactions(),
partial_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected partial epoch failure: {error}"),
};
let finalized_epoch = match super::RawTransactionIngestCoverageEpoch::new(
4,
[5_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Finalized,
crate::RawTransactionIngestCoverageScope::full_ledger_transactions(),
full_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected finalized epoch failure: {error}"),
};
assert_eq!(
exact_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap),
std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact),
);
assert_eq!(
full_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap),
std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Superset),
);
assert!(exact_epoch.relation_to_requirement([2_u8; 32], &target_requirement, gap).is_none());
assert!(partial_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap).is_none());
assert!(finalized_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap).is_none());
return;
}
#[test]
fn pre_005_cross_family_exact_scopes_never_become_redundant_by_fingerprint_alone() {
let target_requirement = super::RawTransactionIngestCoverageRequirement {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
scope: exact_scope("standard_logs", 11),
};
let gap = match super::RawTransactionIngestGapRange::new(500, 510) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected gap fixture failure: {error}"),
};
let range = match super::RawTransactionIngestCoverageRange::new(490, 520) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage range failure: {error}"),
};
let epoch = match super::RawTransactionIngestCoverageEpoch::new(
1,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("helius_transaction", 11),
range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected epoch failure: {error}"),
};
assert!(epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap).is_none());
return;
}
#[test]
fn pre_005_coverage_epoch_ledger_is_bounded_monotone_and_capability_bound() {
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS, 256);
let matching = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 5)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("matching capability fixture unavailable"),
};
let range = match super::RawTransactionIngestCoverageRange::new(100, 200) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage range failure: {error}"),
};
let epoch = match super::RawTransactionIngestCoverageEpoch::new(
1,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("standard_logs", 5),
range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage epoch failure: {error}"),
};
let valid = super::RawTransactionIngestCoverageEpochLedger { epochs: std::vec![epoch.clone()], next_epoch_id: 2 };
assert!(valid.validate_invariants(std::slice::from_ref(&matching)).is_ok());
let requirement = super::RawTransactionIngestCoverageRequirement {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
scope: exact_scope("standard_logs", 5),
};
let gap = match super::RawTransactionIngestGapRange::new(120, 130) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected ledger gap failure: {error}"),
};
assert_eq!(
valid.redundant_relation_for_gap([1_u8; 32], &requirement, gap),
std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact),
);
assert!(valid.redundant_relation_for_gap([2_u8; 32], &requirement, gap).is_none());
let stale = super::RawTransactionIngestCoverageEpochLedger { epochs: std::vec![epoch.clone()], next_epoch_id: 1 };
assert!(stale.validate_invariants(std::slice::from_ref(&matching)).is_err());
let unknown = super::RawTransactionIngestCoverageEpochLedger { epochs: std::vec![epoch], next_epoch_id: 2 };
assert!(unknown.validate_invariants(&[]).is_err());
let mut too_many = std::vec::Vec::new();
for index in 0..=super::MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS {
let epoch_id = (index as u64) + 1;
let epoch = match super::RawTransactionIngestCoverageEpoch::new(
epoch_id,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("standard_logs", 5),
range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected bounded epoch failure: {error}"),
};
too_many.push(epoch);
}
let overflow =
super::RawTransactionIngestCoverageEpochLedger { epochs: too_many, next_epoch_id: (super::MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS as u64) + 2 };
assert!(overflow.validate_invariants(std::slice::from_ref(&matching)).is_err());
return;
}
#[test]
fn pre_007_known_reference_obligation_preserves_exact_reference_slot_and_commitment() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let reference = ksp_store_lib::RawTransactionReference::new(network, ksp_store_lib::RawTransactionSignature::new([9_u8; 64]));
let obligation =
match crate::RawTransactionIngestKnownReferenceObligation::new(reference.clone(), 4242, ksp_onchain_transport_lib::SolanaCommitment::Confirmed) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected known-reference obligation failure: {error}"),
};
assert_eq!(obligation.reference(), &reference);
assert_eq!(obligation.slot(), 4242);
assert_eq!(obligation.commitment(), ksp_onchain_transport_lib::SolanaCommitment::Confirmed);
let processed = crate::RawTransactionIngestKnownReferenceObligation::new(reference, 4242, ksp_onchain_transport_lib::SolanaCommitment::Processed);
assert!(processed.is_err());
return;
}
#[test]
fn pre_008_continuity_frontier_is_gap_aware_and_recovers_after_redundant_proof() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert!(contracts.record_source_loss_gap([1_u8; 32], 100, 110).is_ok());
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(99));
assert!(contracts.record_coverage_epoch([2_u8; 32], 90, 120).is_ok());
let decision = contracts.source_loss_decision([1_u8; 32], &[[2_u8; 32]], std::option::Option::Some(120));
assert!(matches!(decision, std::result::Result::Ok(crate::RawTransactionIngestSourceLossDecision::Continue)));
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(120));
assert!(!contracts.gap_ledger.has_open_gaps());
return;
}
#[test]
fn pre_008_source_loss_requires_full_active_target_coverage() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("helius_transaction", 8)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let decision = contracts.source_loss_decision([1_u8; 32], &[[2_u8; 32]], std::option::Option::Some(120));
assert!(matches!(decision, std::result::Result::Ok(crate::RawTransactionIngestSourceLossDecision::Fault)));
return;
}
#[test]
fn pre_008_open_gap_without_proven_epoch_blocks_redundant_source_continuation() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let gap = match gap(1, 1, 100, 110, super::RawTransactionIngestGapState::Unresolved) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("gap fixture unavailable"),
};
contracts.gap_ledger.gaps.push(gap);
contracts.gap_ledger.next_gap_id = 2;
let decision = contracts.source_loss_decision([1_u8; 32], &[[2_u8; 32]], std::option::Option::Some(120));
assert!(matches!(decision, std::result::Result::Ok(crate::RawTransactionIngestSourceLossDecision::Fault)));
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(99));
return;
}
#[test]
fn pre_008_known_reference_missing_moves_to_continuity_ledger_and_reconciles_from_proven_full_ledger_epoch() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second =
match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, crate::RawTransactionIngestCoverageScope::full_ledger_transactions()) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("full-ledger capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert!(contracts.record_known_reference_gap([1_u8; 32], 105).is_ok());
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(104));
assert!(contracts.gap_ledger.has_open_gaps());
assert!(contracts.record_coverage_epoch([2_u8; 32], 100, 110).is_ok());
assert!(!contracts.gap_ledger.has_open_gaps());
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(120));
return;
}
#[test]
fn pre_009_health_projection_distinguishes_future_coverage_from_open_gap_reconciliation() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let healthy = match contracts.health_projection(&[[1_u8; 32], [2_u8; 32]], &[], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("health projection failed: {error}"),
};
assert_eq!(healthy, (std::option::Option::Some(120), false, true, true));
let redundant_future = match contracts.health_projection(&[[2_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("redundant health projection failed: {error}"),
};
assert_eq!(redundant_future, (std::option::Option::Some(120), false, true, false));
assert!(contracts.record_source_loss_gap([1_u8; 32], 100, 110).is_ok());
let pending = match contracts.health_projection(&[[2_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("pending health projection failed: {error}"),
};
assert_eq!(pending, (std::option::Option::Some(99), true, true, false));
assert!(contracts.record_coverage_epoch([2_u8; 32], 90, 120).is_ok());
let reconciled = match contracts.health_projection(&[[2_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("reconciled health projection failed: {error}"),
};
assert_eq!(reconciled, (std::option::Option::Some(120), false, true, true));
let uncovered_future = match contracts.health_projection(&[], &[], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("uncovered health projection failed: {error}"),
};
assert_eq!(uncovered_future, (std::option::Option::Some(120), false, false, true));
return;
}
#[test]
fn pre_009_health_projection_rejects_duplicate_and_unknown_active_sources() {
let capability = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![capability]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let duplicate = match contracts.health_projection(&[[1_u8; 32], [1_u8; 32]], &[], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(duplicate.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(duplicate.context().iter().any(|context| return context.value() == "continuity.health_active_set_invalid"));
let unknown = match contracts.health_projection(&[[9_u8; 32]], &[], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(unknown.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(unknown.context().iter().any(|context| return context.value() == "continuity.health_active_source_unknown"));
let overlap = match contracts.health_projection(&[[1_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(overlap.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(overlap.context().iter().any(|context| return context.value() == "continuity.health_failed_set_invalid"));
let unknown_failed = match contracts.health_projection(&[], &[[9_u8; 32]], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(unknown_failed.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(unknown_failed.context().iter().any(|context| return context.value() == "continuity.health_failed_source_unknown"));
return;
}
#[test]
fn pre_011_gap_observability_projects_open_and_recent_repaired_entries() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert!(contracts.record_source_loss_gap([1_u8; 32], 100, 110).is_ok());
let pending = match contracts.observability_projection() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("pending observability projection failed: {error}"),
};
assert_eq!(pending.open_gap_count(), 1);
assert_eq!(pending.repairing_gap_count(), 0);
assert_eq!(pending.repaired_gap_total(), 0);
assert_eq!(pending.unresolved_gap_total(), 0);
assert_eq!(pending.oldest_open_gap_start_slot(), std::option::Option::Some(100));
assert_eq!(pending.gaps().len(), 1);
assert_eq!(pending.gaps()[0].start_slot(), 100);
assert_eq!(pending.gaps()[0].end_slot(), 110);
assert_eq!(pending.gaps()[0].state(), crate::RawTransactionIngestGapState::Pending);
assert_eq!(pending.gaps()[0].reason(), crate::RawTransactionIngestGapReason::SourceFailure);
assert_eq!(pending.gaps()[0].last_method(), std::option::Option::None);
assert!(contracts.record_coverage_epoch([2_u8; 32], 90, 120).is_ok());
let repaired = match contracts.observability_projection() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("repaired observability projection failed: {error}"),
};
assert_eq!(repaired.open_gap_count(), 0);
assert_eq!(repaired.repaired_gap_total(), 1);
assert_eq!(repaired.redundant_coverage_repair_total(), 1);
assert_eq!(repaired.replay_repair_total(), 0);
assert_eq!(repaired.http_scan_repair_total(), 0);
assert_eq!(repaired.repair_block_fetch_total(), 0);
assert_eq!(repaired.repair_transaction_hydration_total(), 0);
assert_eq!(repaired.oldest_open_gap_start_slot(), std::option::Option::None);
assert_eq!(repaired.gaps().len(), 1);
assert_eq!(repaired.gaps()[0].state(), crate::RawTransactionIngestGapState::Repaired);
assert_eq!(repaired.gaps()[0].last_method(), std::option::Option::Some(crate::RawTransactionIngestRepairMethod::RedundantCoverage));
return;
}
#[test]
fn pre_011_unresolved_gap_remains_visible_and_bounded() {
let capability = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![capability]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let unresolved = match gap(1, 1, 200, 205, super::RawTransactionIngestGapState::Unresolved) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("unresolved gap fixture unavailable"),
};
contracts.gap_ledger.gaps.push(unresolved);
contracts.gap_ledger.next_gap_id = 2;
let projection = match contracts.observability_projection() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unresolved observability projection failed: {error}"),
};
assert_eq!(projection.open_gap_count(), 1);
assert_eq!(projection.unresolved_gap_total(), 1);
assert_eq!(projection.oldest_open_gap_start_slot(), std::option::Option::Some(200));
assert!(projection.gaps().len() <= super::MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS);
assert_eq!(projection.gaps()[0].state(), crate::RawTransactionIngestGapState::Unresolved);
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
// version: 1
// version: 3
#[derive(Clone, Copy)]
enum PortResponse {
@@ -12,6 +12,8 @@ struct FakePersistencePort {
calls: std::sync::atomic::AtomicUsize,
network: ksp_store_lib::RawNetworkId,
normal_mode_seen: std::sync::atomic::AtomicBool,
observation_calls: std::sync::atomic::AtomicUsize,
observation_response: std::option::Option<ksp_store_lib::RawObservationWriteOutcome>,
response: PortResponse,
}
@@ -21,9 +23,16 @@ impl FakePersistencePort {
calls: std::sync::atomic::AtomicUsize::new(0),
network,
normal_mode_seen: std::sync::atomic::AtomicBool::new(false),
observation_calls: std::sync::atomic::AtomicUsize::new(0),
observation_response: std::option::Option::None,
response,
};
}
fn with_observation_response(mut self, observation_response: ksp_store_lib::RawObservationWriteOutcome) -> Self {
self.observation_response = std::option::Option::Some(observation_response);
return self;
}
}
impl crate::RawTransactionIngestPersistencePort for FakePersistencePort {
@@ -51,9 +60,50 @@ impl crate::RawTransactionIngestPersistencePort for FakePersistencePort {
};
});
}
fn record_observation<'a>(
&'a self,
_observation: ksp_store_lib::RawTransactionObservation,
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>> {
self.observation_calls.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
let observation_response = self.observation_response;
let response = self.response;
return std::boxed::Box::pin(async move {
if let std::option::Option::Some(outcome) = observation_response {
return std::result::Result::Ok(outcome);
}
return match response {
PortResponse::Outcome(_, observation) => std::result::Result::Ok(observation),
PortResponse::Conflict => std::result::Result::Err(ksp_core_lib::Error::new(ksp_store_lib::ERROR_CODE_RAW_CONFLICT, "synthetic conflict")),
PortResponse::StoreFailure => std::result::Result::Err(ksp_core_lib::Error::new(
ksp_core_lib::ErrorCode::new("synthetic_store", "write_failed"),
"synthetic remote failure text that must not escape",
)),
};
});
}
}
fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
return acquisition_with_observation_key(network_name, signature_byte, signature_byte);
}
fn acquisition_with_observation_key(
network_name: &str,
signature_byte: u8,
observation_byte: u8,
) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
return acquisition_with_shape(network_name, signature_byte, observation_byte, 42, std::option::Option::Some(1_700_000_000), "AQID");
}
fn acquisition_with_shape(
network_name: &str,
signature_byte: u8,
observation_byte: u8,
slot: u64,
block_time: std::option::Option<i64>,
transaction_data: &str,
) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
let network = match ksp_store_lib::RawNetworkId::new(network_name) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
@@ -62,9 +112,9 @@ fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ks
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
network,
signature,
42,
std::option::Option::Some(1_700_000_000),
"AQID",
slot,
block_time,
transaction_data,
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
@@ -92,7 +142,7 @@ fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ks
let provenance = ksp_store_lib::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_lib::RawAcquisitionOrigin::Live, received_at);
return std::option::Option::Some(ksp_raw_transaction_lib::assemble_raw_transaction_acquisition(
transaction,
ksp_store_lib::RawObservationKey::new([signature_byte; 32]),
ksp_store_lib::RawObservationKey::new([observation_byte; 32]),
provenance,
));
}
@@ -251,3 +301,108 @@ async fn pre_007_store_network_mismatch_is_rejected_before_write() {
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 0);
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_008_same_canonical_identity_uses_atomic_entity_once_then_additional_observation() {
let network = match network("mainnet") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let port = FakePersistencePort::new(
network,
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
);
let convergence = crate::RawTransactionIngestPersistenceConvergence::new(8);
let first = match acquisition_with_observation_key("mainnet", 41, 1) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let second = match acquisition_with_observation_key("mainnet", 41, 2) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let first_outcome = crate::persist_raw_transaction_ingest_converged_acquisition(&port, first, &convergence).await;
let second_outcome = crate::persist_raw_transaction_ingest_converged_acquisition(&port, second, &convergence).await;
assert!(first_outcome.is_ok());
assert!(second_outcome.is_ok());
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 1);
assert_eq!(port.observation_calls.load(std::sync::atomic::Ordering::Acquire), 1);
let second_outcome = match second_outcome {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(second_outcome.entity(), crate::RawTransactionIngestEntityPersistence::AlreadyPresent);
assert_eq!(second_outcome.observation(), crate::RawTransactionIngestObservationPersistence::Inserted);
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_008_replayed_same_observation_is_idempotent_without_raw_resubmission() {
let network = match network("mainnet") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let port = FakePersistencePort::new(
network,
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
)
.with_observation_response(ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent);
let convergence = crate::RawTransactionIngestPersistenceConvergence::new(8);
let first = match acquisition_with_observation_key("mainnet", 42, 3) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let second = match acquisition_with_observation_key("mainnet", 42, 3) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let first_result = crate::persist_raw_transaction_ingest_converged_acquisition(&port, first, &convergence).await;
assert!(first_result.is_ok());
let second_result = crate::persist_raw_transaction_ingest_converged_acquisition(&port, second, &convergence).await;
let second_outcome = match second_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 1);
assert_eq!(port.observation_calls.load(std::sync::atomic::Ordering::Acquire), 1);
assert_eq!(second_outcome.observation(), crate::RawTransactionIngestObservationPersistence::AlreadyPresent);
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_009_source_skew_and_provider_disagreement_fail_before_additional_observation() {
let cases = [
(43_u64, std::option::Option::Some(1_700_000_000_i64), "AQID"),
(42_u64, std::option::Option::Some(1_700_000_001_i64), "AQID"),
(42_u64, std::option::Option::Some(1_700_000_000_i64), "BAUG"),
];
for (index, (slot, block_time, transaction_data)) in cases.into_iter().enumerate() {
let network = match network("mainnet") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let port = FakePersistencePort::new(
network,
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
);
let convergence = crate::RawTransactionIngestPersistenceConvergence::new(8);
let first = match acquisition_with_shape("mainnet", 51, 10, 42, std::option::Option::Some(1_700_000_000), "AQID") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let divergent = match acquisition_with_shape("mainnet", 51, index as u8 + 20, slot, block_time, transaction_data) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(crate::persist_raw_transaction_ingest_converged_acquisition(&port, first, &convergence).await.is_ok());
let conflict = crate::persist_raw_transaction_ingest_converged_acquisition(&port, divergent, &convergence).await;
let error = match conflict {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_CONTENT_CONFLICT);
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 1);
assert_eq!(port.observation_calls.load(std::sync::atomic::Ordering::Acquire), 0);
}
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs
// version: 9
// version: 11
struct ActiveTaskGuard {
active: std::sync::Arc<std::sync::atomic::AtomicUsize>,
@@ -348,6 +348,36 @@ impl crate::RawTransactionIngestPersistencePort for RuntimePersistencePort {
));
});
}
fn record_observation<'a>(
&'a self,
_observation: ksp_store_lib::RawTransactionObservation,
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>> {
let response = self.response;
return std::boxed::Box::pin(async move {
if matches!(response, RuntimePortResponse::StoreFailureBlocked | RuntimePortResponse::SuccessBlocked) {
let current = self.active.fetch_add(1, std::sync::atomic::Ordering::AcqRel) + 1;
let _active_guard = RuntimePortActiveGuard { active: &self.active };
self.update_max_active(current);
while !self.released.load(std::sync::atomic::Ordering::Acquire) {
self.notify.notified().await;
}
if matches!(response, RuntimePortResponse::SuccessBlocked) {
self.completed.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
return std::result::Result::Ok(ksp_store_lib::RawObservationWriteOutcome::Inserted);
}
}
if matches!(response, RuntimePortResponse::Conflict) {
self.completed.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
return std::result::Result::Err(ksp_core_lib::Error::new(ksp_store_lib::ERROR_CODE_RAW_CONFLICT, "synthetic conflict"));
}
self.completed.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
return std::result::Result::Err(ksp_core_lib::Error::new(
ksp_core_lib::ErrorCode::new("synthetic_store", "write_failed"),
"synthetic store failure",
));
});
}
}
fn runtime_ingress(network: &ksp_store_lib::RawNetworkId, signature_byte: u8) -> std::option::Option<crate::RawTransactionIngress> {
@@ -869,3 +899,98 @@ async fn pre_009_drain_timeout_aborts_and_joins_all_owned_source_and_persistence
assert_eq!(port.completed.load(std::sync::atomic::Ordering::Acquire), 0);
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_011_source_counter_exhaustion_stays_terminal_and_is_not_collapsed_to_source_failed() {
let settings = match settings("mainnet") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let handle = match super::start_foundation_with_source_spawner(
settings,
tokio::runtime::Handle::current(),
std::option::Option::None,
move |children: &mut tokio::task::JoinSet<ksp_core_lib::Result<()>>, _stop_receiver, _admission_sender| {
let _abort_handle = children.spawn(async move {
return std::result::Result::Err(crate::counter_exhausted_error("source_reconnect_total"));
});
},
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let source = handle.snapshot_source();
let terminal = match handle.wait_terminal().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(terminal, ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED));
let snapshot = source.current();
assert_eq!(snapshot.source_failure_total(), 1);
assert_eq!(snapshot.worker_snapshot().state(), terminal);
assert_eq!(snapshot.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
let terminal_sequence = snapshot.worker_snapshot().sequence();
assert!(!handle.request_stop());
let retained = source.current();
assert_eq!(retained.worker_snapshot().state(), terminal);
assert_eq!(retained.worker_snapshot().sequence(), terminal_sequence);
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_011_drain_timeout_prevents_late_persistence_completion_after_terminal() {
let settings = match settings_with_runtime_limits(1, 1, crate::MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let network = settings.network().clone();
let port = std::sync::Arc::new(RuntimePersistencePort::new(network.clone(), RuntimePortResponse::SuccessBlocked, false));
let runtime_port: super::PersistencePort = port.clone();
let source_active = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let source_active_for_task = std::sync::Arc::clone(&source_active);
let handle = match super::start_foundation_with_port_and_source_spawner(
settings,
tokio::runtime::Handle::current(),
std::option::Option::Some(runtime_port),
move |children, _stop_receiver, admission_sender| {
let _abort_handle = children.spawn(async move {
let ingress = match runtime_ingress(&network, 81) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(crate::runtime_error("test.ingress_invalid")),
};
if admission_sender.send(ingress).await.is_err() {
return std::result::Result::Err(crate::runtime_error("test.source_failed"));
}
let _guard = ActiveTaskGuard::new(source_active_for_task);
return std::future::pending::<ksp_core_lib::Result<()>>().await;
});
},
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(wait_for_max_active(&port, 1).await);
assert!(wait_for_active_count(&source_active, 1).await);
assert!(handle.request_stop());
let terminal = match handle.wait_terminal().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(terminal, ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT));
let snapshot = handle.snapshot_source().current();
let terminal_sequence = snapshot.worker_snapshot().sequence();
assert_eq!(snapshot.in_flight_persistence(), 0);
assert_eq!(port.active.load(std::sync::atomic::Ordering::Acquire), 0);
assert_eq!(port.completed.load(std::sync::atomic::Ordering::Acquire), 0);
assert_eq!(source_active.load(std::sync::atomic::Ordering::Acquire), 0);
port.release();
for _ in 0..64 {
tokio::task::yield_now().await;
}
assert_eq!(port.completed.load(std::sync::atomic::Ordering::Acquire), 0);
let retained = handle.snapshot_source().current();
assert_eq!(retained.worker_snapshot().state(), terminal);
assert_eq!(retained.worker_snapshot().sequence(), terminal_sequence);
assert_eq!(retained.in_flight_persistence(), 0);
return;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,9 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
// version: 2
// version: 7
fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> {
fn snapshot_foundation_with_source_total(
source_total: usize,
) -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> {
let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
@@ -19,7 +21,11 @@ fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnap
if lifecycle.start().is_err() {
return std::option::Option::None;
}
return std::option::Option::Some(crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle));
return std::option::Option::Some(crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle, source_total));
}
fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> {
return snapshot_foundation_with_source_total(0);
}
#[test]
@@ -50,6 +56,17 @@ fn pre_008_initial_snapshot_and_common_projection_are_exact() {
assert_eq!(concrete.store_failure_total(), 0);
assert_eq!(concrete.source_failure_total(), 0);
assert_eq!(concrete.backpressure_wait_total(), 0);
assert_eq!(concrete.hydration_pending(), 0);
assert_eq!(concrete.processing_frontier_slot(), std::option::Option::None);
assert_eq!(concrete.oldest_pending_slot(), std::option::Option::None);
assert_eq!(concrete.source_state(), std::option::Option::None);
assert_eq!(concrete.source_total(), 0);
assert_eq!(concrete.source_active(), 0);
assert_eq!(concrete.source_reconnecting(), 0);
assert_eq!(concrete.source_failed(), 0);
assert_eq!(concrete.source_reconnect_total(), 0);
assert_eq!(concrete.source_replay_attempt_total(), 0);
assert_eq!(concrete.source_continuity_gap_total(), 0);
let common = ksp_worker_api::WorkerSnapshotSource::current(&source);
assert_eq!(&common, concrete.worker_snapshot());
return;
@@ -134,3 +151,198 @@ fn pre_009_source_failure_and_backpressure_counters_advance_without_changing_pro
assert_eq!(&common, concrete.worker_snapshot());
return;
}
#[test]
fn pre_007_processing_frontier_projection_is_latest_value_and_activity_aware() {
let (mut publisher, source) = match snapshot_foundation() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let initial = source.current();
assert_eq!(initial.hydration_pending(), 0);
assert_eq!(initial.processing_frontier_slot(), std::option::Option::None);
assert_eq!(initial.oldest_pending_slot(), std::option::Option::None);
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(2, std::option::Option::Some(40), std::option::Option::Some(41));
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
let pending = source.current();
assert_eq!(pending.hydration_pending(), 2);
assert_eq!(pending.processing_frontier_slot(), std::option::Option::Some(40));
assert_eq!(pending.oldest_pending_slot(), std::option::Option::Some(41));
assert_eq!(pending.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Active);
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(45), std::option::Option::None);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
let settled = source.current();
assert_eq!(settled.hydration_pending(), 0);
assert_eq!(settled.processing_frontier_slot(), std::option::Option::Some(45));
assert_eq!(settled.oldest_pending_slot(), std::option::Option::None);
assert_eq!(settled.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Idle);
return;
}
#[test]
fn pre_008_source_continuity_projection_preserves_processing_frontier_and_distinguishes_replay() {
let (mut publisher, source) = match snapshot_foundation() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(2, std::option::Option::Some(40), std::option::Option::Some(41))
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting), 0, 1, 0);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
let reconnecting = source.current();
assert_eq!(reconnecting.hydration_pending(), 2);
assert_eq!(reconnecting.processing_frontier_slot(), std::option::Option::Some(40));
assert_eq!(reconnecting.oldest_pending_slot(), std::option::Option::Some(41));
assert_eq!(reconnecting.source_state(), std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting));
assert_eq!(reconnecting.source_reconnect_total(), 0);
assert_eq!(reconnecting.source_replay_attempt_total(), 1);
assert_eq!(reconnecting.source_continuity_gap_total(), 0);
assert_eq!(reconnecting.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Active);
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(45), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 0);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
let resumed = source.current();
assert_eq!(resumed.hydration_pending(), 0);
assert_eq!(resumed.processing_frontier_slot(), std::option::Option::Some(45));
assert_eq!(resumed.source_state(), std::option::Option::Some(crate::RawTransactionIngestSourceState::Active));
assert_eq!(resumed.source_reconnect_total(), 1);
assert_eq!(resumed.source_replay_attempt_total(), 1);
assert_eq!(resumed.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Idle);
return;
}
#[test]
fn v0_3_13_pre_010_multi_source_counts_and_health_are_conservative_and_source_neutral() {
let (mut publisher, source) = match snapshot_foundation_with_source_total(3) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let initial = source.current();
assert_eq!(initial.source_total(), 3);
assert_eq!(initial.source_active(), 0);
assert_eq!(initial.source_reconnecting(), 0);
assert_eq!(initial.source_failed(), 0);
assert_eq!(initial.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unknown);
let reconnecting = crate::RawTransactionIngestProcessingFrontierProjection::new(1, std::option::Option::Some(50), std::option::Option::Some(50))
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting), 0, 1, 0)
.with_source_counts(3, 2, 1, 0);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, reconnecting).is_ok());
let degraded = source.current();
assert_eq!(degraded.source_total(), 3);
assert_eq!(degraded.source_active(), 2);
assert_eq!(degraded.source_reconnecting(), 1);
assert_eq!(degraded.source_failed(), 0);
assert_eq!(degraded.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Degraded);
assert_eq!(degraded.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Active);
let active = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(51), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 0)
.with_source_counts(3, 3, 0, 0);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, active).is_ok());
let healthy = source.current();
assert_eq!(healthy.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Healthy);
assert_eq!(healthy.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Idle);
let failed = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(51), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 0)
.with_source_counts(3, 2, 0, 1);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, failed).is_ok());
let unhealthy = source.current();
assert_eq!(unhealthy.source_failed(), 1);
assert_eq!(unhealthy.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
return;
}
#[test]
fn v0_3_14_pre_009_health_requires_present_and_future_coverage_before_healthy() {
let (mut publisher, source) = match snapshot_foundation_with_source_total(2) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let reconnecting = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting), 1, 1, 1)
.with_source_counts(2, 1, 1, 0)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, reconnecting).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
let gap_pending = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 1)
.with_source_counts(2, 2, 0, 0)
.with_continuity_health(std::option::Option::Some(49), true, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, gap_pending).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
let healthy = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 1)
.with_source_counts(2, 2, 0, 0)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, healthy).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Healthy);
let failed_unreconciled = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 1)
.with_source_counts(2, 1, 0, 1)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(false);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, failed_unreconciled).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
let degraded = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 1)
.with_source_counts(2, 1, 0, 1)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, degraded).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Degraded);
let future_uncovered = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 1)
.with_source_counts(2, 1, 0, 1)
.with_continuity_health(std::option::Option::Some(50), false, false)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, future_uncovered).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
assert!(publisher.publish_state(ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED), 0, 0).is_ok());
let faulted = source.current();
assert!(matches!(faulted.worker_snapshot().state(), ksp_worker_api::WorkerState::Faulted(_)));
assert_eq!(faulted.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
return;
}
#[test]
fn v0_3_14_pre_011_snapshot_carries_checked_gap_observability() {
let (mut publisher, source) = match snapshot_foundation_with_source_total(2) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let gap = crate::RawTransactionIngestGapSnapshot::new(
crate::RawTransactionIngestGapId::new(7),
100,
110,
crate::RawTransactionIngestGapState::Pending,
crate::RawTransactionIngestGapReason::SourceFailure,
std::option::Option::None,
);
let continuity_snapshot = crate::RawTransactionIngestContinuitySnapshotProjection::empty()
.with_gap_state(std::vec![gap], 1, 0, std::option::Option::Some(100))
.with_recovery_totals(3, 1, 2, 1, 0)
.with_material_totals(4, 5);
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(120), std::option::Option::None)
.with_continuity_snapshot(continuity_snapshot);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
let snapshot = source.current();
assert_eq!(snapshot.gaps().len(), 1);
assert_eq!(snapshot.gaps()[0].gap_id().value(), 7);
assert_eq!(snapshot.gaps()[0].start_slot(), 100);
assert_eq!(snapshot.gaps()[0].end_slot(), 110);
assert_eq!(snapshot.gaps()[0].state(), crate::RawTransactionIngestGapState::Pending);
assert_eq!(snapshot.gaps()[0].reason(), crate::RawTransactionIngestGapReason::SourceFailure);
assert_eq!(snapshot.gaps()[0].last_method(), std::option::Option::None);
assert_eq!(snapshot.open_gap_count(), 1);
assert_eq!(snapshot.repairing_gap_count(), 0);
assert_eq!(snapshot.repaired_gap_total(), 3);
assert_eq!(snapshot.unresolved_gap_total(), 1);
assert_eq!(snapshot.replay_repair_total(), 2);
assert_eq!(snapshot.redundant_coverage_repair_total(), 1);
assert_eq!(snapshot.http_scan_repair_total(), 0);
assert_eq!(snapshot.repair_block_fetch_total(), 4);
assert_eq!(snapshot.repair_transaction_hydration_total(), 5);
assert_eq!(snapshot.oldest_open_gap_start_slot(), std::option::Option::Some(100));
return;
}

270
deltas/0.3.12/pre.001.md Normal file
View File

@@ -0,0 +1,270 @@
<!-- file: deltas/0.3.12/pre.001.md -->
<!-- version: 1 -->
# Delta 0.3.12-pre.001 — audit Yellowstone, hydration HTTP et continuité Worker
## Base
```text
archive : khadhroony-solana-project-v0.3.11.zip
SHA-256 : a6bb59935650a7f48630d12ef80fbd1a44a84819dd129b085ff7f467369535fc
ZIP bytes : 8125100
ZIP entries : 1950
unzip -t : PASS
workspace members : 21
workspace.package.version base : 0.3.11
stable delta : deltas/0.3.11/rel.001.md
```
Le ZIP a une racine unique et a été contrôlé sans entrée absolue, path traversal, symlink, `.git/`, `target/`, `node_modules/`, `Cargo.lock` ou `.env`.
Préconditions du prompt `031` confirmées :
```text
Worker RawTransaction présent et documenté
aucune source réseau productive Worker stable
Common RAW présente
Transport Yellowstone générique présent
Store facade présente
Backfill indépendant du Worker
```
## Type de livraison
```text
ksp-general-0.3.12-pre.001.zip
```
Le type `general` est requis par le bump `Cargo.toml` de toute prerelease non-fix (`VER-ID-009`). L'archive d'échange contient uniquement les fichiers ajoutés/modifiés par cette tranche et son delta.
## Baseline exécutée 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 (340 table(s), 798 file(s))
```
## Objectif
Exécuter le gate `pre.001` de `prompts/031-V0_3_12_START_PROMPT.md` : audit complet de la stable et des règles, inventaire Worker/Transport/Common RAW/Store/Config, fraîcheur Yellowstone/Solana/providers/crates, matrice des signaux, architecture d'injection, hydration/provenance, continuité/frontier, threat model, smokes, graphe Cargo et sizing, sans implémentation réseau productive.
## Audit des règles
Aucune contradiction normative active n'a été trouvée dans les règles courantes. Aucun fichier de règles n'est modifié.
Contraintes fermées :
```text
Worker/Job séparés
Config seul propriétaire des endpoints/secrets/composition
Store facade seulement
pas de public enqueue
pas de second moteur Yellowstone
nouvelle prerelease => Cargo 0.3.12-pre.1
tranches <= 1520 minutes
```
## Décisions fermées
### Edge et injection
```text
seul nouvel edge autorisé : Worker -> ksp-onchain-transport-lib
Worker -> Config reste interdit
Worker -> Backfill reste interdit
caller supérieur construit channel/request Yellowstone + pool/role HTTP
RawTransactionIngestYellowstoneSource + RawTransactionIngestRuntimeResources retenus
start stable source-neutral conservé
start_with_runtime_resources(settings, Arc<Store>, resources) retenu
```
### Matrice Yellowstone
```text
Transaction -> candidat hydration
TransactionStatus -> candidat hydration
Block transactions-> candidats hydration
BlockMeta -> continuity/health seulement
Slot -> continuity/health seulement
from_slot -> reconnect/replay Transport, pas checkpoint historique
ReplayInfo -> borne de rétention seulement
```
### RAW/hydration
```text
RAW-direct Yellowstone non prouvé et interdit en P0
get_transaction_observed = hydration P0
Base64 + confirmed/finalized + maxSupportedTransactionVersion=0
processed exclu du chemin hydraté P0
get_block_observed non requis au P0
```
### Provenance
Le modèle Store existant est conservé sans migration. La route multi-hop est représentée par des codes sûrs composites `yellowstone_http` contenant les identités logiques Yellowstone et le winner HTTP, toutes bornées par `RawProvenanceCode`. Aucune URL/token/header n'est conservé.
### Continuité
```text
Transport high-watermark != Worker processing frontier
processing frontier != blockchain completeness
reconnect != replay != repair
replay attempt != preuve de couverture
continuity gap prouvé par ReplayInfo => fault sûr
aucun Backfill/repair automatique en 0.3.12
frontier run-local, non persistante
```
## Fraîcheur externe du 8 septembre 2026
```text
yellowstone-grpc-proto latest = 12.7.0 (2026-08-29)
workspace = ^12.7 => aucun bump requis
Solana getTransaction fournit blockTime/meta/slot/transaction/version
Solana getBlock reste disponible mais non requis au P0
OrbitFlare documente Devnet gRPC et free Devnet-only
Helius LaserStream documente Yellowstone compatibility et replay provider-specific ~216k slots/~24h
```
Le changelog Yellowstone du 22 juillet 2026 corrige un défaut `from_slot` + `blocks` pouvant reprendre au live head sans rejouer les Blocks. KSP ne considérera donc jamais l'acceptation de `from_slot` comme une preuve de continuité.
## Smokes
Smokes Transport existants :
```text
OrbitFlare Devnet : opt-in, token opérateur
PublicNode Mainnet/Testnet : opt-in, tokens opérateur
```
Aucun secret live n'est fourni dans l'archive. Aucun smoke Worker n'est exécuté en `pre.001` et aucun faux PASS live n'est déclaré.
## Graphe Cargo cible
Après `pre.002` :
```text
ksp-worker-raw-transaction-ingest-lib
-> ksp-core-lib
-> ksp-logging-lib
-> ksp-onchain-transport-lib
-> ksp-raw-transaction-lib
-> ksp-store-lib default-features=false
-> ksp-worker-api
-> sha2
-> tokio macros,rt,sync,time
```
Pas d'edge Worker direct vers Config, Backfill, Store backend, Tonic, Reqwest, Yellowstone proto ou SDK provider.
## Sizing recalibré
```text
pre.002 Transport resource/source contract
pre.003 Transaction + TransactionStatus adapters
pre.004 HTTP hydration + provenance
pre.005 Block + BlockMeta + Slot
pre.006 source task + supervisor/coalescence
pre.007 processing frontier
pre.008 reconnect/from_slot/ReplayInfo
pre.009 race/retry/backpressure hardening
pre.010 cross-layer completeness/security
pre.011 technical/live gate
pre.012 documentation reconciliation
pre.013 publication preparation
rel.001
```
Décision : maintenir `0.3.12`. Le split frontier/replay évite une tranche de continuité trop large ; toute tranche dépassant réellement 1520 minutes sera encore scindée avant exécution.
## Fichiers ajoutés
```text
docs/plans/033-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY_PLAN.md
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
deltas/0.3.12/pre.001.md
```
## Fichiers modifiés
```text
Cargo.toml
```
Versions :
```text
Cargo.toml : 518 -> 519
workspace.package.version : 0.3.11 -> 0.3.12-pre.1
```
Le bump Cargo est requis par `VER-ID-009` pour une prerelease non-fix, même si la tranche ne modifie aucun Rust.
## Fichiers supprimés
```text
aucun
```
## Validation après modification
Audits statiques réellement exécutés sur le delta complet :
```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 (340 table(s), 801 file(s))
```
Résultat : PASS.
## Validations non exécutées localement
`cargo` n'est pas installé dans l'environnement d'assemblage. Ne sont donc pas déclarés PASS localement :
```text
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
cargo tree --duplicates
```
Le journal opérateur fourni avec la stable montre un gate Cargo complet vert sur `0.3.11`; cette preuve reste distincte du delta `0.3.12-pre.001`.
## Gate opérateur demandé
Après application :
```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 -p ksp-worker-raw-transaction-ingest-lib
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
```
## Questions reportées
```text
RAW-direct Yellowstone après preuve byte-exact
retry Worker borné spécifique au null getTransaction
multi-source redundancy/failover
hot listener reconfiguration complète
repair automatique 0.3.14
checkpoint durable inter-process
provider-specific capabilities permanentes
```

261
deltas/0.3.12/pre.002.md Normal file
View File

@@ -0,0 +1,261 @@
<!-- file: deltas/0.3.12/pre.002.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.002` — edge Transport + contrats runtime/source Yellowstone
## Base requise
```text
0.3.12-pre.001
workspace.package.version = 0.3.12-pre.1
```
Le gate opérateur communiqué pour `pre.001` est vert sur :
```text
cargo fmt --all
Rust rule audit
Markdown table audit
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
Le journal ne contient pas de `cargo test` ni de `cargo tree` ; ces commandes ne sont pas inventées comme PASS.
## Objectif
Ouvrir uniquement le nouvel edge de production décidé en `pre.001` :
```text
ksp-worker-raw-transaction-ingest-lib -> ksp-onchain-transport-lib
```
et matérialiser les contrats Worker-owned nécessaires à la future source Yellowstone + hydration HTTP, sans ouvrir de stream ni exécuter de requête réseau.
## Version
Cette tranche est une prerelease non-fix :
```text
workspace.package.version = 0.3.12-pre.2
```
## Graphe Worker normal attendu
```text
ksp-core-lib
ksp-logging-lib
ksp-onchain-transport-lib
ksp-raw-transaction-lib
ksp-store-lib default-features=false
ksp-worker-api
sha2
tokio macros,rt,sync,time
```
Toujours interdits directement au Worker :
```text
ksp-config-lib
ksp-job-api
ksp-job-backfill-lib
ksp-store-api
ksp-store-postgres-lib
reqwest
tokio-tungstenite
tonic
yellowstone-grpc-proto
SDK provider
```
## Nouveau contrat `RawTransactionIngestYellowstoneSource`
Le type public à champs privés possède :
```text
YellowstoneGrpcChannel
YellowstoneSubscribeRequest
HttpTransportPool
HttpRoleName d'hydration
```
`RawTransactionIngestYellowstoneSource::new(...)` valide sans I/O :
```text
request Yellowstone valide
au moins une famille transactions / transactions_status / blocks
commitment explicite confirmed ou finalized
présence d'une route HTTP configurée compatible get_transaction
cohérence cluster entre Yellowstone et toutes les routes HTTP compatibles
```
Les erreurs de composition sont projetées sur `worker_raw_transaction_ingest.runtime_invalid` avec uniquement une condition stable et sûre.
## Nouveau contrat `RawTransactionIngestRuntimeResources`
`RawTransactionIngestRuntimeResources::new(yellowstone_source)` possède exactement cette première famille runtime.
Aucune collection multi-source, enum provider, closure source, callback public, `enqueue`, `send`, getter de client interne ou backend n'est exposé.
La ressource valide également la cohérence entre le réseau du source composé et `RawTransactionIngestSettings.network` avant le démarrage.
## Nouveau start de composition
Ajout :
```text
RawTransactionIngestWorker::start_with_runtime_resources(
settings,
Arc<Store>,
RawTransactionIngestRuntimeResources,
)
```
La méthode conserve :
```text
runtime Tokio caller-owned
validation réseau Store/settings
validation réseau Worker/source
handle/snapshot/shutdown existants
```
En `pre.002`, les ressources sont volontairement validées mais ne sont pas activées avant délégation au runtime source-neutral existant. Cela ferme l'API de composition sans anticiper `pre.006`.
## Absence volontaire de comportement live
Les canaris interdisent dans cette tranche :
```text
open_standard_subscribe
next_update
get_transaction_observed
get_block_observed
source task spawn spécifique
nouveau JoinSet
HTTP hydration
persistence issue du réseau
```
`pre.003` reste donc libre d'introduire uniquement les adapters déterministes `Transaction`/`TransactionStatus`, tandis que l'activation productive de la source reste réservée à `pre.006`.
## Sécurité et redaction
Le `Debug` des ressources expose uniquement des métadonnées sûres :
```text
logical Yellowstone endpoint name
provider
network
filter counts
commitment
presence from_slot
hydration role
HTTP endpoint count
```
Il n'expose ni URL, token, header, filter name ni client interne.
Tests déterministes ajoutés :
```text
acceptation confirmed + transaction + get_transaction
rejet processed
rejet commitment implicite
rejet absence de famille ingestion-bearing
rejet rôle HTTP non compatible
rejet mismatch Yellowstone/HTTP cluster
rejet mismatch Worker/source network
Debug redaction
public API/root contract
manifest/dependency firewall
Transport confiné à runtime_resources.rs
absence de live I/O en pre.002
```
## Décisions prises
```text
un seul nouvel edge productif Worker -> Onchain Transport
composition fournie par l'appelant, sans dépendance Worker -> Config
ressources Transport encapsulées par des types Worker-owned à champs privés
commitment de la future hydration limité à confirmed/finalized dès la composition
aucune activation réseau avant la tranche productive prévue
```
## Questions ouvertes
Aucune question ouverte n'est bloquante pour `pre.002`. Les choix volontairement reportés restent ceux du plan actif : forme exacte du signal privé `Transaction`/`TransactionStatus` en `pre.003`, provenance Yellowstone + hydration en `pre.004`, puis continuité/replay dans les tranches dédiées.
## Fichiers ajoutés
```text
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
deltas/0.3.12/pre.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## 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 (340 table(s), 802 file(s))
```
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun gate Cargo local n'est déclaré PASS.
## 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-raw-transaction-ingest-lib
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
```
## Non-claims
Cette tranche ne revendique pas :
```text
session Yellowstone ouverte
signal Transaction/TransactionStatus produit
hydration HTTP exécutée
provenance composite assemblée
coalescence d'hydration
frontier/replay/repair
smoke live Worker
réconciliation documentaire finale README/USAGE/architecture
```
## Suite
Après gate opérateur vert, `pre.003` adapte uniquement `YellowstoneTransactionUpdate` et `YellowstoneTransactionStatusUpdate` vers le signal Worker privé prévu par le plan, sans HTTP ni persistence réseau.

View File

@@ -0,0 +1,136 @@
<!-- file: deltas/0.3.12/pre.003-fix.001.md -->
<!-- version: 2 -->
# Delta `0.3.12-pre.003-fix.001` — suppression du dead code préparatoire
## Base requise
```text
0.3.12-pre.003
workspace.package.version = 0.3.12-pre.3
```
Le gate opérateur reçu pour `pre.003` montre :
```text
audit Rust : clean
audit Markdown : clean (340 tables, 803 fichiers)
cargo check --workspace : PASS visible
cargo test -p ksp-worker-raw-transaction-ingest-lib : 45 PASS, 0 fail
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL
```
L'échec Clippy est limité à huit diagnostics `dead_code` dans `runtime_resources.rs` pour les adapters privés préparatoires de `pre.003` et le champ privé `route`.
## Objectif
Corriger exclusivement le gate Clippy de `pre.003` sans avancer le périmètre fonctionnel vers `pre.004`.
## Version
La correction touche du code Rust ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
delivery id = 0.3.12-pre.003-fix.001
workspace.package.version = 0.3.12-pre.3.fix.1
```
## Correction
`RUST-API-008` exige qu'un helper strictement privé, sans consommateur de production et présent uniquement pour une préparation testée, soit compilé sous `#[cfg(test)]` jusqu'à sa première consommation réelle.
Sont donc passés sous `#[cfg(test)]` :
```text
import sha2::Digest utilisé uniquement par le fingerprint préparatoire
RAW_TRANSACTION_INGEST_YELLOWSTONE_FILTER_FINGERPRINT_DOMAIN
RawTransactionIngestSourceFamily
RawTransactionIngestSourceTimestamp + Debug
RawTransactionIngestSourceSignal + Debug
RawTransactionIngestYellowstoneSignalView
impls Transaction et TransactionStatus
impls From vers le signal privé
matched_filter_fingerprint
project_yellowstone_signal
```
Ces éléments restent présents dans la source et restent exercés par les unit tests `pre.003`; ils réentreront dans le build productif à leur première consommation réelle en `pre.004`.
Aucun `#[allow(dead_code)]` ni `#[expect(dead_code)]` n'est utilisé.
Le champ `RawTransactionIngestYellowstoneSource::route` n'est pas test-only : sa validation provider/endpoint fait déjà partie du contrat productif `pre.003`. Le `Debug` sûr de la source lit maintenant ce champ, ce qui supprime son warning sans masquer le lint.
## Décisions prises
```text
respect strict de RUST-API-008
pas de suppression des adapters testés de pre.003
pas d'élargissement de visibilité
pas d'allow/expect dead_code
route validée conservée dans la source productive
aucune activation live anticipée
```
## Questions ouvertes
Aucune. La première consommation productive des adapters reste `pre.004`.
## Fichiers ajoutés
```text
deltas/0.3.12/pre.003-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## 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 (340 table(s), 804 file(s))
```
## Validations non exécutées dans l'environnement d'assemblage
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans cet environnement. Aucun gate Cargo local n'est déclaré PASS.
## 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-raw-transaction-ingest-lib
```
## Non-claims
Ce correctif ne revendique toujours pas :
```text
stream Yellowstone ouvert
update réseau consommée
hydration HTTP
Common RAW issu du réseau
provenance composite finale
persistence réseau
Block / BlockMeta / Slot
coalescence
frontier / from_slot / ReplayInfo
```

View File

@@ -0,0 +1,130 @@
<!-- file: deltas/0.3.12/pre.003-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.003-fix.002` — correction du canari source sous `#[cfg(test)]`
## Base requise
```text
0.3.12-pre.003-fix.001
workspace.package.version = 0.3.12-pre.3.fix.1
```
Le gate opérateur reçu pour `pre.003-fix.001` montre :
```text
audit Rust : clean
audit Markdown : clean (340 tables, 804 fichiers)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
unit tests Worker : 45 PASS
integration dependency_boundary : 4 PASS
integration public_api : 8 PASS
integration release_completeness : 3 PASS
integration hardening : 9 PASS, 1 FAIL
```
L'unique échec est `v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload`, qui rapporte à tort `required private signal field missing: created_at:`.
## Objectif
Corriger exclusivement le canari source devenu incompatible avec l'attribut `#[cfg(test)]` introduit par `pre.003-fix.001`, sans modifier le contrat runtime ni avancer `pre.004`.
## Version
La correction modifie un test Rust ; conformément aux règles de version du workspace :
```text
delivery id = 0.3.12-pre.003-fix.002
workspace.package.version = 0.3.12-pre.3.fix.2
```
## Correction
Le scanner du test isolait `RawTransactionIngestSourceSignal` avec une terminaison littérale qui supposait que son `impl Debug` suivait immédiatement la structure :
```text
}\n\nimpl std::fmt::Debug for RawTransactionIngestSourceSignal
```
Après `pre.003-fix.001`, la forme correcte est :
```text
}
#[cfg(test)]
impl std::fmt::Debug for RawTransactionIngestSourceSignal
```
Le scanner est donc rendu indépendant de cet attribut : après avoir trouvé la déclaration de la structure, il borne désormais le segment sur le début de `impl std::fmt::Debug for RawTransactionIngestSourceSignal` lui-même.
Cette forme fonctionne avec les adapters actuellement test-only et restera compatible lorsque `pre.004` remettra ces helpers dans le build productif.
## Décisions prises
```text
aucune modification du signal privé
aucune modification de runtime_resources.rs
aucun allow/expect lint
aucune activation Yellowstone/HTTP/Store
aucune avance fonctionnelle vers pre.004
correction limitée au canari qui inspecte la source
```
## Questions ouvertes
Aucune.
## Fichiers ajoutés
```text
deltas/0.3.12/pre.003-fix.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## Validations exécutées dans l'environnement d'assemblage
```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
```
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans cet environnement ; aucun gate Cargo local n'est déclaré PASS.
## 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-raw-transaction-ingest-lib
```
## Non-claims
Ce fix ne revendique toujours pas :
```text
stream Yellowstone ouvert
update réseau consommée
hydration HTTP
Common RAW issu du réseau
provenance composite finale
persistence réseau
Block / BlockMeta / Slot
coalescence
frontier / from_slot / ReplayInfo
```

255
deltas/0.3.12/pre.003.md Normal file
View File

@@ -0,0 +1,255 @@
<!-- file: deltas/0.3.12/pre.003.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.003` — signaux privés Yellowstone Transaction + TransactionStatus
## Base requise
```text
0.3.12-pre.002
workspace.package.version = 0.3.12-pre.2
```
Le gate opérateur communiqué pour `pre.002` est vert sur :
```text
cargo fmt --all
Rust rule audit
Markdown table audit
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib : 41 PASS
cargo tree Worker normal/features
cargo tree --duplicates exécuté pour inspection
```
Le graphe confirme que `reqwest`, `tonic` et `yellowstone-grpc-proto` restent transitifs via `ksp-onchain-transport-lib`, jamais directs dans le Worker.
## Objectif
Matérialiser uniquement les deux adapters déterministes prévus :
```text
YellowstoneTransactionUpdate -> signal Worker privé
YellowstoneTransactionStatusUpdate -> signal Worker privé
```
sans ouvrir de session Yellowstone, sans HTTP et sans persistence réseau.
## Version
```text
workspace.package.version = 0.3.12-pre.3
```
## Signal Worker privé
Le signal est strictement privé à `runtime_resources.rs`. Sa forme contient :
```text
RawNetworkId
RawTransactionSignature
slot u64
transaction_index Option<u64>
family Transaction | TransactionStatus
route sûre provider + endpoint_id
matched_filter_count
matched_filter_fingerprint [u8; 32]
created_at Option<seconds+nanos>
```
Il ne contient volontairement pas :
```text
transaction body Yellowstone
transaction meta Yellowstone
is_vote
TransactionStatus.error
raw/provider payload
HTTP material
Store acquisition
```
La signature Yellowstone `[u8; 64]` est convertie directement vers `RawTransactionSignature`; aucune Base58 intermédiaire n'est introduite.
## Adapter `Transaction`
Le mapping utilise exactement :
```text
update.slot()
update.transaction().signature()
update.transaction().index()
update.filters()
update.created_at()
```
Le body et la meta présents dans `YellowstoneTransactionInfo` restent ignorés jusqu'à l'hydration HTTP qualifiée de `pre.004`.
## Adapter `TransactionStatus`
Le mapping utilise exactement :
```text
update.slot()
update.signature()
update.index()
update.filters()
update.created_at()
```
`update.error()` n'est pas consulté. Le remote error opaque n'est donc ni copié dans le signal, ni transformé en contexte Worker, ni loggé.
## Fingerprint des filtres
Le fingerprint est Worker-owned, SHA-256 et domain-separated :
```text
ksp.raw_transaction_ingest.yellowstone.filters.v1\0
```
Les noms sont triés puis dédupliqués avant hashing ; chaque élément est length-prefixed par un `u64` big-endian. Le résultat est donc déterministe pour le même ensemble de filtres, même si l'ordre d'enveloppe diffère.
Le golden de fixture `filter-a + filter-b` est :
```text
20831dc8378b6070f91d9de6d3a1d2b3d77298e3ada76c01faa30826ffabcd84
```
Les noms eux-mêmes ne sont pas conservés dans le signal et n'apparaissent pas dans son `Debug`.
## Route source sûre
La source Yellowstone convertit dès construction :
```text
cluster -> RawNetworkId
provider -> RawProvenanceCode
logical endpoint name -> RawProvenanceCode
```
Une valeur impossible à représenter selon les bornes Store est rejetée par `worker_raw_transaction_ingest.runtime_invalid` avec une condition statique. Il n'y a ni troncature ni fuite de valeur.
Cela prépare `pre.004`, où le provider/endpoint HTTP gagnant sera combiné avec cette identité Yellowstone dans la provenance composite retenue par le plan.
## Absence volontaire de comportement live
Toujours absents en `pre.003` :
```text
open_standard_subscribe
next_update
get_transaction_observed
get_block_observed
source task Yellowstone
HTTP hydration
RawTransactionIngress issue du réseau
Store write issue du réseau
coalescence hydration
frontier / reconnect / replay
```
L'activation productive de la session reste `pre.006`.
## Tests et hardening ajoutés
```text
Transaction fixture -> identité source-neutral exacte
TransactionStatus fixture -> même forme sans payload/error
fingerprint golden exact
fingerprint order/duplicate insensitive
signal Debug redacted
route provider/endpoint non représentable rejetée
source-scan des deux adapters Transport
TransactionStatus.error absent du mapping
signal non public
aucun I/O live prématuré
```
## Décisions prises
```text
un seul signal privé commun aux deux familles
signature canonique conservée en bytes fixes
transaction_index toujours Some pour ces deux DTOs mais champ du signal reste optionnel pour les familles suivantes
filter fingerprint canonique indépendant de l'ordre
gRPC route identity validée tôt pour la future provenance
remote TransactionStatus.error ignoré au boundary Worker
```
## Questions ouvertes
Aucune question ouverte n'est bloquante pour `pre.003`. Les choix HTTP missing/mismatch, provenance composite finale et fan-out/coalescence restent volontairement dans `pre.004`, conformément au plan.
## Fichiers ajoutés
```text
deltas/0.3.12/pre.003.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## 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
```
L'audit Markdown final est exécuté après création de ce delta.
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun gate Cargo local n'est déclaré PASS.
## 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-raw-transaction-ingest-lib
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
```
## Non-claims
Cette tranche ne revendique pas :
```text
session Yellowstone ouverte
update réseau réellement consommée
hydration HTTP
conversion réseau vers Common RAW
provenance composite finale
persistence réseau
Block / BlockMeta / Slot
coalescence
processing frontier
from_slot / ReplayInfo
smoke live Worker
réconciliation README/USAGE/architecture finale
```
## Suite
Après gate opérateur vert, `pre.004` ferme `signal -> get_transaction_observed -> Common RAW -> RawTransactionIngress`, avec `missing`, mismatches et provenance Yellowstone + HTTP observée. Block et la continuité/replay restent hors scope.

View File

@@ -0,0 +1,97 @@
<!-- file: deltas/0.3.12/pre.004-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.004-fix.001` — correction du gate Clippy strict
## Base requise
```text
0.3.12-pre.004
workspace.package.version = 0.3.12-pre.4
```
Le gate opérateur communiqué confirme :
```text
Rust rule audit : clean
Markdown table audit : clean (340 tables, 806 fichiers)
cargo check --workspace : PASS
cargo test -p ksp-raw-transaction-lib : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib : PASS
```
Le seul gate rouge est :
```text
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
avec `clippy::large_enum_variant` et deux occurrences de `clippy::collapsible_if` dans `runtime_resources.rs`.
## Objectif
Corriger exclusivement ces trois diagnostics sans `#[allow(...)]`, sans `#[expect(...)]`, sans changer le comportement de hydration et sans avancer le scope de `pre.005`.
## Version
```text
workspace.package.version = 0.3.12-pre.4.fix.1
```
## Corrections
`RawTransactionIngestHydrationOutcome::Available` devient :
```text
Available(Box<RawTransactionIngress>)
```
La box ne concerne que ce résultat privé `#[cfg(test)]`; elle supprime la disproportion de taille entre `Available` et `Missing`. Les unit tests déplacent explicitement l'ingress hors de la box avant admission.
Le contrôle `transaction_index` utilise désormais une chaîne `if let` Rust 2024 unique. Il conserve exactement la règle précédente : un mismatch n'est rejeté que si le signal possède un index et si la réponse HTTP fournit un `SolanaWireField::Value` différent. `Omitted` et `Null` restent acceptés comme absence d'index HTTP comparable.
## Fichiers ajoutés
```text
deltas/0.3.12/pre.004-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## Non-changements
```text
aucune API publique
aucune dépendance
aucun changement de provenance
aucune politique retry Worker
aucun Block / BlockMeta / Slot
aucun source task Yellowstone productif
aucune continuité / replay
aucun lint neutralisé
```
## Gate opérateur requis
```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-raw-transaction-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
Après gate vert, la suite reste `pre.005`.

292
deltas/0.3.12/pre.004.md Normal file
View File

@@ -0,0 +1,292 @@
<!-- file: deltas/0.3.12/pre.004.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.004` — hydration `getTransaction` et provenance composite Yellowstone + HTTP
## Base requise
```text
0.3.12-pre.003-fix.002
workspace.package.version = 0.3.12-pre.3.fix.2
```
Le gate opérateur communiqué pour cette base est entièrement vert :
```text
cargo fmt --all
Rust rule audit : clean
Markdown table audit : clean (340 tables, 805 fichiers)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib : PASS
unit tests Worker : 45 PASS
integration dependency_boundary : 4 PASS
integration hardening : 10 PASS
integration public_api : 8 PASS
integration release_completeness : 3 PASS
doc-tests : 0 fail
```
## Objectif
Fermer et qualifier déterministiquement, sans source task productive, la chaîne :
```text
signal Yellowstone Transaction / TransactionStatus
-> signature canonique 64 bytes -> Base58 commun
-> get_transaction_observed
Base64
confirmed | finalized
maxSupportedTransactionVersion = 0
-> contrôles source/hydration
-> RawTransactionMaterial commun
-> RawTransactionIngress existant
```
La tranche ne branche ni Block, ni continuité/replay, ni session Yellowstone productive.
## Version
```text
workspace.package.version = 0.3.12-pre.4
```
## Common RAW — formatage Base58
`ksp-raw-transaction-lib` expose désormais `format_raw_transaction_signature` depuis la racine de crate.
La fonction encode exactement les 64 bytes de `RawTransactionSignature` vers le texte Base58 canonique attendu par les RPC Solana. Elle ne dépend d'aucune crate Transport ni d'un second codec externe. Les bornes restent celles déjà publiques :
```text
MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES = 64
MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES = 88
```
Le round-trip `bytes -> format -> parse -> bytes` est qualifié sur plusieurs signatures, dont la signature nulle représentée par exactement 64 caractères `1`.
## Hydration HTTP qualifiée
Le helper privé de qualification construit `SolanaGetTransactionConfig` avec :
```text
encoding = base64
commitment = confirmed | finalized
maxSupportedTransactionVersion = 0
```
La signature RPC est obtenue exclusivement via `ksp_raw_transaction_lib::format_raw_transaction_signature`.
`HttpTransportPool::get_transaction_observed` reste propriétaire du routage, retry/backoff et rate-limit Transport. Le Worker n'ajoute aucune boucle retry autour d'une erreur Transport.
## Contrôles avant admission
Avant de créer un ingress, la qualification impose :
```text
network signal == network Worker == network source
route signal == route source
slot HTTP == slot signal
transaction_index HTTP == index signal lorsque les deux côtés le fournissent
transaction HTTP réellement encodée en Base64
signature extraite du wire Base64 HTTP == signature signal
```
Un mismatch devient `worker_raw_transaction_ingest.runtime_invalid` avec une condition `hydration.*` stable. Il n'est jamais converti en content conflict Store.
Les erreurs Transport sont reclassées en `worker_raw_transaction_ingest.source_failed` en ne conservant que le domain/code lower-layer déjà sûrs.
## Missing
`getTransaction -> null` retourne :
```text
Missing(RawTransactionReference)
```
Aucun `RawTransactionMaterial`, aucune provenance et aucun ingress ne sont inventés. Aucun retry Worker supplémentaire n'est ajouté dans cette tranche.
## Provenance composite
La provenance réutilise sans migration `RawAcquisitionProvenance` et `RawProvenanceCode` :
```text
protocol = yellowstone_http
provider = ys.<grpc_provider>:http.<http_provider_observé>
endpoint_id = ys.<grpc_endpoint>:http.<http_endpoint_observé>
acquisition_method = transaction_get_transaction | status_get_transaction
origin = Live
commitment = confirmed | finalized
capture_session_id = WorkerId sûr
filter_id = nom direct unique représentable | sha256.<fingerprint>
observed_at = created_at Yellowstone seulement si représentable et <= received_at
```
Le provider et l'endpoint HTTP proviennent exclusivement du `HttpObservedValue` gagnant. Les URL, headers, tokens, bodies HTTP bruts et remote errors ne sont pas recopiés.
Le constructeur `RawTransactionIngestYellowstoneSource::new` valide maintenant que chaque route HTTP compatible peut former les codes composites provider/endpoint sans dépasser `RawProvenanceCode`. Aucune troncature n'est admise.
## Common RAW et wire
Après contrôles, la réponse HTTP est projetée vers `RawTransactionMaterial::binary_base64` en préservant :
```text
blockTime -> Option<i64> puis RawTimestamp commun en millisecondes
meta omitted | null | value
version omitted | null | legacy | numeric
transactionIndex omitted | null | value
```
La signature réellement embarquée dans le wire Base64 est extraite via `extract_raw_transaction_signature_from_binary_base64` avant admission.
Les fixtures couvrent notamment :
```text
Legacy + meta valeur + block_time valeur + index présent
V0 + meta null + block_time null + index omis
```
## `RUST-API-008`
Le source task productif n'est pas créé avant `pre.006`. Les adapters Transaction/TransactionStatus et la chaîne d'hydration, encore sans consumer productif, restent donc sous `#[cfg(test)]` conformément à `RUST-API-008`.
Aucun `#[allow(dead_code)]` ni `#[expect(dead_code)]` n'est introduit et aucune surface publique artificielle n'est créée.
Les éléments immédiatement productifs de `pre.004` sont limités à :
```text
format_raw_transaction_signature dans la common RAW
validation de représentabilité de provenance composite au constructeur de source
```
## Canaris déterministes ajoutés ou étendus
```text
Base58 formatter round-trip et crate-root API
getTransaction confirmed/base64/max version 0
getTransaction finalized/base64/max version 0
commitment finalized conservé dans la provenance
winner HTTP observé dans provider/endpoint composites
provenance Transaction et TransactionStatus distincte
filter unique direct
multi-filter -> fingerprint sha256 borné
created_at futur ignoré pour observed_at
getTransaction null -> Missing sans ingress/retry Worker
slot mismatch
transaction index mismatch
signature wire mismatch
network mismatch avant I/O
composite provider overflow rejeté sans troncature
absence Block/getBlock/stream/source spawn
absence Config/Backfill/backend/reqwest/tonic/proto direct dans le Worker
```
## Décisions prises
```text
aucune migration Store pour une chaîne de provenance multi-hop
Base58 appartient à la common RAW, pas au Worker
HTTP observed est la source de vérité du provider/endpoint gagnant
processed reste exclu du P0 hydration
missing n'ajoute pas de retry Worker par défaut
mismatch source/hydration est un fault Worker avant Store
pas de RAW direct Yellowstone avant qualification byte-exacte dédiée
adapters/hydration restent test-only jusqu'au source task productif de pre.006
```
## Questions ouvertes
Aucune question ouverte n'est bloquante pour `pre.004`.
Les sujets suivants restent volontairement réservés aux tranches ultérieures :
```text
Block / BlockMeta / Slot : pre.005
source task productive et coalescence : pre.006
processing frontier : pre.007
reconnect/from_slot/ReplayInfo : pre.008
races/retry/backpressure final : pre.009
```
## Fichiers ajoutés
```text
deltas/0.3.12/pre.004.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-raw-transaction-lib/README.md
crates/ksp-raw-transaction-lib/USAGE.md
crates/ksp-raw-transaction-lib/src/lib.rs
crates/ksp-raw-transaction-lib/src/signature.rs
crates/ksp-raw-transaction-lib/tests/public_api.rs
crates/ksp-raw-transaction-lib/unit_tests/signature.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/033-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY_PLAN.md
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## 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 (340 table(s), 806 file(s))
```
## Validations non exécutées dans l'environnement d'assemblage
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun résultat Cargo local n'est déclaré PASS.
Le gate opérateur requis reste :
```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-raw-transaction-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
## Non-claims
Cette tranche ne revendique pas :
```text
open_standard_subscribe
consommation d'une update réseau live
source task Yellowstone productif
persistence d'une acquisition issue d'un stream réel
Block / BlockMeta / Slot
getBlock hydration
coalescence productive
processing frontier
from_slot / ReplayInfo
repair historique
smoke live Worker
réconciliation documentaire finale de pre.012
```
## Suite
Après gate opérateur vert, `pre.005` adapte `Block` en signaux transactionnels et `BlockMeta`/`Slot` en signaux continuity-only, sans encore introduire la politique reconnect Worker.

View File

@@ -0,0 +1,105 @@
<!-- file: deltas/0.3.12/pre.005-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.005-fix.001` — correction de la conversion de signature Block
## Base requise
```text
0.3.12-pre.005
workspace.package.version = 0.3.12-pre.5
```
Le gate opérateur communiqué confirme :
```text
Rust rule audit : clean
Markdown table audit : clean (340 tables, 808 fichiers)
cargo check --workspace : PASS
```
La compilation des tests Worker échoue ensuite sur un unique diagnostic `E0599` : l'adapter privé `Block` appelle `YellowstoneTransactionSignature::into_bytes()`, méthode absente de la façade Transport.
## Objectif
Corriger uniquement cette incompatibilité d'API sans changer le comportement `Block`, sans export supplémentaire et sans avancer le scope productif de `pre.006`.
## Version
```text
workspace.package.version = 0.3.12-pre.5.fix.1
```
## Correction
La construction de la signature source-neutral passe de :
```text
RawTransactionSignature::new(signature.into_bytes())
```
à :
```text
RawTransactionSignature::new(*signature.as_bytes())
```
`YellowstoneTransactionSignature::as_bytes()` est le contrat réel exposé par Transport. Le dereference copie le tableau canonique `[u8; 64]` attendu par `RawTransactionSignature::new`. Cette forme est déjà utilisée dans le même module pour `TransactionStatus`.
## Fichiers ajoutés
```text
deltas/0.3.12/pre.005-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## Non-changements
```text
aucune API publique
aucune dépendance
aucun changement de provenance
aucun changement d'ordre Block
aucun RAW depuis BlockMeta/Slot
aucun source task Yellowstone productif
aucune coalescence productive
aucun reconnect/from_slot/ReplayInfo
aucun lint neutralisé
```
## Validations exécutées dans l'environnement d'assemblage
```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
```
Les contrôles statiques vérifient également qu'il ne reste aucun appel `YellowstoneTransactionSignature::into_bytes()` dans `runtime_resources.rs` et que la conversion `*signature.as_bytes()` correspond à la forme déjà utilisée par l'adapter `TransactionStatus`.
## Validations non exécutées dans l'environnement d'assemblage
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage. Aucun gate Cargo local n'est revendiqué.
## Gate opérateur requis
```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-raw-transaction-ingest-lib
```
Après gate vert, la suite reste `pre.006`.

188
deltas/0.3.12/pre.005.md Normal file
View File

@@ -0,0 +1,188 @@
<!-- file: deltas/0.3.12/pre.005.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.005` — Block transactionnel + BlockMeta/Slot continuity-only
## Base requise
```text
0.3.12-pre.004-fix.001
workspace.package.version = 0.3.12-pre.4.fix.1
```
Le gate opérateur communiqué pour cette base est entièrement vert : audits Rust/Markdown, `cargo check --workspace`, Clippy strict, 17 tests Common RAW et 52 tests Worker avec toutes les suites d'intégration associées.
## Objectif
Fermer les adapters privés Yellowstone restants nécessaires avant le source task productif :
```text
Block -> N signaux transactionnels d'hydration dans l'ordre source
BlockMeta -> signal continuity-only
Slot -> signal continuity-only
```
La tranche ne branche ni session live, ni reconnect policy Worker, ni `from_slot`, ni `ReplayInfo`, ni processing frontier.
## Version
```text
workspace.package.version = 0.3.12-pre.5
```
## Block
`YellowstoneBlockUpdate` implémente un adapter privé borné qui lit uniquement les identités nécessaires au P0 :
```text
filters
created_at
slot
transactions[].signature
transactions[].index
```
Chaque transaction incluse produit un `RawTransactionIngestSourceSignal` avec `family = Block`. L'ordre du `Vec` de sortie suit strictement l'ordre `transactions[]` du DTO Transport. Aucun body/meta Yellowstone n'entre dans Common RAW directement.
La future hydration réutilise `getTransaction` et la provenance composite `yellowstone_http`; la méthode d'acquisition devient `block_get_transaction`, distincte de `transaction_get_transaction` et `status_get_transaction`.
`get_block_observed` reste hors P0 et n'est pas introduit.
## BlockMeta et Slot
Les deux DTOs convergent vers un signal Worker privé séparé, `RawTransactionIngestContinuitySignal`. Sa forme est strictement bornée à :
```text
created_at
family
matched_filter_count
matched_filter_fingerprint
matched_filter_id
network
parent_slot
route
slot
status
```
`BlockMeta` fournit slot/parent et aucun status. Ses blockhash, rewards, block_time, block_height et compteurs ne sont pas utilisés comme preuve de complétude transactionnelle dans cette tranche.
`Slot` fournit slot/parent/status. Les sept états Yellowstone sont mappés vers un enum Worker privé source-neutral. Le diagnostic `dead_error` n'est jamais lu.
Aucun signal continuity-only ne peut produire un `RawTransactionIngress`.
## `RUST-API-008`
Le consumer productif n'arrive qu'en `pre.006`. Les adapters et projecteurs privés `Block`/`BlockMeta`/`Slot` restent donc sous `#[cfg(test)]`, comme la chaîne Transaction/Status/hydration déjà qualifiée.
Aucun `#[allow(dead_code)]`, `#[expect(dead_code)]` ou export public artificiel n'est ajouté.
## Canaris
```text
Block -> 3 signatures exactes dans l'ordre source, avec indexes non triés conservés
Block -> slot/network/filter context commun
Block -> méthode de provenance block_get_transaction
BlockMeta -> continuity-only slot/parent sans status
Slot -> continuity-only slot/parent/status
mapping exact Processed/Confirmed/Finalized/FirstShredReceived/Completed/CreatedBank/Dead
Debug continuity sans filter names ni RAW material
scanner continuity sans signature/transaction/meta/payload/body/error/dead_error
impls réels YellowstoneBlockUpdate/YellowstoneBlockMetaUpdate/YellowstoneSlotUpdate présents
aucun open_standard_subscribe/next_update/get_block_observed
aucune dépendance directe Config/Backfill/backend/reqwest/tonic/proto
```
## Décisions prises
```text
Block est une troisième famille de discovery/hydration, pas un second pipeline RAW
BlockMeta et Slot sont continuity-only
les compteurs BlockMeta ne prouvent jamais la complétude
dead_error ne traverse jamais la frontière Worker
getBlock reste hors P0
reconnect/from_slot/ReplayInfo restent pre.008
activation source productive et coalescence restent pre.006
```
## Questions ouvertes
Aucune question ouverte ne bloque `pre.005`.
Restent réservés :
```text
source task productive + coalescence : pre.006
processing frontier run-local : pre.007
reconnect/from_slot/ReplayInfo : pre.008
races/retry/backpressure : pre.009
```
## Fichiers ajoutés
```text
deltas/0.3.12/pre.005.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## 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
```
Les scanners source ciblés vérifient également l'absence d'I/O live prématurée, de `get_block_observed`, de `dead_error` dans l'adapter Slot et de dépendances d'implémentation directes interdites.
## Validations non exécutées dans l'environnement d'assemblage
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun résultat Cargo local n'est déclaré PASS.
Gate opérateur requis :
```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-raw-transaction-ingest-lib
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
```
## Non-claims
```text
pas de source Yellowstone productive
pas de stream ouvert
pas de getBlock
pas de RAW direct depuis Block
pas de RAW depuis BlockMeta/Slot
pas de coalescence productive
pas de frontier
pas de reconnect/replay/repair Worker
pas de smoke live Worker
```

View File

@@ -0,0 +1,96 @@
<!-- file: deltas/0.3.12/pre.006-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.006-fix.001` — retours explicites et guard réseau pré-hydration
## Base requise
```text
0.3.12-pre.006
workspace.package.version = 0.3.12-pre.6
```
Le gate opérateur communiqué confirme que les audits et `cargo check --workspace` passent. Les 58 unit tests s'exécutent, avec 57 PASS et un unique échec sur le contexte du guard réseau. Le gate Clippy strict échoue sur trois `clippy::implicit-return`.
## Objectif
Corriger exclusivement ces diagnostics de `pre.006`, sans neutraliser de lint et sans avancer les responsabilités `pre.007`/`pre.008`.
## Version
```text
workspace.package.version = 0.3.12-pre.6.fix.1
```
## Corrections
La branche neutre `Account/Ping/Pong/Entry` du routage Yellowstone retourne maintenant explicitement `Ok(())`.
La closure de sélection de la prochaine hydration pending utilise des `return` explicites dans ses deux branches, conformément au lint workspace `clippy::implicit-return`.
`hydration_key` rejette désormais avant coalescence et avant HTTP :
```text
signal.network != source.network -> hydration.network_mismatch
```
Ce guard restaure le contrat de qualification `pre.004`. Le contrôle équivalent de `finalize_yellowstone_hydration` reste volontairement présent comme défense secondaire. Aucun test n'est affaibli pour accepter `hydration.network_key_mismatch`.
## Fichiers ajoutés
```text
deltas/0.3.12/pre.006-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## Non-changements
```text
aucune API publique
aucune dépendance
aucun changement de clé de coalescence
aucun changement de provenance
aucune modification des bornes pending/in-flight
aucune modification de la supervision JoinSet
aucun processing frontier
aucun from_slot / ReplayInfo / reconnect policy
aucun repair/backfill implicite
aucun lint neutralisé
```
## Validations exécutées dans l'environnement d'assemblage
```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
```
Contrôles statiques complémentaires : absence des trois retours implicites signalés par le gate, guard `hydration.network_mismatch` présent dans `hydration_key`, version workspace exacte et reproduction du delta sur la base `pre.006`.
## Validations non exécutées dans l'environnement d'assemblage
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage. Aucun gate Cargo local n'est revendiqué.
## Gate opérateur requis
```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-raw-transaction-ingest-lib
```
Après gate vert, la suite reste `pre.007`.

219
deltas/0.3.12/pre.006.md Normal file
View File

@@ -0,0 +1,219 @@
<!-- file: deltas/0.3.12/pre.006.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.006` — source Yellowstone productive + coalescence bornée
## Base requise
```text
0.3.12-pre.005-fix.001
workspace.package.version = 0.3.12-pre.5.fix.1
```
Le gate opérateur communiqué pour cette base est entièrement vert : audits Rust/Markdown, `cargo check --workspace`, Clippy strict et 56 tests Worker avec toutes les suites d'intégration associées.
## Objectif
Ouvrir la première source Yellowstone réellement productive en réutilisant exclusivement les moteurs déjà qualifiés :
```text
Transport Yellowstone session
adapters Transaction/Status/Block
hydration HTTP getTransaction observed
coalescence bornée
RawTransactionIngress existant
SourceTasks JoinSet existant
supervisor/admission/persistence existants
```
La tranche ne crée ni processing frontier, ni politique Worker de reconnect/from_slot/ReplayInfo, ni repair historique.
## Version
```text
workspace.package.version = 0.3.12-pre.6
```
## Source task productive
`RawTransactionIngestWorker::start_with_runtime_resources` consomme maintenant les ressources validées et ajoute exactement un child source au `JoinSet` privé existant.
Le child appelle uniquement la façade Transport :
```text
YellowstoneGrpcChannel::open_standard_subscribe
SolanaYellowstoneGrpcSubscribeSession::next_update
SolanaYellowstoneGrpcSubscribeSession::close
```
Aucun client `tonic`, `reqwest`, proto Yellowstone ou SDK provider n'est instancié par le Worker.
L'ouverture est stop-preemptible. Après stop/fault, les hydrations privées sont abort/join et la session est fermée via Transport. Toute erreur distante est réduite à un `ErrorCode` KSP sûr avant de remonter au supervisor, qui conserve son mapping stable `source_failed`.
## Activation des adapters
Les adapters `Transaction`, `TransactionStatus`, `Block`, `BlockMeta` et `Slot` sortent de `#[cfg(test)]` car ils possèdent désormais un consumer productif réel, conformément à `RUST-API-008`.
Le routage est :
```text
Transaction/TransactionStatus/Block -> candidat hydration
BlockMeta/Slot -> continuity-only, aucune admission RAW
Account/Entry/Ping/Pong -> ignorés par le vertical RAW
```
Le wrapper fixture `hydrate_yellowstone_signal` reste test-only ; le chemin productif sépare fetch HTTP et finalisation afin de permettre le fan-out coalescé.
## Coalescence
La clé est exactement `(network, signature, commitment)`.
Un `RawTransactionIngestHydrationCoordinator` privé possède :
```text
BTreeMap pending
JoinSet hydration
max_in_flight = settings.persistence_concurrency()
max_pending_signals = MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY
```
Des signaux Transaction/Status/Block identiques partagent donc un seul `getTransaction` in-flight. À la complétion, la réponse HTTP observée est finalisée séparément pour chaque signal afin de conserver family/filter/observed_at et donc des provenances distinctes.
Aucun cache terminé n'est conservé. La saturation applique de la backpressure au stream et ne provoque aucun drop silencieux.
## Hydration/admission
Le fetch reste strictement :
```text
get_transaction_observed
base64
confirmed | finalized
maxSupportedTransactionVersion = 0
```
La finalisation conserve tous les guards `pre.004` : network, source route, slot, transaction index, signature wire et provenance composite depuis le winner HTTP réel. `null` ne produit aucun ingress.
Les ingresses disponibles passent uniquement par le sender central créé par `RawTransactionAdmission::new`; la persistence Normal reste inchangée.
## Continuité
`BlockMeta` et `Slot` sont réellement reçus/projetés dans le source task, mais restent continuity-only. Aucune frontier n'est maintenue et aucun gap n'est interprété dans cette tranche.
Le snapshot reconnect/replay de Transport n'est pas encore consommé par le Worker ; cette responsabilité reste `pre.008`.
## API / dépendances
```text
aucun nouvel export public
aucun nouveau module
aucune nouvelle dépendance Cargo
Worker -> Transport reste l'unique edge live
Worker -> Config/Backfill/Store backend reste interdit
```
## Canaris ajoutés/ajustés
```text
source runtime réellement supervisée par SourceTasks
open_standard_subscribe/next_update/close présents
coalescence key network/signature/commitment
Transaction + Status identiques -> une pending key / une hydration task
bounded pending/in-flight
stop preemptible
abort/join hydration
aucun getBlock/get_block_observed
aucun remote error/dead_error/secret
public API et module inventory inchangés
anciens canaris pre.003-pre.005 ajustés de offline -> private/Transport-only
```
## Décisions prises
```text
coalescence productive dans le Worker, retry réseau reste Transport
concurrence hydration bornée par le budget de concurrence runtime déjà validé
pending total borné par la borne maximale d'admission publiée
fan-out après fetch partagé, avant admission
source close via Transport uniquement
frontier/reconnect/replay non anticipés
```
## Questions ouvertes
Aucune question ouverte ne bloque `pre.006`.
Restent réservés :
```text
processing frontier run-local : pre.007
reconnect/from_slot/ReplayInfo : pre.008
races/retry/backpressure final : pre.009
cross-layer completeness/security : pre.010
live opt-in : pre.011
```
## Fichiers ajoutés
```text
deltas/0.3.12/pre.006.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## Validations exécutées dans l'environnement d'assemblage
```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
```
Des scanners statiques ciblés vérifient également le confinement Transport, l'absence de `get_block_observed`, l'absence de dépendances directes interdites, la clé de coalescence et l'inventaire public/module inchangé.
## Validations non exécutées dans l'environnement d'assemblage
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage. Aucun gate Cargo local n'est revendiqué.
## Gate opérateur requis
```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-raw-transaction-ingest-lib
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
```
## Non-claims
```text
pas de processing frontier
pas d'interprétation reconnect/replay Worker
pas de from_slot Worker
pas de ReplayInfo Worker
pas de détection de gap de rétention
pas de repair/backfill implicite
pas de getBlock
pas de nouveau backend/Config edge
pas de smoke live revendiqué
```

View File

@@ -0,0 +1,107 @@
<!-- file: deltas/0.3.12/pre.007-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.007-fix.001` — correction de propagation de la processing frontier
## Base requise
```text
0.3.12-pre.007
workspace.package.version = 0.3.12-pre.7
```
Le gate opérateur de `pre.007` a validé les audits Rust/Markdown puis a échoué pendant `cargo check`/Clippy sur la propagation interne du receiver frontier et sur deux callsites test-only encore sur l'ancienne arité de `queue_signal`.
## Objectif
Corriger uniquement ces défauts d'intégration sans modifier le contrat fonctionnel de la processing frontier `pre.007`.
## Version
```text
workspace.package.version = 0.3.12-pre.7.fix.1
```
## Modifications
### Runtime supervisor
`processing_frontier_receiver` est désormais passé à `supervise_until_stop`, qui est le seul chemin qui le consomme via `wait_processing_frontier` et `record_processing_frontier`.
Le paramètre ajouté par erreur à `drain_admission_and_persistence` est retiré ; le drain reste inchangé fonctionnellement.
### Canari coalescence
Le test `pre_006_coalescence_key_merges_transaction_and_status_before_http_fanout` construit désormais un `RawTransactionIngestProcessingFrontierReporter` privé sur un `watch` local et le passe aux deux appels `queue_signal`.
Le comportement testé reste identique : les signaux Transaction et TransactionStatus identiques coalescent vers une seule hydration en vol.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichier ajouté
```text
deltas/0.3.12/pre.007-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Invariants préservés
Le correctif ne modifie pas :
```text
définition pending / settled
compaction des slots
processing_frontier_slot
oldest_pending_slot
hydration_pending
source Yellowstone productive
coalescence key
hydration HTTP
admission / persistence
API publique
reconnect / from_slot / ReplayInfo / repair
```
## Validations exécutées dans l'environnement d'assemblage
```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
contrôle statique des callsites drain_admission_and_persistence
contrôle statique des callsites supervise_until_stop
contrôle statique des callsites queue_signal du canari de coalescence
reconstruction du delta sur base pre.007
unzip -t de l'archive finale
```
## Validations non exécutées localement
L'environnement d'assemblage ne fournit pas Cargo/rustc/rustfmt. Les gates Cargo doivent être rejoués par l'opérateur :
```text
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Décisions
Aucune nouvelle décision fonctionnelle.
## Questions ouvertes
Aucune nouvelle question ouverte introduite par ce fix.

View File

@@ -0,0 +1,108 @@
<!-- file: deltas/0.3.12/pre.007-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.007-fix.002` — correction du canari d'inventaire des watches
## Base requise
```text
0.3.12-pre.007-fix.001
workspace.package.version = 0.3.12-pre.7.fix.1
```
Le gate opérateur de `pre.007-fix.001` a validé les audits Rust/Markdown, `cargo check`, Clippy strict et les 61 tests unitaires Worker. L'unique échec restant provient du test d'intégration `dependency_boundary`, qui comptait encore exactement deux `tokio::sync::watch::channel` dans `runtime.rs`.
Depuis `pre.007`, une troisième watch run-local transporte légitimement la projection latest-value de processing frontier.
## Objectif
Corriger uniquement ce canari structurel obsolète, sans modifier le runtime ni le contrat fonctionnel de la processing frontier.
## Version
```text
workspace.package.version = 0.3.12-pre.7.fix.2
```
## Modifications
### Canari `dependency_boundary`
Le test distingue désormais explicitement les usages de watch dans `runtime.rs` :
```text
2 x watch::channel(false) : stop external-control + stop private-source
1 x watch::channel(RawTransactionIngestProcessingFrontierProjection::empty()) : frontier run-local latest-value
3 x watch::channel au total dans runtime.rs
```
Le canari conserve aussi l'invariant d'une seule watch de snapshot concret dans `snapshot.rs`.
Aucun test n'est affaibli : le contrôle devient plus précis qu'un simple comptage global.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichier ajouté
```text
deltas/0.3.12/pre.007-fix.002.md
```
## Fichiers supprimés
```text
aucun
```
## Invariants préservés
Le correctif ne modifie pas :
```text
runtime.rs
pending / settled
compaction des slots
processing_frontier_slot
oldest_pending_slot
hydration_pending
source Yellowstone productive
coalescence / hydration HTTP
admission / persistence
API publique
reconnect / from_slot / ReplayInfo / repair
```
## Validations exécutées dans l'environnement d'assemblage
```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
contrôle statique de l'inventaire des watches runtime/snapshot
reconstruction du delta sur base pre.007-fix.001
unzip -t de l'archive finale
```
## Validations non exécutées localement
L'environnement d'assemblage ne fournit pas Cargo/rustc/rustfmt. Les gates Cargo doivent être rejoués par l'opérateur :
```text
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Décisions
Aucune nouvelle décision fonctionnelle.
## Questions ouvertes
Aucune nouvelle question ouverte introduite par ce fix.

216
deltas/0.3.12/pre.007.md Normal file
View File

@@ -0,0 +1,216 @@
<!-- file: deltas/0.3.12/pre.007.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.007` — processing frontier run-local bornée
## Base requise
```text
0.3.12-pre.006-fix.001
workspace.package.version = 0.3.12-pre.6.fix.1
```
Le gate opérateur communiqué pour cette base est entièrement vert : audits Rust/Markdown, `cargo check --workspace`, Clippy strict, 58 tests Worker et toutes les suites d'intégration associées.
## Objectif
Ajouter la processing frontier run-local prévue au plan sans anticiper reconnect/replay/repair :
```text
source work observed
pending / settled par slot
oldest pending slot
highest unblocked actually-observed settled slot
latest-value snapshot projection
```
La frontier reste strictement processing-only.
## Version
```text
workspace.package.version = 0.3.12-pre.7
```
## Définition pending / settled
Un signal transactionnel devient pending seulement après validation de sa clé d'hydration et insertion dans le coordinator borné.
Il devient settled lorsque :
```text
hydration HTTP -> Missing
ou
hydration HTTP -> Available puis ingress accepté par la queue centrale
```
`BlockMeta` et `Slot` sont continuity-only et deviennent settled dès leur projection locale, sans créer de RAW.
Un signal non admis à cause d'un stop ou d'un fault reste pending dans la dernière projection de run. `settled` ne signifie jamais Store persisted/durable.
## Frontier
Le tracker privé maintient :
```text
pending_total
pending par slot
settled par slot retenu
oldest_pending_slot
processing_frontier_slot
```
La frontier est la plus haute slot réellement observée/settled située avant la plus ancienne slot encore pending. Sans pending, elle devient la plus haute slot settled effectivement observée.
Ainsi, une slot pending empêche explicitement toute progression à travers elle.
## Borne mémoire
Les slots pending sont conservées exactement. Les slots settled-only sont compactées par intervalles autour des slots pending : un seul maximum settled est conservé par intervalle.
La représentation est donc bornée linéairement par le nombre de slots pending distinctes, lui-même borné par le coordinator d'hydration. Un flux continuity-only sans pending se compacte à une seule candidate.
Aucun historique de slots non borné n'est conservé.
## Projection snapshot
Le source task émet une projection privée latest-value par `tokio::sync::watch` :
```text
hydration_pending
processing_frontier_slot
oldest_pending_slot
```
Le supervisor reste l'unique owner/mutateur du snapshot concret et applique ces valeurs via `RawTransactionIngestSnapshotPublisher`.
Les trois getters deviennent publics sur `RawTransactionIngestSnapshot`. Aucun nouveau type public ni module public n'est ajouté.
`WorkerActivity` considère désormais `hydration_pending > 0` comme activité réelle même si la queue centrale et la persistence sont momentanément vides.
## Sécurité / redaction
La projection ne transporte jamais :
```text
signature
filter id/value
provider
endpoint
transaction/meta
URL/header/credential
remote error text
```
Les erreurs de compteur/frontier utilisent uniquement des codes/contextes internes sûrs.
## Canaris ajoutés/ajustés
```text
frontier ne traverse jamais une slot pending
oldest pending avance après settlement
frontier rejoint la plus haute slot settled lorsque tout pending est résolu
compaction bornée des settled-only
snapshot initial frontier vide
snapshot latest-value des trois getters
WorkerActivity active sur hydration pending
public getters exacts
source.run inclut le publisher frontier privé
aucun ReplayInfo/from_slot/continuity_gap/repair
aucun backend/direct protocol edge
release completeness inclut les nouveaux canaris
```
## Décisions prises
```text
frontier = source-processing, pas Store durability
Missing = settled source-processing
Available = settled après send admission réussi
continuity-only = settled immédiat local
projection supervisor via watch latest-value
pas de checkpoint persistant
pas de claim monotone de blockchain completeness
```
## Questions ouvertes
Aucune question ne bloque `pre.007`.
Restent réservés :
```text
reconnect / from_slot / ReplayInfo / retention gap : pre.008
races/retry/backpressure final : pre.009
cross-layer completeness/security : pre.010
live opt-in : pre.011
```
## Fichiers ajoutés
```text
deltas/0.3.12/pre.007.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## Validations exécutées dans l'environnement d'assemblage
```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
```
Des scanners statiques ciblés vérifient également la borne du tracker, le blocage par oldest pending, les getters publics, le confinement Transport et l'absence de responsabilités `pre.008`.
## Validations non exécutées dans l'environnement d'assemblage
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage. Aucun gate Cargo local n'est revendiqué.
## Gate opérateur requis
```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-raw-transaction-ingest-lib
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
```
## Non-claims
```text
pas de reconnect policy Worker
pas de from_slot Worker
pas d'interprétation ReplayInfo
pas de preuve replay
pas de continuity gap fault
pas de repair/backfill implicite
pas de checkpoint inter-process
pas de getBlock
pas de nouveau backend/Config edge
pas de smoke live revendiqué
```

View File

@@ -0,0 +1,107 @@
<!-- file: deltas/0.3.12/pre.008-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.008-fix.001` — correction du faux positif hardening `historical`
## Base requise
```text
0.3.12-pre.008
workspace.package.version = 0.3.12-pre.8
```
Le gate opérateur de `pre.008` valide les audits Rust/Markdown, `cargo check`, Clippy strict, 388 tests unitaires Transport, 51 `public_api` Transport, 43 `release_completeness` Transport, 63 tests unitaires Worker et 9 `dependency_boundary` Worker.
L'unique échec est le canari `pre_010_production_surface_has_no_historical_backfill_or_retriever_contract`, car la documentation de crate Worker contient la phrase `historical repair remains outside this crate`.
## Objectif
Corriger uniquement ce faux positif documentaire sans modifier ni affaiblir le canari hardening.
## Version
```text
workspace.package.version = 0.3.12-pre.8.fix.1
```
## Modification
Dans `ksp-worker-raw-transaction-ingest-lib/src/lib.rs` :
```text
historical repair remains outside this crate
```
devient :
```text
history repair remains outside this crate
```
Le token `historical` reste interdit par le canari existant. Aucun code productif, contrat, dépendance, type, import ou comportement n'est modifié.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichier ajouté
```text
deltas/0.3.12/pre.008-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Invariants préservés
Le correctif ne modifie pas :
```text
Transport reconnect/from_slot/ReplayInfo
projection source-neutral Worker
processing frontier run-local
coalescence/hydration HTTP
admission/persistence
politique proven continuity gap -> source fault
API publique
backfill/repair
```
## Validations exécutées dans l'environnement d'assemblage
```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
canari statique des tokens historiques/backfill/retriever dans src/
contrôle diff exact contre pre.008
reconstruction du delta sur la base pre.008
unzip -t de l'archive finale
```
## Validations non exécutées localement
L'environnement d'assemblage ne fournit pas Cargo/rustc/rustfmt. Le gate opérateur doit être rejoué :
```text
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Décisions
Le canari hardening reste strict ; une phrase documentaire ne doit pas utiliser un token réservé à l'absence de surface historique si cela provoque un faux positif structurel.
## Questions ouvertes
Aucune.

View File

@@ -0,0 +1,105 @@
<!-- file: deltas/0.3.12/pre.008-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.008-fix.002` — correction du faux positif `public_api` sur `repair`
## Base requise
```text
0.3.12-pre.008-fix.001
workspace.package.version = 0.3.12-pre.8.fix.1
```
Le gate opérateur valide les audits, `cargo check`, Clippy strict, l'ensemble Transport, 63 tests unitaires Worker, 9 `dependency_boundary` et 15 `hardening`. L'unique échec restant est le canari `v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only`, car la documentation de crate Worker contient encore le token `repair`.
## Objectif
Corriger uniquement ce faux positif documentaire sans modifier ni affaiblir le canari `public_api` de `pre.007`.
## Version
```text
workspace.package.version = 0.3.12-pre.8.fix.2
```
## Modification
Dans `ksp-worker-raw-transaction-ingest-lib/src/lib.rs` :
```text
history repair remains outside this crate
```
devient :
```text
history remediation remains outside this crate
```
Le token `repair` reste interdit par le canari `pre.007` existant. Aucun code productif, contrat, dépendance, type, import ou comportement n'est modifié.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichier ajouté
```text
deltas/0.3.12/pre.008-fix.002.md
```
## Fichiers supprimés
```text
aucun
```
## Invariants préservés
Le correctif ne modifie pas :
```text
Transport reconnect/from_slot/ReplayInfo
projection source-neutral Worker
processing frontier run-local
coalescence/hydration HTTP
admission/persistence
politique proven continuity gap -> source fault
API publique
backfill/repair
```
## Validations exécutées dans l'environnement d'assemblage
```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
canari statique exact des tokens interdits dans src/lib.rs
contrôle diff exact contre pre.008-fix.001
reconstruction du delta sur la base pre.008-fix.001
unzip -t de l'archive finale
```
## Validations non exécutées localement
L'environnement d'assemblage ne fournit pas Cargo/rustc/rustfmt. Le gate opérateur doit être rejoué :
```text
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Décisions
Le canari `pre.007` reste strict et inchangé. La documentation de racine publique évite les tokens réservés aux surfaces explicitement absentes.
## Questions ouvertes
Aucune.

205
deltas/0.3.12/pre.008.md Normal file
View File

@@ -0,0 +1,205 @@
<!-- file: deltas/0.3.12/pre.008.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.008` — reconnect / replay / gap de rétention prouvé
## Base requise
```text
0.3.12-pre.007-fix.002
workspace.package.version = 0.3.12-pre.7.fix.2
```
Le gate opérateur communiqué pour cette base est entièrement vert : audits Rust/Markdown, `cargo check --workspace`, Clippy strict, 61 tests unitaires Worker, 8 `dependency_boundary`, 14 `hardening`, 9 `public_api`, 3 `release_completeness` et doc-tests sans échec.
## Objectif
Brancher la continuité Yellowstone déjà possédée par Transport dans le Worker sans dupliquer la politique de replay :
```text
Transport reconnect snapshot
-> Worker source-neutral latest-value projection
-> reconnect/replay/gap observability
-> proven retention gap => source fault
```
Le Worker ne calcule ni `from_slot`, ni `SubscribeReplayInfo.first_available`; il ne déclenche aucun repair historique.
## Version
```text
workspace.package.version = 0.3.12-pre.8
```
## Façade Transport ajoutée
`ksp-onchain-transport-lib` expose désormais :
```text
YellowstoneGrpcSubscribeSnapshotSource
SolanaYellowstoneGrpcSubscribeSession::snapshot_source()
YellowstoneGrpcSubscribeSnapshotSource::current()
YellowstoneGrpcSubscribeSnapshotSource::wait_for_change()
```
Cette façade encapsule le `tokio::sync::watch::Receiver` déjà possédé par la session. Elle n'expose ni Tonic, ni protobuf upstream, ni actor interne, ni credentials.
Le snapshot Transport reste la source autoritaire pour :
```text
state
reconnect_count
replay_attempt_count
continuity_gap_count
```
La sélection/clamp de `from_slot` et l'appel `SubscribeReplayInfo` restent exclusivement dans Transport.
## Projection Worker
Le snapshot Worker ajoute le type public source-neutral :
```text
RawTransactionIngestSourceState
Active
Reconnecting
Closing
Closed
Failed
```
et les getters :
```text
source_state()
source_reconnect_total()
source_replay_attempt_total()
source_continuity_gap_total()
```
La même watch privée que la processing frontier transporte ces latest-values ; aucune nouvelle watch Worker n'est ajoutée.
Les champs processing-only `hydration_pending`, `processing_frontier_slot` et `oldest_pending_slot` restent inchangés et sont conservés pendant reconnect.
## Politique reconnect/replay
Le source task :
```text
ouvre la session Yellowstone via Transport
obtient snapshot_source()
publie le snapshot initial Active
observe les changements Transport en parallèle de next_update et des hydrations
continue les hydrations déjà en vol pendant Reconnecting
laisse le stop préempter reconnect/hydration/admission
```
`replay_attempt_count` et `reconnect_count` restent distincts. Un replay attempt peut être observé alors qu'aucun reconnect n'a encore réussi.
## Gap de rétention prouvé
Le Worker ne fault que si `continuity_gap_count` augmente.
Transport incrémente ce compteur uniquement lorsqu'il prouve :
```text
SubscribeReplayInfo.first_available > requested replay slot
```
La hausse est publiée dans le snapshot Worker, puis le source task retourne `source.continuity_gap_proven`; le supervisor classe ensuite le terminal en `ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED`.
Cette preuve reste conservatrice : elle prouve une couverture de replay demandée devenue indisponible, pas l'existence certaine d'un update filtré perdu.
Les compteurs Transport observés doivent être monotones ; une régression impossible est rejetée avec `source.continuity_counter_regression`.
## Non-claims
`pre.008` n'ajoute pas :
```text
repair/backfill automatique
source secondaire ou failover multi-provider
checkpoint durable inter-process
getBlock de repair
retry Worker spécifique getTransaction null
exactly-once
preuve de continuité blockchain globale
ownership Worker de from_slot
ownership Worker de SubscribeReplayInfo
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/grpc_stream.rs
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichier ajouté
```text
deltas/0.3.12/pre.008.md
```
## Fichiers supprimés
```text
aucun
```
## Validations exécutées dans l'environnement d'assemblage
```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
canaris statiques pre.008 replay/frontier/redaction/public API
contrôle des versions de fichiers modifiés
contrôle diff exact contre pre.007-fix.002
unzip -t du delta final
reproduction du delta sur la base exacte
```
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun `cargo check`, Clippy ou test Cargo local n'est déclaré PASS ici.
## Gate opérateur requis
```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-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
## Décisions durables
```text
Transport reste propriétaire de reconnect/from_slot/ReplayInfo.
Worker observe les résultats sûrs, il ne réimplémente pas la stratégie Transport.
reconnect != replay attempt != successful reconnect != proven retention gap.
proven retention gap => fault/stop, jamais repair implicite dans 0.3.12.
processing frontier reste run-local et processing-only.
```
## Suite
`pre.009` reste consacré au hardening races/retry/backpressure : stop pendant hydration/reconnect, duplicate storm, Store lent, source failure, counter exhaustion et no-orphan, sans élargissement fonctionnel.

View File

@@ -0,0 +1,97 @@
<!-- file: deltas/0.3.12/pre.009-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.009-fix.001` — correction du canari duplicate-storm
## Base requise
```text
0.3.12-pre.009
workspace.package.version = 0.3.12-pre.9
```
## Objectif
Corriger l'unique échec du gate opérateur de `pre.009` sans modifier le runtime :
```text
pre_009_duplicate_storm_is_bounded_coalesced_and_abort_leaves_no_orphan_frontier
left: None
right: Some(9)
```
La predecessor `9` n'est pas une garantie du modèle borné. Après compaction, un pending tardif sur la candidate haute peut rendre la frontier conservative `None`. Le contrat requis est seulement qu'elle n'avance pas à travers le pending et qu'un abort ne l'avance pas artificiellement.
## Version
```text
workspace.package.version = 0.3.12-pre.9.fix.1
```
## Correction
Le canari conserve les preuves de saturation/coalescence/no-orphan mais remplace l'attente exacte `Some(9)` par les invariants :
```text
frontier pré-abort != Some(10)
sauvegarde de la frontier sûre pré-abort
abort_all -> 0 pending / 0 tâche
frontier post-abort == frontier sûre pré-abort
```
Aucun test n'est supprimé et aucune garantie productive n'est relâchée.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichier ajouté
```text
deltas/0.3.12/pre.009-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Non-changements
```text
aucun code productif modifié
aucune API publique modifiée
aucune sémantique frontier/coalescence/hydration modifiée
aucun changement Transport/Store/Config/Backfill/Common RAW
aucun nouveau retry ou repair
```
## Validations d'assemblage
```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
canari statique du test corrigé
contrôle diff exact contre pre.009
unzip -t
reproduction du delta sur pre.009
```
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage ; aucun gate Cargo local n'est revendiqué.
## Gate opérateur requis
```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-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
```

152
deltas/0.3.12/pre.009.md Normal file
View File

@@ -0,0 +1,152 @@
<!-- file: deltas/0.3.12/pre.009.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.009` — hardening races/retry/backpressure et no-orphan
## Base requise
```text
0.3.12-pre.008-fix.002
workspace.package.version = 0.3.12-pre.8.fix.2
```
Le gate opérateur communiqué pour cette base est entièrement vert : audits Rust/Markdown, `cargo check --workspace`, Clippy strict, 388 tests unitaires Transport, 51 `public_api` Transport, 43 `release_completeness` Transport, 63 tests unitaires Worker, 9 `dependency_boundary`, 15 `hardening`, 9 `public_api` et 3 `release_completeness` Worker sans échec.
## Objectif
Fermer les races terminales prévues pour `pre.009` sans élargir le pipeline :
```text
stop/fault pendant hydration
pending/coalescence abortée
processing frontier conservative
retry ownership Transport-only
duplicate storm bornée
counter exhaustion transactionnelle
no-orphan final
```
## Version
```text
workspace.package.version = 0.3.12-pre.9
```
## No-orphan source/hydration
`RawTransactionIngestHydrationCoordinator::abort_all` reçoit désormais le reporter de processing frontier. Le cleanup :
```text
abort_all des tâches hydration
join de toutes les tâches hydration
clear de la map pending
pending_signal_count = 0
discard_all_pending de la frontier
```
`discard_all_pending` ne convertit jamais un pending en settled. Toute slot qui contenait encore au moins un pending est retirée de la projection conservative, même si un autre signal de cette slot avait déjà été settled.
Cela empêche une frontier terminale d'avancer à travers du travail abandonné lors d'un stop, d'un source fault, d'une erreur hydration ou d'un gap de continuité prouvé.
## Arithmetic transactionnelle
Les mutations de `RawTransactionIngestProcessingFrontier` sont durcies :
```text
observe_pending : overflow vérifié avant mutation de l'entrée
observe_settled : overflow vérifié avant mutation de l'entrée
settle_pending : settled.checked_add avant décrément pending
```
Un `source.frontier_slot_settled_counter_exhausted` laisse donc le pending et la projection inchangés ; aucun état partiellement muté n'est publié.
## Duplicate storm et backpressure
Le canari source crée une petite borne déterministe et injecte plusieurs signaux identiques :
```text
hydration in-flight -> abort/join puis 0 tâche et 0 pending
3 signaux identiques -> 1 clé coalescée, 3 pending
4e signal -> source.hydration_pending_saturated
aucune tâche HTTP supplémentaire
abort -> 0 pending, 0 tâche, frontier inchangée au dernier niveau sûr
```
La borne productive existante reste inchangée et aucun canal unbounded n'est ajouté.
## Retry ownership
Le Worker conserve exactement un appel `get_transaction_observed` dans son chemin hydration et n'ajoute aucun `sleep`/`interval` de retry. Les retries réseau, backoff, rate-limit et reroutage restent exclusivement dans Transport.
Les tests runtime existants continuent de qualifier Store lent, source failure, stop pendant drain, saturation centrale, drain timeout et counter exhaustion snapshot.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichier ajouté
```text
deltas/0.3.12/pre.009.md
```
## Fichiers supprimés
```text
aucun
```
## Non-changements
`pre.009` n'ajoute ni ne modifie :
```text
API publique Worker
snapshot public
Store/Config/Backfill
Common RAW
Transport replay semantics
from_slot / ReplayInfo
repair/failover
retry Worker
checkpoint durable
```
## Validations exécutées dans l'environnement d'assemblage
```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
canaris statiques pre.009 no-orphan/retry ownership
contrôle des versions de fichiers modifiés
contrôle diff exact contre pre.008-fix.002
unzip -t du delta final
reproduction du delta sur la base exacte
```
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage. Aucun gate Cargo local n'est revendiqué.
## Gate opérateur requis
```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-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
## Suite
`pre.010` reste consacré à la completeness/security cross-layer : fixtures Legacy/V0, dependency firewall, public API exact, redaction, release completeness et scanners, sans nouveau scope fonctionnel.

68
deltas/0.3.12/pre.010.md Normal file
View File

@@ -0,0 +1,68 @@
<!-- file: deltas/0.3.12/pre.010.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.010`
## Base
`0.3.12-pre.009-fix.001`.
## Objectif
Fermer la tranche `pre.010` par un audit cross-layer déterministe de complétude et de sécurité entre Transport, Worker RawTransaction, Common RAW et Store, sans ajouter de comportement runtime.
## Fichiers ajoutés
- `crates/ksp-worker-raw-transaction-ingest-lib/tests/cross_layer_completeness.rs` ;
- `deltas/0.3.12/pre.010.md`.
## Fichiers modifiés
- `Cargo.toml` ;
- `crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs` ;
- `docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md`.
## Fichiers supprimés
Aucun.
## Décisions
- la version workspace devient `0.3.12-pre.10` ;
- aucun fichier Rust de production n'est modifié ;
- le firewall de dépendances est vérifié sur les manifests Transport, Worker, Common RAW, Store API et Store façade ;
- les fixtures Legacy/V0 déjà matérialisées dans Transport/Worker/Common RAW deviennent une matrice de complétude explicitement vérifiée ;
- les scanners/redaction déjà présents dans chaque couche sont vérifiés comme matrice de sécurité cross-layer ;
- l'inventaire public Worker reste strictement inchangé ;
- aucun nouveau comportement de reconnect, replay, hydration, persistence, repair ou checkpoint n'est introduit.
## Validations exécutées dans l'environnement d'assemblage
- `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` ;
- canaris statiques `pre.010` ;
- vérification d'inventaire du delta ;
- `unzip -t` ;
- reproduction byte-à-byte du delta sur `0.3.12-pre.009-fix.001`.
## Validations non exécutées localement
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun PASS Cargo local n'est revendiqué.
## Gate opérateur requis
```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-onchain-transport-lib
cargo test -p ksp-raw-transaction-lib
cargo test -p ksp-store-api
cargo test -p ksp-store-lib --no-default-features
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```

71
deltas/0.3.12/pre.011.md Normal file
View File

@@ -0,0 +1,71 @@
<!-- file: deltas/0.3.12/pre.011.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.011`
## Base
`0.3.12-pre.010`.
## Objectif
Fermer la tranche de gate technique final et de live opt-in prévue par le plan : workspace complet, Clippy strict, suites ciblées, graphes Cargo, duplicates et smokes réellement accessibles, sans nouveau scope fonctionnel.
## Fichiers ajoutés
- `deltas/0.3.12/pre.011.md`.
## Fichiers modifiés
- `Cargo.toml` ;
- `docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md`.
## Fichiers supprimés
Aucun.
## Décisions
- la version workspace devient `0.3.12-pre.11` ;
- aucun fichier Rust de production ou de test n'est modifié ;
- le gate `pre.010` réellement communiqué est enregistré sans revendiquer les commandes `cargo tree` ou workspace-all-targets qui n'étaient pas présentes dans ce log ;
- `pre.011` exige le workspace complet `--all-targets --all-features`, les suites cross-layer ciblées, les graphes Worker normal/features et `cargo tree --duplicates` ;
- les smokes HTTP/WS Devnet sans secret sont distingués des smokes Yellowstone token-gated ;
- les tokens Yellowstone restent lus sur stdin et ne doivent pas être passés en argument CLI ni copiés dans les logs/deltas ;
- aucun Worker live smoke artificiel n'est ajouté sans secret Yellowstone, hydration HTTP cohérente et Store réellement provisionné ;
- un smoke non exécutable faute de secret reste `NON EXÉCUTÉ`, jamais PASS ;
- aucun comportement reconnect/replay, frontier, hydration, persistence, Store, Config ou Backfill n'est modifié.
## Validations exécutées dans l'environnement d'assemblage
- `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` ;
- vérification statique de l'inventaire des smokes et de leur caractère `#[ignore]` ;
- vérification d'inventaire du delta ;
- `unzip -t` ;
- reproduction byte-à-byte du delta sur `0.3.12-pre.010`.
## Validations non exécutées localement
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun PASS Cargo ou live local n'est revendiqué.
## Gate opérateur déterministe 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-onchain-transport-lib
cargo test -p ksp-raw-transaction-lib
cargo test -p ksp-store-api
cargo test -p ksp-store-lib --no-default-features
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
Les commandes live opt-in sont documentées dans `docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md` et leurs résultats doivent être enregistrés séparément du gate déterministe.

71
deltas/0.3.12/pre.012.md Normal file
View File

@@ -0,0 +1,71 @@
<!-- file: deltas/0.3.12/pre.012.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.012`
## Base
`0.3.12-pre.011`.
## Objectif
Réconcilier la documentation durable du Worker RawTransaction avec la verticale Yellowstone + hydration HTTP + continuité réellement matérialisée et avec les preuves `pre.011`, sans changer le runtime ni préparer encore la publication.
## Fichiers ajoutés
- `deltas/0.3.12/pre.012.md`.
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `crates/ksp-worker-raw-transaction-ingest-lib/README.md` ;
- `crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md` ;
- `docs/architecture/004-COMPONENT_INVENTORY.md` ;
- `docs/architecture/005-DEPENDENCY_GRAPH.md` ;
- `docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md` ;
- `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md` ;
- `docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md`.
## Fichiers supprimés
Aucun.
## Décisions
- la version workspace devient `0.3.12-pre.12` ;
- aucun fichier Rust de production ou de test n'est modifié ;
- la documentation Worker décrit désormais la première source productive Yellowstone et l'hydration HTTP `getTransaction` ;
- `RawTransactionIngestWorker::start` reste la fondation sans source, `start_with_runtime_resources` est l'entrée productive ;
- les signaux `Transaction`, `TransactionStatus` et `Block` sont hydratés avant Common RAW ; `BlockMeta`/`Slot` restent continuity-only ;
- la processing frontier est explicitement run-local et non durable ;
- reconnect, `from_slot` et `SubscribeReplayInfo` restent propriétaires de Transport ;
- un gap de rétention prouvé provoque un fault Worker et ne déclenche aucun Backfill automatique ;
- le graphe durable matérialise l'edge Worker -> `ksp-onchain-transport-lib` sans déplacer Config, backend Store ou clients inférieurs dans le Worker ;
- le gate déterministe `pre.011` et les deux smokes keyless PASS sont enregistrés ;
- les smokes Yellowstone/Worker non réellement exécutés restent explicitement NON EXÉCUTÉS ;
- `CHANGELOG.md`, `ROADMAP.md` et le prompt suivant ne sont pas modifiés.
## Validations exécutées dans l'environnement d'assemblage
- `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` ;
- recherche des formulations durables obsolètes sur le Worker ;
- vérification d'inventaire du delta ;
- `unzip -t` ;
- reproduction byte-à-byte du delta sur `0.3.12-pre.011`.
## Validations non exécutées localement
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun PASS Cargo local n'est revendiqué pour `pre.012`.
## Gate opérateur 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 -p ksp-worker-raw-transaction-ingest-lib
```

174
deltas/0.3.12/pre.013.md Normal file
View File

@@ -0,0 +1,174 @@
<!-- file: deltas/0.3.12/pre.013.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.013` — préparation de publication
## 1. Base requise
```text
0.3.12-pre.012
```
Le gate opérateur de `pre.012` est vert sur rustfmt check, audits Rust/Markdown, `cargo check --workspace`, Clippy strict, 66 tests Worker et toutes les suites Worker boundary/hardening/public API/completeness.
Le gate technique `pre.011` précédent est déjà qualifié : workspace all-targets/all-features, suites ciblées, graphes Cargo/duplicates et smokes live keyless HTTP Devnet + WebSocket Devnet PASS.
## 2. Objectif
`pre.013` est la dernière prerelease de préparation de publication de `0.3.12`.
Conformément à `VER-LIFECYCLE-003` et `PROMPT_STRUCTURE.md`, elle modifie fonctionnellement uniquement :
```text
prompt de démarrage 0.3.13
CHANGELOG.md
ROADMAP.md
```
Les seuls fichiers mécaniques supplémentaires sont :
```text
Cargo.toml racine
delta pre.013
```
Aucun README, USAGE, plan, validation, architecture, code, test, Config ou manifest de crate n'est rouvert.
## 3. Version workspace
```text
0.3.12-pre.12
->
0.3.12-pre.13
```
## 4. Prompt suivant
Le nouveau prompt :
```text
prompts/032-V0_3_13_START_PROMPT.md
```
ouvre `0.3.13` exclusivement depuis le futur stable :
```text
v0.3.12
```
Sa mission est bornée à :
```text
WS standard logsSubscribe + hydration
WS standard blockSubscribe
Helius transactionSubscribe + hydration
HTTP live block polling
convergence simultanée multi-source
observations multiples/coalescence/content conflict/backpressure
```
Le gap repair/hardening multi-source final reste explicitement `0.3.14`.
Le `pre.001` du prompt interdit tout codage lourd avant : audit de la base stable, réaudit courant Solana/Helius, matrice de matériau/hydration, architecture runtime resources multi-source, threat model, smokes accessibles, graphes, sizing et création du plan `034` + validation `030`.
## 5. CHANGELOG
`CHANGELOG.md` reçoit l'entrée `0.3.12` synthétisant :
```text
première source productive Yellowstone
hydration HTTP getTransaction observed
Common RAW + Store inchangés dans leurs rôles
processing frontier run-local
reconnect/from_slot/ReplayInfo Transport-owned
fault sur gap prouvé sans Backfill automatique
hardening duplicate/backpressure/shutdown
canaris cross-layer
workspace gate final PASS
smokes HTTP Devnet + WS Devnet PASS
smokes Yellowstone/Worker E2E non exécutés explicitement
handoff 0.3.13 / 0.3.14
prompt 032
```
## 6. ROADMAP
L'entrée `0.3.12` passe de `[ ]` à `[X]` et décrit l'état réellement livré. Les entrées `0.3.13` et `0.3.14` restent ouvertes et conservent leur séparation : convergence multi-source d'abord, repair multi-source ensuite.
## 7. Fichiers ajoutés
```text
prompts/032-V0_3_13_START_PROMPT.md
deltas/0.3.12/pre.013.md
```
## 8. Fichiers modifiés
```text
Cargo.toml
CHANGELOG.md
ROADMAP.md
```
## 9. Fichiers supprimés
```text
aucun
```
## 10. Surfaces explicitement inchangées
```text
README.md
docs/**
crates/**
config/**
tests/**
```
Hormis le nouveau prompt sous `prompts/`, aucun prompt existant n'est modifié.
## 11. Validations exécutées pendant l'assemblage
Sur l'état exact destiné à l'archive :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 table(s), 824 file(s))
diff exact : 3 fichiers modifiés + 2 ajoutés, 0 supprimé
headers/version workspace : PASS
prompt 032 : 16 sections normatives présentes
```
Les contrôles d'archive (`unzip -t`, archive safety et reproduction byte-à-byte sur `pre.012`) sont exécutés après construction du ZIP et doivent être verts avant livraison.
L'environnement d'assemblage ne revendique aucun Cargo PASS qui n'y a pas réellement été exécuté.
## 12. Gate opérateur minimum
Comme aucun code/test/dépendance/feature n'est modifié :
```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
```
Un Clippy/test workspace supplémentaire peut être exécuté par l'opérateur, mais `pre.013` ne doit pas réinterpréter le gate technique `pre.011` déjà validé.
## 13. Décisions prises
- `0.3.12` est prêt pour une publication stable mécanique après validation de `pre.013` ;
- `0.3.13` ne commence qu'après `0.3.12-rel.001` et le tag stable `v0.3.12` ;
- le prompt suivant maintient Worker -> Config interdit et impose la composition caller-owned des ressources live ;
- aucune correction d'un couloir technique/documentaire antérieur n'est absorbée dans cette tranche de publication.
## 14. Questions ouvertes
```text
aucune question bloquante pour la préparation de publication 0.3.12
```

162
deltas/0.3.12/rel.001.md Normal file
View File

@@ -0,0 +1,162 @@
<!-- file: deltas/0.3.12/rel.001.md -->
<!-- version: 1 -->
# Delta `0.3.12-rel.001` — publication stable
## Base requise
Base directe attendue :
```text
0.3.12-pre.013
workspace.package.version = 0.3.12-pre.13
```
Le gate opérateur minimal de `pre.013`, exécuté le **9 septembre 2026**, est propre :
```text
cargo fmt --all -- --check: PASS
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 table(s), 824 file(s))
cargo check --workspace: PASS
```
Les couloirs de fermeture antérieurs sont déjà fermés :
- `pre.011` : gate technique complet avec `cargo test --workspace --all-targets --all-features`, suites ciblées, graphes Cargo Worker normal/features et `cargo tree --duplicates` ;
- smokes live keyless exécutés après `pre.011` : HTTP Solana Devnet PASS et WebSocket Solana Devnet PASS ;
- `pre.012` : réconciliation documentaire finale des surfaces Yellowstone/hydration/frontier/replay et du statut réel des preuves live ;
- `pre.013` : préparation minimale de publication avec `CHANGELOG.md`, `ROADMAP.md` et `prompts/032-V0_3_13_START_PROMPT.md`.
## Objet
Publier mécaniquement la version stable :
```text
0.3.12
```
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
538 -> 539
```
La version workspace devient :
```text
0.3.12-pre.13 -> 0.3.12
```
Aucune autre ligne du `Cargo.toml` racine n'est modifiée.
## Fichiers ajoutés
```text
deltas/0.3.12/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/032-V0_3_13_START_PROMPT.md
crates/**
docs/**
config/**
```
La surface préparée en `pre.013` reste byte-identique hors mécanique Cargo et ajout du présent delta.
## Surface stable publiée
`0.3.12` stabilise la première verticale réseau productive de `ksp-worker-raw-transaction-ingest-lib` :
- source Yellowstone gRPC caller-composed et supervisée via `ksp-onchain-transport-lib` ;
- updates `Transaction`, `TransactionStatus` et transactions de `Block` projetées en signaux source-neutral ;
- coalescence bornée par identité `(network, signature, commitment)` avant hydration ;
- hydration HTTP `getTransaction` observed puis conversion Common RAW via `ksp-raw-transaction-lib` avant admission/persistence Store ;
- `BlockMeta` et `Slot` réservés à la continuité ;
- provenance composite sûre, sans URL, secret ni payload distant ;
- processing frontier strictement run-local, distincte d'une preuve durable de complétude ;
- reconnect/replay, `from_slot` et `SubscribeReplayInfo` conservés sous ownership Transport ;
- distinction explicite entre tentative de replay, reconnexion réussie et gap de rétention prouvé ;
- fault Worker sur gap prouvé, sans lancement automatique de Backfill ni campagne de réparation ;
- hardening des duplicate storms, overflow de compteurs, backpressure, stop/fault races, abort/join des hydrations et absence de tâches/frontier orphelines ;
- canaris cross-layer Transport -> Worker -> Common RAW -> Store, inventaires publics et dependency firewalls fermés ;
- gate workspace complet `--all-targets --all-features` validé ;
- smokes live keyless HTTP Devnet et WebSocket Devnet réellement PASS ;
- smokes Yellowstone token-gated et Worker end-to-end laissés explicitement non exécutés faute de ressources opérateur complètes lors du gate.
Le Job Backfill historique paramétré et le Worker Ingest continu restent deux producteurs indépendants du même Store. `0.3.12` ne transforme pas le Worker en moteur de campagne historique et ne persiste aucun checkpoint de replay Worker.
## Suite préparée
La session suivante part exclusivement de la base stable :
```text
v0.3.12
```
et exécute :
```text
prompts/032-V0_3_13_START_PROMPT.md
```
pour ouvrir :
```text
0.3.13 — WS standard / Helius / HTTP live + convergence multi-source
```
`0.3.13-pre.001` doit commencer par audit/sizing/planification avant tout développement lourd. Le gap repair/hardening multi-source complet reste réservé à `0.3.14`.
## Gate demandé
La tranche est purement mécanique. Aucune dépendance, feature, source runtime, configuration ou documentation durable 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.12
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
```
Après gate propre :
```text
commit : v0.3.12-rel.001
tag : v0.3.12
```
Aucun tag de prerelease ou `rel.001` n'est requis.

274
deltas/0.3.13/pre.001.md Normal file
View File

@@ -0,0 +1,274 @@
<!-- file: deltas/0.3.13/pre.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.001 — audit règles et convergence live multi-source
## Base requise
```text
archive : khadhroony-solana-project-v0.3.12.zip
SHA-256 : 85c41eb9f8575ee58edd9863445cbc37b79ef59c4a7cf882679ff24dc8991bf5
ZIP bytes : 8260528
ZIP entries : 1981
workspace.package.version base : 0.3.12
stable delta : deltas/0.3.12/rel.001.md
prompt : prompts/032-V0_3_13_START_PROMPT.md
```
## Objectif
Exécuter le gate `pre.001` de `0.3.13` avant tout développement : vérifier l'archive stable et les règles, réauditer les surfaces WS standard/Helius/HTTP actuelles, fermer l'architecture de convergence multi-source, les bornes, la stratégie d'observations multiples, le content conflict, les owners retry/reconnect, les smokes et le sizing.
Aucune nouvelle source réseau productive n'est implémentée dans cette tranche.
## Type de livraison
```text
0.3.13-pre.001
Cargo : 0.3.13-pre.1
archive : ksp-general-0.3.13-pre.001.zip
```
Le type `general` est requis par la synchronisation de `Cargo.toml` pour une prerelease non-fix.
## Vérification archive
Contrôlé :
```text
unzip -t : PASS
absolute entries : 0
parent traversal : 0
duplicate ZIP entries : 0
symlink entries : 0
Cargo.lock : absent
rust-toolchain.toml : absent
package lockfiles : absents
.env : absent
crate manifests : 21
workspace members : 21
crates hors workspace : 0
members sans crate : 0
workspace edition : 2024
```
## Audit règles
Les règles actives ont été relues depuis `RULES.md` et les neuf documents de `docs/rules/`.
Le script Rust stable reste vert, mais un contrôle supplémentaire d'unicité des identifiants normatifs a trouvé :
```text
rule ids : 489
unique ids : 488
collision : KSP-CONFIG-018 x2
```
La preuve historique `deltas/0.1.3/pre.014.md` attribue explicitement `KSP-CONFIG-018` à la règle d'inventaire `.env.example`. La règle desktop plus récente est donc renumérotée `KSP-CONFIG-019` sans modification de sa sémantique.
Après correction :
```text
rule ids : 489
unique ids : 489
duplicates : 0
```
Aucune autre contradiction normative active n'a été identifiée.
## Audit architecture/dépendances
Confirmé :
```text
Worker -> Transport autorisé et déjà productif
Worker -> Config interdit et absent
Worker -> Job Backfill interdit et absent
Worker -> backend Store physique interdit et absent
Worker -> reqwest/tonic/yellowstone proto direct absent
Transport -> Store absent
Transport -> Common RAW absent
ksp-store-lib reste la façade de persistence Worker
Common RAW reste propriétaire de la canonicalisation
```
`solana-keypair` dans `ksp-wallet-lib` est explicitement admis par `DEP-SOL-004` et ne constitue pas une violation.
## Audit WS / Helius / HTTP courant
Décisions :
```text
logsSubscribe standard -> signature/slot -> getTransaction observed
blockSubscribe standard -> full/base64, maxSupportedTransactionVersion=1
Helius transactionSubscribe -> full/base64 -> getTransaction observed
Helius LaserStream WSS ne porte pas blockSubscribe
HTTP live -> getSlot + getBlocksWithLimit + getBlock observed
```
La documentation Solana courante demande la préparation `maxSupportedTransactionVersion = 1` pour Transaction V1. V1 n'est pas encore actif sur les clusters publics au 10 septembre 2026, donc la voie `blockSubscribe` V1 reste deterministic-test-gated avant d'être déclarée RAW-direct.
Le pricing Helius courant place `transactionSubscribe` à partir du tier Developer ; ce fait reste une qualification de smoke, jamais une constante métier KSP.
## Architecture multi-source fermée
Décisions principales :
```text
sources simultanées, pas de first-provider-wins
source identity opaque SHA-256 et provider-neutral
1..32 sources runtime
collection validée avant tout spawn
coordinator global Worker avant hydration
coalescence (network, signature, commitment)
une hydration HTTP pour les références cross-source identiques
pending global <= admission_queue_capacity effective
hydratation in-flight <= persistence_concurrency
aucune queue Worker provider privée non bornée
```
Observation/persistence :
```text
identité durable = network + signature
première observation = persist_raw_transaction_acquisition atomique
observations supplémentaires = record_raw_transaction_observation
même identité + même contenu = idempotence
même identité + contenu divergent = content conflict terminal
```
La capability Store d'observation additionnelle existe déjà ; aucun changement Store API n'est requis.
## HTTP live polling
Contrat de plan retenu :
```text
run start = getSlot(commitment)
aucun scan avant ce start slot
getBlocksWithLimit pour exclure naturellement skipped slots
getBlock observed pour chaque bloc listé
poll interval : 100 ms..30 s, default 1 s
blocks/cycle : 1..1024, default 128
timer/cadence : Worker
retry/rate-limit HTTP : Transport
```
## Health et continuité
Le Worker conservera un inventaire de source privé et borné. La surface publique restera source-neutral avec des counts agrégés seulement.
Politique `0.3.13` conservative : une source configurée qui devient terminalement `Failed` faute le Worker, car l'équivalence de coverage des autres sources n'est pas prouvée. Le failover non-terminal et le repair appartiennent à `0.3.14`.
## Sizing retenu
```text
pre.002 runtime resources multi-source
pre.003 standard logs + hydration
pre.004 standard blockSubscribe
pre.005 Helius transactionSubscribe
pre.006 HTTP live polling
pre.007 supervisor/source inventory
pre.008 convergence/observations multiples
pre.009 conflict/backpressure/fairness
pre.010 snapshots/health
pre.011 races/shutdown
pre.012 completeness/security
pre.013 gate technique/live
pre.014 réconciliation documentaire
pre.015 préparation publication
rel.001 stable
```
Décision : `0.3.13` est maintenue ; aucune rescission n'est nécessaire avant `pre.002`.
## Fichiers ajoutés
```text
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
deltas/0.3.13/pre.001.md
```
## Fichiers modifiés
```text
Cargo.toml
docs/rules/RULES_KSP.md
```
## Fichiers supprimés
```text
aucun
```
## Validations exécutées avant modification
```text
python3 scripts/audit_rust_workspace_rules.py
PASS : General Rust rule audit clean
PASS : Rust export completeness audit 0 candidate
PASS : KSP workspace Rust rule audit clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.12
PASS : Markdown table audit clean (306 tables, 199 files)
```
La preuve Cargo stable fournie par l'opérateur montre également fmt/check/clippy/tests Worker/trees verts sur `0.3.12`; elle reste une preuve de la base et non une exécution locale de cette prerelease.
## Validations exécutées après modification
```text
python3 scripts/audit_rust_workspace_rules.py
PASS : General Rust rule audit clean
PASS : Rust export completeness audit 0 candidate
PASS : KSP workspace Rust rule audit clean
supplemental normative rule-id scan
PASS : 489 ids, 489 uniques, 0 duplicate
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
PASS : Markdown table audit clean (340 tables, 828 files)
```
## Validations non exécutées localement
Le toolchain Cargo/Rustfmt n'est pas installé dans l'environnement d'assemblage :
```text
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
Statut : `NON EXÉCUTÉ LOCAL`, jamais PASS.
## Décisions prises
```text
corriger l'identifiant desktop en KSP-CONFIG-019
maintenir la release 0.3.13
aucune nouvelle dépendance externe
maxSupportedTransactionVersion=1 pour les nouvelles voies concernées
convergence/hydration globale Worker-owned
observation fanout via capability Store existante
source failure terminale conservative jusqu'au coverage/repair 0.3.14
```
## Questions ouvertes
Reportées explicitement :
```text
coverage équivalent entre sources
failover non-terminal
repair de gaps multi-source
sources EARLY
campagnes historiques multi-source
composition Desk 0.3.15
```

View File

@@ -0,0 +1,171 @@
<!-- file: deltas/0.3.13/pre.002-fix.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.002-fix.001 — normalisation rustfmt du root Worker
## Base requise
```text
delivery précédente : 0.3.13-pre.002
Cargo base : 0.3.13-pre.2
delta base : deltas/0.3.13/pre.002.md
archive delta base : ksp-general-0.3.13-pre.002.zip
SHA-256 delta base : 9e7c65d2340fe4918fcfa285cc14beabe4cccd3af1a58aa86e1d24a7ac8bf7b2
```
Le gate opérateur exécuté après application de `pre.002` a identifié un seul défaut : `cargo fmt --all -- --check` demande le tri de l'export `MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES` après les exports du module `runtime`. Les audits Rust/Markdown, `cargo check --workspace`, Clippy strict et les suites Transport/Worker exécutées ensuite sont verts.
## Objectif
Corriger strictement le défaut de format de `pre.002`, sans modification fonctionnelle :
```text
aucune API ajoutée ou supprimée
aucune signature modifiée
aucune dépendance modifiée
aucune logique runtime modifiée
aucun changement du plan pre.003+
réordonnancement rustfmt uniquement dans le root Worker
```
## Type de livraison
```text
livraison : 0.3.13-pre.002-fix.001
Cargo : 0.3.13-pre.2.fix.1
archive : ksp-general-0.3.13-pre.002-fix.001.zip
```
Conformément à `VER-ID-007` et `VER-ID-010`, le correctif touche un fichier `.rs`; `workspace.package.version` est donc synchronisé avec l'identifiant SemVer technique du fix.
## Correction
Dans `crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs`, l'export :
```text
MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES
```
est déplacé après le groupe des exports `runtime`, exactement comme demandé par `rustfmt` dans le gate opérateur.
L'ordre corrigé est :
```text
RAW_TRANSACTION_INGEST_WORKER_KIND_CODE
RawTransactionIngestHandle
RawTransactionIngestTerminalFuture
RawTransactionIngestWorker
MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES
RawTransactionIngestRuntimeResources
RawTransactionIngestYellowstoneSource
```
Le changement est purement lexical. La constante, sa visibilité, son chemin public et sa valeur restent inchangés.
## Fichiers ajoutés
```text
deltas/0.3.13/pre.002-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
```
## Fichiers supprimés
```text
aucun
```
## Versions de fichiers incrémentées
```text
Cargo.toml : 540 -> 541
worker lib.rs : 18 -> 19
```
## Validations exécutées sur la base pre.002 par l'opérateur
Le log opérateur fourni pour `0.3.13-pre.2` confirme :
```text
cargo fmt --all -- --check : FAIL, uniquement diff de tri d'exports dans worker src/lib.rs
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables, 829 files)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-onchain-transport-lib : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib : PASS
```
Suites explicitement observées :
```text
Transport unit : 389 passed
Transport public_api : 52 passed
Transport release_completeness : 44 passed
Transport doc-tests : 4 passed
smokes réseau : ignored / opt-in conformément au contrat
Worker unit : 70 passed
Worker cross_layer_completeness : 4 passed
Worker dependency_boundary : 9 passed
Worker hardening : 16 passed
Worker public_api : 10 passed
Worker release_completeness : 4 passed
Worker doc-tests : 0
```
## Validations exécutées dans l'environnement d'assemblage du fix
```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
contrôle supplémentaire des collisions d'identifiants normatifs
contrôle exhaustif du diff pre.002 -> pre.002-fix.001
contrôle du contenu et de l'intégrité ZIP
```
## Validations non exécutées localement
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les commandes suivantes ne sont donc pas déclarées PASS localement :
```bash
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
Le correctif reproduit exactement le déplacement demandé par le diff `cargo fmt --all -- --check` du gate opérateur.
## Gate opérateur avant pre.003
```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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Décisions
- Le fix ne change pas le périmètre fonctionnel de `pre.002`.
- Le supervisor multi-source reste réservé à `pre.007`.
- `pre.003` reste la prochaine tranche fonctionnelle après validation de ce correctif.
- Aucun document durable n'est modifié pour une correction de format déjà entièrement décrite par ce delta.
## Questions ouvertes
```text
aucune
```

323
deltas/0.3.13/pre.002.md Normal file
View File

@@ -0,0 +1,323 @@
<!-- file: deltas/0.3.13/pre.002.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.002 — runtime resources multi-source bornées
## Base requise
```text
delivery précédente : 0.3.13-pre.001
Cargo base : 0.3.13-pre.1
delta base : deltas/0.3.13/pre.001.md
archive delta base : ksp-general-0.3.13-pre.001.zip
SHA-256 delta base : ca57546f19bd47b5d3b832a51905159497a5e601c186460db22b04364a1835c4
```
Le gate opérateur fourni après application de `pre.001` est vert : fmt, audits Rust/Markdown, check workspace, Clippy strict, tests Worker, arbres normal/features et duplicates ont été exécutés sans anomalie signalée.
## Objectif
Matérialiser le contrat `pre.002` défini par le plan `034` sans démarrer prématurément les nouvelles familles live :
```text
collection runtime privée bornée 1..32
identité logique déterministe par source
source discriminant capability-owned
validation globale avant spawn
rejet des doublons logiques
rejet cross-network
aucun nouveau client réseau
aucune nouvelle dépendance
```
Le supervisor simultané appartient toujours à `pre.007`.
## Type de livraison
```text
0.3.13-pre.002
Cargo : 0.3.13-pre.2
archive : ksp-general-0.3.13-pre.002.zip
```
## Runtime resources multi-source
`RawTransactionIngestRuntimeResources` ne contient plus directement un unique champ Yellowstone. Il possède désormais une collection privée :
```text
Vec<RawTransactionIngestLiveSource>
```
Le discriminant privé initial est :
```text
Yellowstone(RawTransactionIngestYellowstoneSource)
```
Il est volontairement capability-owned ; aucun provider n'entre dans l'enum Worker.
Surface publique ajoutée :
```text
MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES = 32
RawTransactionIngestRuntimeResources::source_count()
RawTransactionIngestRuntimeResources::try_push_yellowstone_source(...)
```
`try_push_yellowstone_source` est transactionnel : le candidat est entièrement rejeté sans mutation de la collection si la borne, le réseau ou l'identité logique sont invalides.
## Identité logique Yellowstone
Chaque `RawTransactionIngestYellowstoneSource` possède maintenant un `source_key: [u8; 32]` privé.
Le hash SHA-256 est dérivé uniquement de données sûres :
```text
KSP domain separator
source family = yellowstone
network
provider identity sûre
endpoint identity sûre
commitment
identité opaque de YellowstoneSubscribeRequest
```
Le rôle et le pool HTTP d'hydration sont volontairement exclus du `source_key` : ils ne définissent pas une nouvelle source live Yellowstone. Ainsi, deux subscriptions identiques ne deviennent pas artificiellement distinctes par simple changement de stratégie d'hydration.
Sont explicitement absents du matériau en clair et de Debug :
```text
URL
API key / credential
header
transaction payload
remote error material
request identity bytes
source_key bytes/hash
```
Concernant `source_key`, le Debug de la source n'en publie que la longueur ; ses autres champs Debug restent les identités sûres déjà admises par le contrat stable. Le Debug de l'aggregate runtime publie seulement `source_count`.
## Identité déterministe de SubscribeRequest dans Transport
`ksp-onchain-transport-lib` expose `YellowstoneSubscribeRequestIdentity` comme valeur opaque et `YellowstoneSubscribeRequest::identity()`.
Canonicalisation de l'identité :
```text
sept familles de filtres séparées
ordre déterministe fourni par les BTreeMap KSP
nom de filtre length-framed
protobuf exact de chaque filtre length-framed
options communes protobuf encodées après vidage des sept maps
```
Les bytes internes restent privés. L'identité implémente `Hash` afin que le Worker puisse l'incorporer à son SHA-256 sans escape hatch de sérialisation.
Aucune dépendance n'est ajoutée : le codage réutilise `prost` réexporté par `yellowstone-grpc-proto`, déjà dépendance de Transport.
## Validation globale avant spawn
Avant activation, `RawTransactionIngestRuntimeResources::validate_network` impose :
```text
collection non vide
source_count <= 32
chaque source sur le réseau Worker
source_key unique dans la collection
```
Les erreurs restent mappées sur le contrat stable `ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID` avec conditions internes stables, sans valeurs sensibles.
## Gate volontaire d'activation multi-source
Le plan final exige que toutes les sources configurées soient démarrées simultanément. Cette responsabilité est explicitement réservée à `pre.007`.
Pour éviter un comportement faux en `pre.002` :
```text
1 source Yellowstone : voie productive stable conservée
2..32 sources : composition/validation possible
start avec 2..32 sources : fail closed avant spawn
jamais de démarrage silencieux de la première source seulement
```
Condition interne :
```text
runtime_resources.multi_source_activation_pending
```
Ce gate sera supprimé uniquement par la tranche supervisor/source inventory.
## Preuves déterministes ajoutées
Transport :
```text
identité de request stable malgré ordre d'insertion
identité différente si le filtre logique change
Debug identité redacted
surface identité disponible depuis crate root
aucun bytes()/as_bytes() public
```
Worker :
```text
source_key stable pour identité logique identique
source_key sensible au request fingerprint
source_key sensible à l'endpoint identity sûre
32 sources exactement acceptées
33e rejetée sans mutation
duplicate source identity rejetée
source cross-network rejetée
Debug aggregate limité à source_count
activation multi-source fail-closed avant pre.007
public API canary pour bound/count/push
release export inventory mis à jour
```
## Compatibilité et non-régression
La construction historique :
```text
RawTransactionIngestRuntimeResources::new(yellowstone_source)
```
reste valide et crée un aggregate d'une source.
La voie productive stable `start_with_runtime_resources` continue donc à fonctionner pour une seule source Yellowstone sans changement de Common RAW, Store, Config ni protocole réseau.
## Fichiers ajoutés
```text
deltas/0.3.13/pre.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers supprimés
```text
aucun
```
## Versions de fichiers incrémentées
```text
grpc_subscribe.rs transport src : 8 -> 9
transport lib.rs : 46 -> 47
transport public_api.rs : 51 -> 52
transport release_completeness.rs : 43 -> 44
transport unit grpc_subscribe.rs : 5 -> 6
worker lib.rs : 17 -> 18
worker runtime.rs : 11 -> 12
worker runtime_resources.rs : 12 -> 13
worker public_api.rs : 11 -> 12
worker release_completeness.rs : 9 -> 10
worker unit runtime_resources.rs : 11 -> 12
plan 034 : 1 -> 2
validation 030 : 1 -> 2
```
## Audit intermédiaire et corrections
La première passe statique après introduction de l'identité Transport a détecté une rustdoc devenue non adjacente au `YellowstoneSubscribeRequest`. La structure documentaire a été corrigée avant poursuite.
Une passe ultérieure après ajout des canaris externes a détecté uniquement :
```text
2 fins de fichier avec newline supplémentaire
1 double ligne vide entre items Rust
```
Ces trois écarts de format ont été corrigés avant la validation finale.
Aucun état intermédiaire en échec n'est présenté comme PASS.
## Validations exécutées localement après correction finale
```text
python3 scripts/audit_rust_workspace_rules.py
PASS : General Rust rule audit clean
PASS : Rust export completeness audit 0 candidate
PASS : KSP workspace Rust rule audit clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
PASS : Markdown table audit clean (340 tables, 829 files)
supplemental normative definition-id scan
PASS : 489 definitions, 489 unique, 0 duplicate definition id
pre-packaging exhaustive diff scan
PASS : 14 fichiers modifiés, 1 ajouté, 0 supprimé
PASS : chaque header de version des 13 fichiers versionnés modifiés est incrémenté exactement de +1
PASS : aucune nouvelle occurrence de unsafe/unwrap/expect/panic/todo/unimplemented dans les lignes Rust production ajoutées
```
Les caches Python recréés par les scripts d'audit sont supprimés après la dernière passe et ne font pas partie de la livraison. L'archive delta est ensuite contrôlée par inventaire exact, test ZIP et scan des chemins/artefacts.
## Validations non exécutées localement
Le toolchain Rust n'est pas installé dans l'environnement d'assemblage courant :
```text
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
Statut : `NON EXÉCUTÉ LOCAL`, jamais PASS.
## Décisions prises
```text
conserver 1..32 comme borne runtime P0
source identity opaque et non publique côté Worker
faire posséder l'identité exacte SubscribeRequest par Transport
ne pas ajouter prost comme dépendance Worker/Transport supplémentaire
valider toutes les sources avant spawn
rejeter duplicate identity et cross-network avant mutation
fail closed pour activation >1 jusqu'à pre.007
ne pas démarrer un sous-ensemble silencieux
ne modifier ni Common RAW ni Store API
```
## Questions ouvertes
Aucune nouvelle question bloquante pour `pre.003`.
Restent volontairement dans leurs tranches prévues :
```text
logsSubscribe standard + hydration : pre.003
blockSubscribe standard : pre.004
Helius transactionSubscribe : pre.005
HTTP polling : pre.006
supervisor simultané / source inventory : pre.007
convergence globale / observations multiples : pre.008
```

View File

@@ -0,0 +1,210 @@
<!-- file: deltas/0.3.13/pre.003-fix.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.003-fix.001 — correction des tests Worker après généralisation source-neutral
## Base requise
```text
delivery précédente : 0.3.13-pre.003
Cargo base : 0.3.13-pre.3
delta base : deltas/0.3.13/pre.003.md
archive delta base : ksp-general-0.3.13-pre.003.zip
SHA-256 delta base : 669b875410799b8a2c5806cddad81740bdd4dd78e36c68f6b268a6b0257f18f4
```
Le gate opérateur de `pre.003` confirme que le formatage, les audits et `cargo check --workspace` sont propres, mais que les cibles de tests Worker ne compilent plus après la généralisation source-neutral de `runtime_resources`. `cargo clippy --workspace --all-targets --all-features -- -D warnings` et `cargo test -p ksp-worker-raw-transaction-ingest-lib` échouent sur sept diagnostics provenant du même fichier de tests.
## Objectif
Corriger uniquement les tests devenus obsolètes par rapport au contrat production de `pre.003`, sans rouvrir l'API ni modifier la logique runtime :
```text
aucun code production modifié
aucune API publique ajoutée ou supprimée
aucune dépendance modifiée
aucune logique réseau/hydration/admission modifiée
aucun avancement vers blockSubscribe pre.004
```
## Version
```text
livraison : 0.3.13-pre.003-fix.001
workspace.package.version : 0.3.13-pre.3.fix.1
archive : ksp-general-0.3.13-pre.003-fix.001.zip
```
Conformément aux règles de versionnement KSP, le fix modifie un fichier `.rs`; le `workspace.package.version` est donc synchronisé avec l'identifiant SemVer technique du correctif.
## Diagnostic opérateur
Les sept diagnostics se répartissent en trois causes.
### Ancien extracteur mono-Yellowstone supprimé
Le test `v0_3_13_pre_002_multi_source_activation_fails_closed_until_supervisor_tranche` appelait encore :
```text
RawTransactionIngestRuntimeResources::into_yellowstone_source
```
Cette méthode a été supprimée en `pre.003` lorsque l'aggregate a été rendu source-neutral. La barrière correspondante est désormais :
```text
RawTransactionIngestRuntimeResources::validate_single_source_activation
```
Le test utilise donc le contrat actuel et continue de vérifier exactement l'erreur :
```text
runtime_resources.multi_source_activation_pending
```
Les deux erreurs d'inférence `E0282` étaient secondaires à l'absence de l'ancienne méthode et disparaissent avec cette correction.
### Contexte d'hydration manquant
Deux tests appelaient encore l'ancienne signature de :
```text
build_hydration_provenance
```
`pre.003` a ajouté en premier argument le contexte source-neutral :
```text
&RawTransactionIngestHydrationContext
```
Les tests construisent maintenant ce contexte via :
```text
source.hydration_context()
```
puis le transmettent à `build_hydration_provenance`. Aucun contrat production n'est élargi pour les tests.
### Accesseurs volontairement absents de RawTransactionMaterial
Le nouveau test Standard Logs tentait d'appeler :
```text
ingress.material.slot()
ingress.material.signature()
```
`RawTransactionMaterial` garde ces champs privés et ne fournit volontairement pas ces accesseurs. Le test ne doit pas provoquer l'ajout d'une API uniquement pour introspection.
Les deux assertions sont donc supprimées à ce niveau. La même preuve reste effectuée après passage dans l'admission centrale et canonicalisation, via :
```text
acquisition.transaction().slot() == 42
acquisition.transaction().reference().signature().as_bytes() == [0; 64]
```
Cela vérifie en plus le chemin réellement consommé par le Worker.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.003-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Versions de fichiers incrémentées
```text
Cargo.toml : 542 -> 543
worker unit_tests/runtime_resources.rs : 13 -> 14
```
## Validation opérateur de la base pre.003
Le log fourni confirme avant fix :
```text
cargo fmt --all : exécuté
cargo fmt --all -- --check : PASS / silencieux
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables, 831 files)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL sur compilation des tests Worker
cargo test -p ksp-worker-raw-transaction-ingest-lib : FAIL sur les mêmes 7 diagnostics
```
Diagnostics observés :
```text
E0599 : into_yellowstone_source supprimé
E0282 : 2 erreurs d'inférence secondaires
E0061 : 2 appels build_hydration_provenance sans hydration context
E0599 : RawTransactionMaterial::slot absent
E0599 : RawTransactionMaterial::signature absent
```
## Validations dans l'environnement d'assemblage
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les validations statiques KSP exécutables localement et le contrôle exhaustif du delta sont réalisés avant packaging.
Les commandes Cargo ne sont pas déclarées PASS localement :
```bash
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 832 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
## Gate opérateur avant pre.004
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
`pre.004` ne doit démarrer qu'après retour vert de ce gate.
## Décisions
- Le fix conserve l'encapsulation de `RawTransactionMaterial`; aucun getter n'est ajouté pour satisfaire un test.
- Le fix conserve l'aggregate source-neutral introduit en `pre.003`; l'ancien extracteur mono-Yellowstone n'est pas restauré.
- Le fix conserve le contexte d'hydration source-neutral et adapte les tests à sa signature actuelle.
- Aucun changement fonctionnel de `pre.003` n'est introduit.
## Questions ouvertes
```text
aucune
```

View File

@@ -0,0 +1,194 @@
<!-- file: deltas/0.3.13/pre.003-fix.002.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.003-fix.002 — correction des canaris de dépendance après généralisation source-neutral
## Base requise
```text
delivery précédente : 0.3.13-pre.003-fix.001
Cargo base : 0.3.13-pre.3.fix.1
delta base : deltas/0.3.13/pre.003-fix.001.md
archive delta base : ksp-general-0.3.13-pre.003-fix.001.zip
```
Le gate opérateur de `pre.003-fix.001` confirme que le formatage, les audits, `cargo check --workspace`, Clippy strict et les tests unitaires Worker passent. Deux canaris historiques de `tests/dependency_boundary.rs` échouent cependant parce qu'ils vérifient encore des noms et une borne d'implémentation antérieurs à la généralisation source-neutral de `pre.003`.
## Objectif
Corriger uniquement ces deux canaris afin qu'ils vérifient le contrat production actuel, sans modifier le code runtime :
```text
aucun code production modifié
aucune API publique ajoutée ou supprimée
aucune dépendance modifiée
aucune logique réseau/hydration/admission modifiée
aucun avancement vers blockSubscribe pre.004
```
## Version
```text
livraison : 0.3.13-pre.003-fix.002
workspace.package.version : 0.3.13-pre.3.fix.2
archive : ksp-general-0.3.13-pre.003-fix.002.zip
```
Le fix modifie un fichier Rust de tests ; le `workspace.package.version` est donc incrémenté conformément aux règles KSP.
## Diagnostic opérateur
### Canary hydration historique
Le test :
```text
v0_3_12_pre_004_hydration_contract_uses_only_transport_facade_common_raw_and_existing_ingress
```
attendait encore les helpers mono-Yellowstone :
```text
fn finalize_yellowstone_hydration
fn fetch_yellowstone_hydration
```
`pre.003` a volontairement rendu l'hydration commune à plusieurs familles de sources et les helpers production actuels sont :
```text
fn finalize_hydration
async fn fetch_hydration
```
Le canary est mis à jour pour vérifier ces helpers source-neutral sans réintroduire les anciens noms.
### Canary de borne de coalescence historique
Le test :
```text
v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission
```
attendait encore la présence textuelle de :
```text
MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY
```
`pre.003` a resserré le budget du coordinator commun sur la borne configurée du Worker :
```text
max_pending_signals: settings.admission_queue_capacity()
```
Le canary vérifie désormais cette borne runtime effective. Le plafond absolu n'est pas réintroduit dans l'implémentation.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.003-fix.002.md
```
## Fichiers supprimés
```text
aucun
```
## Versions de fichiers incrémentées
```text
Cargo.toml : 543 -> 544
worker tests/dependency_boundary.rs : 17 -> 18
```
## Validation opérateur de la base pre.003-fix.001
Le log fourni confirme avant ce fix :
```text
cargo fmt --all : exécuté
cargo fmt --all -- --check : PASS / silencieux
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables, 832 files)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL lors des tests dependency_boundary
cargo test -p ksp-worker-raw-transaction-ingest-lib : 75 unit tests PASS, puis 2/10 dependency_boundary FAIL
```
Échecs observés :
```text
v0_3_12_pre_004_hydration_contract_uses_only_transport_facade_common_raw_and_existing_ingress
v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission
```
Les deux échecs sont des assertions `include_str!` sur l'implémentation et non des erreurs du code production.
## Validations dans l'environnement d'assemblage
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging. Les commandes Cargo suivantes ne sont donc pas déclarées PASS localement :
```bash
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 833 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
Les trois chaînes attendues par les canaris corrigés ont également été vérifiées directement dans `runtime_resources.rs` :
```text
fn finalize_hydration : présent
fn fetch_hydration : présent
max_pending_signals: settings.admission_queue_capacity() : présent
```
## Gate opérateur avant pre.004
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
`pre.004` ne doit démarrer qu'après retour vert de ce gate.
## Décisions
- Le fix conserve les helpers d'hydration source-neutral de `pre.003`; les anciens noms Yellowstone ne sont pas restaurés.
- Le fix conserve `settings.admission_queue_capacity()` comme borne effective du coordinator d'hydration ; le plafond absolu n'est pas réinjecté.
- Aucun code production n'est modifié.
- Aucun changement fonctionnel de `pre.003` n'est introduit.
## Questions ouvertes
```text
aucune
```

View File

@@ -0,0 +1,164 @@
<!-- file: deltas/0.3.13/pre.003-fix.003.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.003-fix.003 — correction du dernier canari productif historique
## Base requise
```text
delivery précédente : 0.3.13-pre.003-fix.002
Cargo base : 0.3.13-pre.3.fix.2
delta base : deltas/0.3.13/pre.003-fix.002.md
archive delta base : ksp-general-0.3.13-pre.003-fix.002.zip
```
Le gate opérateur de `pre.003-fix.002` confirme que le formatage, les audits, `cargo check --workspace`, les 75 tests unitaires Worker et 9/10 canaris `dependency_boundary` passent. Un seul canari historique reste obsolète : il recherche encore la chaîne `commitment: commitment.as_str()` alors que `pre.003` a déplacé le commitment dans le contexte d'hydration source-neutral.
## Objectif
Corriger uniquement cette assertion textuelle de test afin qu'elle vérifie le contrat production actuel :
```text
aucun code production modifié
aucune API publique ajoutée ou supprimée
aucune dépendance modifiée
aucune logique réseau/hydration/admission modifiée
aucun avancement vers blockSubscribe pre.004
```
## Version
```text
livraison : 0.3.13-pre.003-fix.003
workspace.package.version : 0.3.13-pre.3.fix.3
archive : ksp-general-0.3.13-pre.003-fix.003.zip
```
Le fix modifie un fichier Rust de tests ; le `workspace.package.version` est donc incrémenté conformément aux règles KSP.
## Diagnostic opérateur
Le test :
```text
v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission
```
attendait encore :
```text
commitment: commitment.as_str()
```
Le coordinator d'hydration source-neutral de `pre.003` porte désormais le commitment dans `RawTransactionIngestHydrationContext`. Le code production courant construit donc la clé avec :
```text
commitment: hydration.commitment.as_str()
```
Le canari est mis à jour pour vérifier cette forme actuelle. Aucun alias, variable ou code de compatibilité artificiel n'est ajouté au runtime.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.003-fix.003.md
```
## Fichiers supprimés
```text
aucun
```
## Versions de fichiers incrémentées
```text
Cargo.toml : 544 -> 545
worker tests/dependency_boundary.rs : 18 -> 19
```
## Validation opérateur de la base pre.003-fix.002
Le log fourni confirme avant ce fix :
```text
cargo fmt --all : exécuté
cargo fmt --all -- --check : PASS / silencieux
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables, 833 files)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : atteint les tests puis échoue sur le même canari dependency_boundary
cargo test -p ksp-worker-raw-transaction-ingest-lib : 75 unit tests PASS, puis 9/10 dependency_boundary PASS et 1 FAIL
```
Échec restant :
```text
required pre.006 productive-source contract missing: commitment: commitment.as_str()
```
Il s'agit d'une assertion `include_str!` obsolète sur l'implémentation, pas d'une erreur du code production.
## Validations dans l'environnement d'assemblage
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging. Les commandes Cargo suivantes ne sont donc pas déclarées PASS localement :
```bash
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 834 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
La chaîne attendue par le canari corrigé a également été vérifiée directement dans `runtime_resources.rs` :
```text
commitment: hydration.commitment.as_str() : présent
```
## Gate opérateur avant pre.004
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
`pre.004` ne doit démarrer qu'après retour vert de ce gate.
## Décisions
- Le commitment reste porté par le contexte d'hydration source-neutral de `pre.003`.
- Le canari historique est aligné sur le contrat actuel ; aucun code production n'est modifié pour satisfaire une recherche textuelle obsolète.
- Aucun changement fonctionnel de `pre.003` n'est introduit.
## Questions ouvertes
```text
aucune
```

260
deltas/0.3.13/pre.003.md Normal file
View File

@@ -0,0 +1,260 @@
<!-- file: deltas/0.3.13/pre.003.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.003 — Standard Solana logsSubscribe + hydration commune
## Base requise
```text
delivery précédente : 0.3.13-pre.002-fix.001
Cargo base : 0.3.13-pre.2.fix.1
delta base : deltas/0.3.13/pre.002-fix.001.md
```
Le gate opérateur de la base a été exécuté après `cargo clean` et est vert : `cargo fmt --all -- --check` silencieux, audits Rust/Markdown propres, `cargo check --workspace`, Clippy strict, 389 tests unitaires Transport, 52 tests public API Transport, 44 tests release Transport, 4 doc-tests Transport, puis 70 tests unitaires Worker et toutes ses suites d'intégration sans échec. Les smokes réseau opt-in sont restés ignorés comme prévu.
## Objectif
Ajouter la première nouvelle famille live de `0.3.13` sans dupliquer Transport ni l'admission RAW :
```text
Solana standard WS logsSubscribe
-> context.slot + signature
-> hydration HTTP getTransaction observed
-> Common RAW
-> admission centrale Worker
-> Store
```
La tranche ne démarre toujours qu'une seule source productive à la fois. La supervision simultanée de plusieurs sources reste réservée à `pre.007`.
## Version
```text
livraison : 0.3.13-pre.003
workspace.package.version : 0.3.13-pre.3
archive attendue : ksp-general-0.3.13-pre.003.zip
```
## Source Standard Logs
Nouvelle surface publique Worker :
```text
RawTransactionIngestStandardLogsSource
```
Construction caller-composed :
```text
WsEndpointSettings kind solana_standard
SolanaLogsSubscribeFilter
SolanaCommitment Confirmed ou Finalized
HttpTransportPool
HttpRoleName hydration
```
La construction ne réalise aucune I/O. Elle valide le protocole WebSocket, le commitment, le réseau, la représentation sûre provider/endpoint et l'existence d'une route HTTP `getTransaction` compatible sur le même cluster.
Au runtime, la source utilise exclusivement :
```text
SolanaStandardWsSession::connect
logs_subscribe
HttpTransportPool::get_transaction_observed
```
Le Worker ne construit aucun socket/client inférieur et ne dépend directement ni de `reqwest`, ni de `tokio-tungstenite`, ni de `tonic`, ni de `yellowstone-grpc-proto`.
## Projection et redaction
Une notification `logsSubscribe` est réduite à son matériau de référence nécessaire :
```text
context.slot
signature
commitment caller-composed
identité source/filter privée
```
Les champs distants `logs` et `err` ne sont pas copiés dans le signal Worker, le snapshot, la provenance textuelle ou `Debug`.
Les variantes :
```text
All
AllWithVotes
Mentions(pubkey)
```
participent à une empreinte SHA-256 privée. La valeur du pubkey `Mentions` n'est pas exposée par le Worker.
## Identité logique
Le `source_key` Standard Logs est dérivé de :
```text
domain KSP live-source v1
family = standard_logs
network
safe provider identity
safe endpoint identity
commitment
private filter fingerprint
```
Le rôle et le pool HTTP d'hydration n'entrent pas dans cette identité : ils décrivent la stratégie d'enrichissement, pas une nouvelle source live.
Le rejet transactionnel des doublons et le bound global `1..32` introduits en `pre.002` s'appliquent aussi à Standard Logs.
## Hydration source-neutral
Le coordinator privé précédemment utilisé par Yellowstone est généralisé en contrat d'hydration source-neutral et réutilisé par Standard Logs.
Pour `pre.003`, chaque exécution mono-source possède encore sa propre instance. Le partage réellement global entre plusieurs tâches source sera introduit avec le supervisor `pre.007`.
Bornes effectives :
```text
hydration in-flight <= persistence_concurrency
pending source signals <= admission_queue_capacity
```
Le plafond pending est donc aligné sur les settings effectifs du Worker plutôt que sur le plafond absolu maximal.
La requête commune `getTransaction observed` utilise maintenant :
```text
encoding = base64
maxSupportedTransactionVersion = 1
```
Cette adaptation s'applique également à Yellowstone. Elle prépare l'hydration aux transactions Legacy/V0/V1 mais ne qualifie aucun nouveau chemin RAW-direct V1.
## Activation runtime
Le runtime-resource aggregate peut composer Yellowstone et Standard Logs dans la collection privée bornée, mais la barrière de sûreté reste :
```text
1 source Yellowstone : productif
1 source Standard Logs : productif
2..32 sources : composition validable
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
```
Aucune source configurée n'est silencieusement ignorée.
## Preuves ajoutées
Unit tests Worker :
```text
validation standard WS / commitment / route HTTP
source_key stable et filter-sensitive
source_key indépendant du hydration role
All / AllWithVotes / Mentions distingués
Mentions/URL/source-key bytes redacted
projection reference-only
fixture signature+slot -> une hydration HTTP -> matériau Common RAW exact
getTransaction base64 + maxSupportedTransactionVersion=1
rejet duplicate Standard Logs identity
```
Canaris externes :
```text
public_api : nouvelle construction/composition accessible depuis crate root
dependency_boundary : uniquement façades Transport + Common RAW/Store existants
hardening : aucune copie logs/err/payload/url et Debug borné
release_completeness : nouvelle surface et canaris pre.003 obligatoires
```
Reconnect, resubscribe, IDs distants et backpressure WebSocket restent couverts et possédés par `ksp-onchain-transport-lib`; le Worker réutilise la façade `SolanaStandardWsSession` au lieu de reproduire ces mécanismes.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.003.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
`pre.003` ne prétend pas :
```text
blockSubscribe productif
Helius transactionSubscribe productif
HTTP live block polling
supervision simultanée de plusieurs sources
fanout Store d'observations multi-source
coordinator partagé entre plusieurs tâches source simultanées
gap repair/backfill
nouvelle dépendance externe
```
## Validation dans l'environnement d'assemblage
Les contrôles réellement exécutés après finalisation de la tranche sont :
```text
python3 scripts/audit_rust_workspace_rules.py
PASS : General Rust rule audit clean
PASS : Rust export completeness audit 0 candidate
PASS : KSP workspace Rust rule audit clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
PASS : Markdown table audit clean (340 tables, 831 files)
supplemental normative definition-id scan
PASS : 489 definitions, 489 unique, 0 duplicate definition id
pre-packaging exhaustive diff scan
PASS : 13 fichiers modifiés, 1 ajouté, 0 supprimé
PASS : les 13 headers versionnés des fichiers modifiés sont incrémentés exactement de +1
PASS : aucune dépendance Cargo nouvelle
```
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans cet environnement. Les commandes Cargo post-modification sont donc explicitement `NON EXÉCUTÉ LOCAL`, jamais déclarées PASS.
Les caches Python produits par les audits sont supprimés avant packaging et ne font pas partie du delta.
## Gate opérateur requis avant pre.004
```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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
`pre.004` reste bloquée si ce gate révèle une anomalie de format, règle, compilation, API, dépendance ou test.

View File

@@ -0,0 +1,94 @@
<!-- file: deltas/0.3.13/pre.004-fix.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.004-fix.001 — retrait de la dépendance serde_json accidentelle
## Base requise
```text
livraison précédente : 0.3.13-pre.004
Cargo base : 0.3.13-pre.4
delta base : deltas/0.3.13/pre.004.md
archive delta base : ksp-general-0.3.13-pre.004.zip
```
Le gate opérateur de `pre.004` a révélé deux erreurs de compilation liées au même défaut : le Worker nommait directement `serde_json::Value` dans `runtime_resources.rs` alors que `serde_json` n'est pas une dépendance du crate, et le test de qualification Standard Block utilisait directement `serde_json::json!`.
## Objectif du fix
Supprimer cette dépendance accidentelle sans ajouter `serde_json` au manifeste du Worker et sans modifier le comportement productif de Standard Block.
Le chemin production conserve exactement le `meta` décodé par `ksp-onchain-transport-lib`, mais son type concret reste inféré depuis `SolanaBlockTransaction::meta()` et n'est plus nommé par le Worker.
Le helper de qualification reçoit désormais seulement la version, la position et une closure de construction du matériau. La closure production capture le `meta` opaque et le clone vers le Common RAW. La fixture Legacy/V0/V1 utilise un `RawTransactionWireField::Null`, car elle vérifie la signature embarquée, le slot, le `blockTime`, la version et le `transactionIndex`, pas le contenu de `meta`.
## Version
```text
livraison : 0.3.13-pre.004-fix.001
workspace.package.version : 0.3.13-pre.4.fix.1
archive : ksp-general-0.3.13-pre.004-fix.001.zip
```
`Cargo.toml` passe de la version de fichier `546` à `547`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
Les deux fichiers Rust passent de la version de fichier `15` à `16`.
## Fichiers ajoutés
```text
deltas/0.3.13/pre.004-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
Ce fix n'ajoute aucune dépendance, ne modifie aucune API publique et ne change ni la qualification Legacy/V0/V1, ni l'admission RAW, ni la frontier, ni le comportement réseau de `blockSubscribe`.
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
## Gate opérateur requis avant pre.005
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 836 files)
```
Contrôle exhaustif du delta :
```text
3 fichiers existants modifiés
1 fichier ajouté
0 fichier supprimé
aucune dépendance ajoutée
aucune référence directe serde_json dans les deux fichiers Rust corrigés
```

267
deltas/0.3.13/pre.004.md Normal file
View File

@@ -0,0 +1,267 @@
<!-- file: deltas/0.3.13/pre.004.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.004 — Standard Solana blockSubscribe RAW-direct qualifié
## Base requise
```text
delivery précédente : 0.3.13-pre.003-fix.003
Cargo base : 0.3.13-pre.3.fix.3
delta base : deltas/0.3.13/pre.003-fix.003.md
archive delta base : ksp-general-0.3.13-pre.003-fix.003.zip
```
Le gate opérateur reçu sur cette base est vert pour toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`75` unit, `4` cross-layer, `10` dependency-boundary, `17` hardening, `11` public-api, `4` release-completeness, `0` doc-test). Cette exécution ne contenait pas le test Transport ni les commandes `cargo tree`; ils ne sont donc pas déclarés PASS pour ce gate précis.
## Objectif
Ajouter la deuxième source Solana Standard WebSocket de `0.3.13` sans dupliquer Transport :
```text
Solana standard WS blockSubscribe
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
-> qualification explicite Legacy / V0 / V1
-> Common RAW direct par transaction
-> admission centrale Worker
-> Store
```
La source ne possède pas de fallback HTTP. Tout bloc ou transaction non qualifiable échoue explicitement et sûrement.
## Version
```text
livraison : 0.3.13-pre.004
workspace.package.version : 0.3.13-pre.4
archive : ksp-general-0.3.13-pre.004.zip
```
`Cargo.toml` passe également de la version de fichier `545` à `546`.
## Source Standard Block
Nouvelle surface publique Worker :
```text
RawTransactionIngestStandardBlockSource
RawTransactionIngestRuntimeResources::from_standard_block_source
RawTransactionIngestRuntimeResources::try_push_standard_block_source
```
Construction caller-composed :
```text
WsEndpointSettings kind solana_standard
SolanaBlockSubscribeFilter
SolanaCommitment Confirmed ou Finalized
```
La construction est sans I/O. Elle valide le protocole, le commitment, le réseau et la représentation sûre provider/endpoint avant spawn.
Au runtime, la source réutilise exclusivement :
```text
SolanaStandardWsSession::connect
SolanaStandardWsSession::block_subscribe
```
Configuration exacte :
```text
encoding = Base64
transactionDetails = Full
maxSupportedTransactionVersion = 1
showRewards = false
```
Aucun client WebSocket direct, `getBlock`, `getTransaction`, `reqwest`, `tokio-tungstenite`, `tonic` ou SDK provider n'est ajouté au Worker.
## Qualification RAW-direct
La version est fail-closed :
```text
Legacy -> RawTransactionVersion::Legacy
0 -> RawTransactionVersion::Number(0)
1 -> RawTransactionVersion::Number(1)
>1 -> source.standard_block_transaction_version_unsupported
omise/null -> source.standard_block_transaction_version_unqualified
```
Le matériau direct conserve :
```text
signature embarquée dans le wire Base64
slot
blockTime
meta
version qualifiée
transactionIndex dans le bloc
```
Les fixtures locales construisent et sérialisent des wires Legacy, V0 et V1 avec les types publics de `ksp-raw-transaction-lib`, puis vérifient le Common RAW canonique obtenu. La preuve V1 ne repose donc pas uniquement sur une étiquette `version=1`.
La parité historique `blockSubscribe` / `getBlock` Legacy reste déjà matérialisée dans `ksp-job-backfill-lib/tests/ws_raw_parity.rs`; `pre.004` rejoue localement les matérialisations Legacy/V0 et ajoute la qualification V1.
## block:null et formes ambiguës
Les cas suivants sont des fautes sûres avant progression de frontier :
```text
context.slot différent du slot de notification
notification err non null
block null
transactions omitted/null
transaction non Base64
version omise/null/>1
signature embarquée invalide
transaction index non représentable en u32
```
`block: null` n'est jamais assimilé à un bloc vide ou à un slot traité. Le décodage Transport qui distingue `block:null` et `err` est conservé tel quel.
Pour un bloc non vide, le slot n'est enregistré settled qu'après l'admission réussie de toutes ses transactions. La voie RAW-direct n'incrémente pas `hydration_pending`; le processing frontier ne peut donc pas avancer transitoirement au milieu du traitement d'un même bloc et un stop/faute partiel ne devient jamais une progression.
## Identité logique et redaction
Le `source_key` Standard Block est dérivé de :
```text
domain KSP live-source v1
family = standard_block
network
safe provider identity
safe endpoint identity
commitment
private block-filter fingerprint
```
Les filtres `All` et `MentionsAccountOrProgram(pubkey)` sont distingués par empreinte privée. Le pubkey brut n'est pas recopié dans `Debug`, snapshot ou provenance textuelle.
Le bound global `1..32`, l'invariant réseau unique et le rejet transactionnel des doublons de `pre.002` s'appliquent à cette nouvelle famille.
## Activation runtime
Le contrat reste volontairement mono-source avant `pre.007` :
```text
1 source Yellowstone : productif
1 source Standard Logs : productif
1 source Standard Block : productif
2..32 sources : composition validable
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
```
Aucune source configurée n'est silencieusement ignorée.
## Preuves ajoutées
Unit tests Worker :
```text
validation protocole/commitment Standard Block
source_key stable et filter-sensitive
redaction filtre/url/source-key
qualification exacte Legacy/V0/V1
rejet version omise/null/>1
wires Legacy/V0/V1 -> Common RAW direct avec signature/version/index exacts
rejet duplicate Standard Block identity
```
Canaris externes :
```text
public_api : construction/composition depuis crate root
dependency_boundary : façade Transport seulement, sans fallback HTTP/direct network crate
hardening : block:null, erreurs, version, encoding et redaction explicites
release_completeness : export et canaris pre.004 obligatoires
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.004.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
`pre.004` ne prétend pas :
```text
Helius transactionSubscribe productif
HTTP live block polling
supervision simultanée de plusieurs sources
fanout Store d'observations multi-source
fallback HTTP getBlock/getTransaction pour Standard Block
RAW-direct pour version >1 ou version ambiguë
block:null considéré comme progression
nouvelle dépendance externe
```
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.004` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan des identifiants normatifs et le contrôle exhaustif du diff sont exécutés avant packaging et leurs résultats finaux sont consignés ci-dessous avant livraison.
## Gate opérateur requis avant pre.005
```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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
`pre.005` ne doit pas commencer si ce gate révèle une anomalie.
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 835 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
Contrôle exhaustif avant packaging :
```text
12 fichiers existants modifiés
1 fichier ajouté
0 fichier supprimé
12/12 headers versionnés incrémentés exactement de +1
Cargo.toml hors header : uniquement workspace.package.version 0.3.13-pre.3.fix.3 -> 0.3.13-pre.4
aucune dépendance ajoutée, supprimée ou modifiée
```

View File

@@ -0,0 +1,94 @@
<!-- file: deltas/0.3.13/pre.005-fix.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.005-fix.001 — correction du canari de redaction Helius
## Base requise
```text
livraison précédente : 0.3.13-pre.005
Cargo base : 0.3.13-pre.5
delta base : deltas/0.3.13/pre.005.md
archive delta base : ksp-general-0.3.13-pre.005.zip
```
Le gate opérateur du 10 septembre 2026 sur `pre.005` est vert pour `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown, `cargo check --workspace`, Clippy strict, les `84` tests unitaires Worker et les `12` tests `dependency_boundary`. Il s'arrête ensuite sur un seul test de hardening : `v0_3_12_pre_002_production_sources_keep_transport_confined_to_runtime_resources` détecte le mot `credential` dans `runtime_resources.rs`.
L'occurrence concernée se trouve uniquement dans une Rustdoc décrivant l'encapsulation des paramètres Transport Helius. Aucun secret, aucune credential et aucune valeur sensible n'est stocké en clair dans le Worker par cette chaîne documentaire.
## Objectif du fix
Conserver le canari de hardening inchangé et reformuler la Rustdoc production afin qu'elle respecte sa politique textuelle stricte. La phrase emploie désormais `sensitive endpoint material` au lieu de `credentials`.
Aucune logique Helius, WebSocket, hydration, source identity, admission, Store, frontier ou API publique n'est modifiée.
## Version
```text
livraison : 0.3.13-pre.005-fix.001
workspace.package.version : 0.3.13-pre.5.fix.1
archive : ksp-general-0.3.13-pre.005-fix.001.zip
```
`Cargo.toml` passe de la version de fichier `548` à `549`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
```
`runtime_resources.rs` passe de la version de fichier `17` à `18`.
## Fichiers ajoutés
```text
deltas/0.3.13/pre.005-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
Ce fix ne modifie aucune dépendance, aucun code exécutable et aucun test. Il ne change pas le comportement de `transactionSubscribe`, de l'hydration `getTransaction observed`, de la redaction runtime ni de la convergence multi-source.
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
## Gate opérateur requis avant pre.006
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 838 files)
```
Contrôle exhaustif du delta :
```text
2 fichiers existants modifiés
1 fichier ajouté
0 fichier supprimé
aucun test modifié
aucune dépendance ajoutée/modifiée/supprimée
aucun marqueur secret-like interdit restant dans les sources Rust production du Worker
```

268
deltas/0.3.13/pre.005.md Normal file
View File

@@ -0,0 +1,268 @@
<!-- file: deltas/0.3.13/pre.005.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.005 — Helius transactionSubscribe + hydration Common RAW
## Base requise
```text
livraison précédente : 0.3.13-pre.004-fix.001
Cargo base : 0.3.13-pre.4.fix.1
delta base : deltas/0.3.13/pre.004-fix.001.md
archive delta base : ksp-general-0.3.13-pre.004-fix.001.zip
```
Le gate opérateur reçu le 10 septembre 2026 sur cette base est vert pour toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`79` unit, `4` cross-layer, `11` dependency-boundary, `18` hardening, `12` public-api, `4` release-completeness, `0` doc-test). Cette exécution ne contenait pas le test Transport ni les commandes `cargo tree`; ils ne sont donc pas déclarés PASS pour ce gate précis.
## Objectif
Ajouter la source Helius WebSocket prévue par la tranche sans dupliquer le chemin Common RAW :
```text
Helius LaserStream transactionSubscribe
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
-> projection privée signature + slot + transactionIndex
-> coordinateur d'hydration source-neutral existant
-> HTTP getTransaction observed
-> Common RAW canonique
-> admission centrale Worker
-> Store
```
Le nested payload transaction Helius n'est pas utilisé comme RAW-direct. Il reste possédé par Transport et n'est jamais copié dans le signal Worker.
## Version
```text
livraison : 0.3.13-pre.005
workspace.package.version : 0.3.13-pre.5
archive : ksp-general-0.3.13-pre.005.zip
```
`Cargo.toml` passe de la version de fichier `547` à `548`.
## Source Helius Transaction
Nouvelle surface publique Worker :
```text
RawTransactionIngestHeliusTransactionSource
RawTransactionIngestRuntimeResources::from_helius_transaction_source
RawTransactionIngestRuntimeResources::try_push_helius_transaction_source
```
Construction caller-composed :
```text
WsEndpointSettings kind helius_laserstream
HeliusTransactionSubscribeFilter
SolanaCommitment Confirmed ou Finalized
HttpTransportPool
HttpRoleName d'hydration
```
La construction est sans I/O. Elle refuse un endpoint invalide ou non Helius LaserStream, `Processed`, un réseau/provenance non représentable et l'absence d'une route HTTP `getTransaction` compatible sur le même réseau.
Au runtime, la source réutilise exclusivement les façades Transport existantes :
```text
HeliusLaserStreamWsSession::connect
HeliusLaserStreamWsSession::transaction_subscribe
HttpTransportPool::get_transaction_observed via le coordinateur commun
```
Aucun client WebSocket direct, SDK Helius, `reqwest`, `tokio-tungstenite`, backend Store ou accès Config n'est ajouté au Worker.
## Requête Helius qualifiée
La requête Worker est fixée à :
```text
commitment = Confirmed | Finalized
encoding = Base64
transactionDetails = Full
showRewards = false
maxSupportedTransactionVersion = 1
```
Une notification productive doit être `HeliusTransactionNotification::Full`. Une forme `Signature`, `Unknown` ou future malgré la requête `Full` devient `source.helius_transaction_notification_unqualified`; elle n'est jamais convertie en succès silencieux.
## Projection et hydration
Le signal privé issu d'une notification `Full` conserve seulement :
```text
network
signature
slot
transactionIndex
safe route identity
private filter fingerprint
commitment via hydration context
```
Le nested champ `transaction` de la notification Helius n'est pas lu par `project_helius_transaction_signal` et n'est pas copié dans Worker. La construction Common RAW reste donc unique : `getTransaction observed` en Base64 avec `maxSupportedTransactionVersion = 1`, puis canonicalisation/admission existantes.
Le coordinateur d'hydration reste borné et source-neutral :
```text
in-flight hydration <= persistence_concurrency
pending source signals <= admission_queue_capacity
coalescence key = network + signature + commitment
```
Cette voie converge avec Yellowstone et Standard Logs au lieu d'introduire un second moteur HTTP.
## Identité logique, redaction et secrets
Le `source_key` Helius est dérivé de :
```text
domain KSP live-source v1
family = helius_transaction
network
safe provider identity
safe endpoint identity
commitment
private normalized Helius filter fingerprint
SHA-256 -> [u8; 32]
```
L'empreinte privée couvre `vote`, `failed`, signature exacte, listes `accountInclude`/`accountExclude`/`accountRequired` et `tokenAccounts`. Les listes de pubkeys sont triées avant hash afin que leur ordre ne crée pas artificiellement deux identités logiques. Le rôle/pool HTTP d'hydration reste exclu du `source_key`.
Les URLs et credentials restent encapsulés par les settings Transport privés utilisés pour ouvrir les connexions ; ils ne sont exposés ni par l'API publique ni par `Debug`. Les valeurs de signature/pubkeys du filtre et les octets de `source_key` ne sont pas rendus dans les diagnostics Worker.
Les profils Config Helius et `KSP_SECRET_HELIUS_API_KEY` existaient déjà avant cette tranche. Aucun changement Config, `.env`, entitlement, quota, prix ou tier provider n'est codé dans Worker.
## Activation runtime
Le contrat reste volontairement mono-source avant `pre.007` :
```text
1 source Yellowstone : productif
1 source Standard Logs : productif
1 source Standard Block : productif
1 source Helius Transaction : productif
2..32 sources : composition validable
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
```
Aucune source configurée n'est silencieusement ignorée.
## Preuves ajoutées
Unit tests Worker :
```text
validation protocole/commitment/route HTTP Helius
requête fixe Full/Base64/maxV1/showRewards=false
source_key filter-sensitive, order-normalized et hydration-role-neutral
Debug redacted : signature/pubkeys/URL/API key/source-key absents
projection Full -> signature/slot/index uniquement
Helius reference -> un getTransaction observed -> Common RAW exact
provenance helius_ws_http + transaction_get_transaction
rejet transactionnel d'une identité Helius dupliquée
```
Canaris externes :
```text
public_api : construction/from/push depuis crate root sans escape hatch des ressources internes
dependency_boundary : façades Transport Helius + un seul getTransaction commun
hardening : nested payload non copié, secrets/redaction et absence de tier provider
release_completeness : export et canaris pre.005 obligatoires
```
## Documentation corrigée
La documentation Worker est mise à jour pour inclure la source Helius et la convergence d'hydration. Une mention héritée de `README.md` qui classait encore `blockSubscribe` et Helius parmi les sources hors périmètre est supprimée ; `USAGE.md` reste volontairement version-neutral.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.005.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
`pre.005` ne prétend pas :
```text
supervision simultanée de plusieurs sources
HTTP live block polling
RAW-direct depuis le nested payload Helius
SDK Helius dans Worker
secret/API key lu directement par Worker
nouveau profil Config Helius
entitlement/tier/quota/prix provider codé
modification de ksp-onchain-transport-lib
nouvelle dépendance externe
```
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.005` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan des identifiants normatifs et le contrôle exhaustif du diff sont exécutés avant packaging.
## Gate opérateur requis avant pre.006
```bash
cargo fmt --all
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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
`pre.006` ne doit pas commencer si ce gate révèle une anomalie.
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 837 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
## Contrôle exhaustif avant packaging
```text
12 fichiers existants modifiés
1 fichier ajouté
0 fichier supprimé
12/12 headers de fichiers existants incrémentés exactement de +1
Cargo.toml : version workspace uniquement hors header
aucune dépendance ajoutée/modifiée/supprimée
```

View File

@@ -0,0 +1,111 @@
<!-- file: deltas/0.3.13/pre.006-fix.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.006-fix.001 — correction du canari historique après live block discovery
## Base requise
```text
livraison précédente : 0.3.13-pre.006
Cargo base : 0.3.13-pre.6
delta base : deltas/0.3.13/pre.006.md
archive delta base : ksp-general-0.3.13-pre.006.zip
```
Le gate opérateur du 10 septembre 2026 sur `pre.006` est vert pour `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown, `cargo check --workspace`, Clippy strict, les `88` tests unitaires Worker et les `13` tests `dependency_boundary`. Il s'arrête ensuite sur un seul canari de hardening : `pre_010_production_surface_has_no_historical_backfill_or_retriever_contract` détecte la chaîne `Backfill` dans la Rustdoc de la nouvelle source HTTP live block polling.
La même vérification interdisait également globalement `Discovery`/`discovery`. Cette hypothèse n'est plus valide depuis `pre.006` : la découverte run-local via `getBlocksWithLimit` est une primitive légitime du live polling et ne constitue ni un retriever historique ni un Backfill.
## Objectif du fix
Conserver la séparation Worker/Backfill sans interdire la terminologie de live block discovery introduite par le poller HTTP :
```text
Rustdoc HTTP polling : suppression de la mention Backfill
canari historique : conserve les interdictions retriever/backfill/checkpoint/historical
mais ne classe plus Discovery/discovery comme marqueur historique
```
Aucune logique de polling, cadence, Transport, admission, Store, processing frontier, provenance, source identity ou API publique n'est modifiée.
## Version
```text
livraison : 0.3.13-pre.006-fix.001
workspace.package.version : 0.3.13-pre.6.fix.1
archive : ksp-general-0.3.13-pre.006-fix.001.zip
```
`Cargo.toml` passe de la version de fichier `550` à `551`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
```
Versions de fichiers :
```text
runtime_resources.rs : 19 -> 20
hardening.rs : 15 -> 16
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.006-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
Ce fix ne modifie aucune dépendance et n'ajoute aucune source. Il ne change ni la borne de run, ni `getSlot`, ni `getBlocksWithLimit`, ni `getBlock observed`, ni les règles Legacy/V0/V1, ni la sémantique `block:null`.
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
## Gate opérateur requis avant pre.007
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 840 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
Contrôle ciblé de la surface production Worker :
```text
ksp-worker-raw-retriever : 0
raw_transaction_retriever : 0
RawTransactionRetriever : 0
Backfill/backfill : 0
Checkpoint/checkpoint : 0
historical : 0
```
`Discovery`/`discovery` n'est plus un marqueur interdit global : `pre.006` l'emploie pour la découverte live run-local de slots/blocs par `getBlocksWithLimit`.

322
deltas/0.3.13/pre.006.md Normal file
View File

@@ -0,0 +1,322 @@
<!-- file: deltas/0.3.13/pre.006.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.006 — HTTP live block polling run-local
## Base requise
```text
livraison précédente : 0.3.13-pre.005-fix.001
Cargo base : 0.3.13-pre.5.fix.1
delta base : deltas/0.3.13/pre.005-fix.001.md
archive delta base : ksp-general-0.3.13-pre.005-fix.001.zip
```
Le gate opérateur reçu le 10 septembre 2026 sur cette base est entièrement vert pour les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`84` unit, `4` cross-layer, `12` dependency-boundary, `19` hardening, `13` public-api, `4` release-completeness, `0` doc-test). Cette exécution ne contenait ni le test Transport ni les commandes `cargo tree`; ils ne sont donc pas déclarés PASS pour ce gate précis.
## Objectif
Ajouter la source HTTP live prévue par la tranche sans la transformer en Backfill :
```text
getSlot(commitment)
-> borne inférieure du run
getBlocksWithLimit(next_scan_slot, bounded_limit, commitment)
-> découverte des blocs disponibles
getBlock observed(slot, Full/Base64/maxV1/rewards=false)
-> Common RAW direct Legacy/V0/V1
-> admission centrale Worker
-> Store
```
Le polling ne remonte jamais avant la borne observée au démarrage et ne crée aucun checkpoint durable inter-run.
## Version
```text
livraison : 0.3.13-pre.006
workspace.package.version : 0.3.13-pre.6
archive : ksp-general-0.3.13-pre.006.zip
```
`Cargo.toml` passe également de la version de fichier `549` à `550`.
## Source HTTP Block Polling
Nouvelle surface publique Worker :
```text
RawTransactionIngestHttpBlockPollingSource
RawTransactionIngestRuntimeResources::from_http_block_polling_source
RawTransactionIngestRuntimeResources::try_push_http_block_polling_source
DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL
MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL
MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL
DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE
MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE
MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE
```
La construction caller-owned reçoit un `HttpTransportPool`, un `HttpRoleName` et un commitment `Confirmed` ou `Finalized`. Elle est sans I/O et vérifie statiquement que le rôle possède des routes compatibles `getSlot`, `getBlocksWithLimit` et `getBlock` sur un même réseau.
Bornes publiques :
```text
poll interval : défaut 1 s ; min 100 ms ; max 30 s
max discovered blocks/cycle : défaut 128 ; min 1 ; max 1024
```
Les limitations physiques, la sélection d'endpoint, le rate-limit, les retries et le backoff HTTP restent possédés par `ksp-onchain-transport-lib`.
## Borne de run et progression
Au démarrage :
```text
start_slot = getSlot(commitment)
next_scan_slot = start_slot
```
Le Worker ne demande aucun slot inférieur à `start_slot`. Pour chaque cycle, il relit le tip courant, puis appelle `getBlocksWithLimit` depuis `next_scan_slot`. La réponse doit être strictement croissante et ne peut contenir aucun slot inférieur à la frontier de scan.
Chaque slot listé est matérialisé par `getBlock observed`. Si le résultat vaut `null` :
```text
next_scan_slot = slot
aucun settled
aucun Common RAW
reprise au cycle suivant
```
Le cas `null` n'est donc ni assimilé à un bloc vide ni converti en progression silencieuse.
Les slots non listés par `getBlocksWithLimit` sont traités comme non produits/skipped dans le run courant. Lorsque la liste reçue ne remplit pas le batch demandé et qu'aucun slot n'est bloqué par `null`, la frontier peut rejoindre le tip observé du cycle sans lancer de scan historique implicite.
## RAW-direct et version
Le poller demande exactement :
```text
encoding = Base64
transactionDetails = Full
maxSupportedTransactionVersion = 1
showRewards = false
commitment = Confirmed | Finalized
```
Chaque transaction qualifiée produit directement `RawTransactionMaterial` avec :
```text
signature embarquée dans le wire Base64
slot
blockTime
meta
version Legacy | 0 | 1
transactionIndex dérivé de la position dans le bloc
```
Les cas suivants sont fail-closed : transaction non Base64, transactions omises/nulles, version omise/nulle, version `>1`, signature invalide ou index non représentable en `u32`.
La voie HTTP Block Polling est RAW-direct et n'utilise pas le coordinateur d'hydration. Le slot n'est marqué `settled` qu'après l'envoi réussi de toutes les transactions du bloc vers l'admission centrale ; `hydration_pending` n'est jamais artificiellement incrémenté.
## Provenance observée
La provenance de chaque transaction reprend l'endpoint et le provider réellement gagnants de `getBlock observed` :
```text
origin = Live
protocol = solana_http
method = block_polling_get_block
endpoint = endpoint observé
provider = provider observé
commitment = source commitment
capture session = WorkerId
```
Aucun URL, header HTTP ou corps brut n'est exposé par `HttpObservedValue` ou les diagnostics Worker.
## Identité logique
Le `source_key` HTTP polling est dérivé de :
```text
domain KSP live-source v1
family = http_block_polling
network
polling role
commitment
private route-profile fingerprint
SHA-256 -> [u8; 32]
```
L'empreinte de profil est construite à partir des identités sûres cluster/provider/endpoint et des capacités pertinentes de chaque route active, après tri déterministe. La cadence et le batch de découverte n'entrent volontairement pas dans l'identité logique : modifier un réglage de timing ne crée pas une deuxième source conceptuelle.
Le bound global `1..32`, l'invariant réseau unique et le rejet transactionnel des doublons de `pre.002` s'appliquent à cette famille.
## Activation runtime
Le contrat reste volontairement mono-source avant `pre.007` :
```text
1 source Yellowstone : productif
1 source Standard Logs : productif
1 source Standard Block : productif
1 source Helius Transaction : productif
1 source HTTP Block Polling : productif
2..32 sources : composition validable
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
```
Aucune source configurée n'est ignorée silencieusement.
## Preuves ajoutées
Unit tests Worker :
```text
bornes cadence/batch et commitment
présence exacte des capacités HTTP requises
source_key stable malgré changement de timing et différent si route logique différente
redaction URL sensible dans Debug
validation stricte de la découverte croissante
qualification Legacy/V0/V1 et rejet version ambiguë/future
rejet transactionnel d'une identité HTTP polling dupliquée
```
Canaris externes :
```text
public_api : constructeurs/from/push et bornes publiques disponibles depuis crate root
dependency_boundary : Transport facade uniquement, aucun Config/Backfill/backend/direct HTTP client
hardening : run-local, timer borné, getBlock observed unique, aucun task par tick, aucun hydration_pending direct
release_completeness : exports et canaris pre.006 obligatoires
```
Les anciens canaris qui interdisaient globalement `get_block_observed` ou `tokio::time::sleep` sont resserrés sur le coordinateur d'hydration : ces primitives restent interdites dans l'hydration mais sont désormais légitimes, explicites et bornées dans la seule source HTTP polling.
## Documentation
`README.md`, le README du crate, `USAGE.md`, le plan et le journal de validation sont alignés sur la cinquième famille productive. `USAGE.md` reste version-neutral : il décrit la construction et le comportement sans journaliser le numéro de prerelease.
Une incohérence du README de crate, qui classait encore le live HTTP polling parmi les éléments hors périmètre, est corrigée dans cette tranche.
## Fichiers modifiés
```text
Cargo.toml
README.md
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.006.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
`pre.006` ne prétend pas :
```text
supervision simultanée de plusieurs sources
checkpoint durable du polling
Backfill implicite
scan de slots antérieurs au start_slot du run
retry/backoff HTTP implémenté dans Worker
client reqwest direct dans Worker
Config lu dans Worker
getBlock=null assimilé à un bloc vide
transaction version >1 ou ambiguë acceptée
modification de ksp-onchain-transport-lib
nouvelle dépendance externe
```
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.006` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, le contrôle des versions de fichiers et le diff exhaustif sont exécutés avant packaging.
## Gate opérateur requis avant pre.007
```bash
cargo fmt --all
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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
`pre.007` ne doit pas commencer si ce gate révèle une anomalie.
## Résultats statiques finaux de l'assemblage
Les valeurs ci-dessous sont mises à jour après la dernière passe d'audits sur l'état exact livré.
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 839 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
## Contrôle exhaustif avant packaging
Comparaison avec la base `0.3.13-pre.005-fix.001` :
```text
added : 1
deltas/0.3.13/pre.006.md
modified : 13
Cargo.toml
README.md
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
deleted : 0
```
Les `13/13` fichiers existants modifiés portent une version de fichier exactement `+1`. Le diff de `Cargo.toml` est limité au header `549 -> 550` et à `workspace.package.version = "0.3.13-pre.6"`; aucun membre workspace ni aucune dépendance n'est modifié.
La revue statique ciblée confirme dans le code Worker :
```text
get_block_observed( : 1 occurrence, source HTTP polling uniquement
get_blocks_with_limit( : 1 occurrence, source HTTP polling uniquement
tokio::time::sleep : 1 occurrence, cadence du poller uniquement
aucun reqwest/tokio-tungstenite/tonic/yellowstone-grpc-proto direct
aucun ksp-config-lib/ksp-job-backfill-lib/ksp-store-postgres-lib direct
aucun unbounded_channel
```

218
deltas/0.3.13/pre.007.md Normal file
View File

@@ -0,0 +1,218 @@
<!-- file: deltas/0.3.13/pre.007.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.007 — supervision et inventaire multi-source
## Base requise
```text
livraison précédente : 0.3.13-pre.006-fix.001
Cargo base : 0.3.13-pre.6.fix.1
delta base : deltas/0.3.13/pre.006-fix.001.md
archive delta base : ksp-general-0.3.13-pre.006-fix.001.zip
```
Le gate opérateur communiqué le 10 septembre 2026 est vert sur toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`88` unit, `4` cross-layer, `13` dependency-boundary, `20` hardening, `14` public-api, `4` release-completeness, `0` doc-test). Le log reçu ne contient pas les sorties séparées `cargo test -p ksp-onchain-transport-lib` ni les trois commandes `cargo tree`; elles ne sont donc pas déclarées PASS dans ce delta.
## Objectif
Activer réellement la collection `1..32` déjà composable depuis `pre.002` :
```text
RawTransactionIngestRuntimeResources
-> validation complète réseau / doublons / borne
-> spawn de toutes les sources configurées
-> inventaire privé source_key -> latest state/frontier
-> projection frontier source-neutral agrégée
-> terminalité conservatrice si une source disparaît ou échoue
```
Aucun provider n'est traité comme primaire, fallback ou standby.
## Version
```text
livraison : 0.3.13-pre.007
workspace.package.version : 0.3.13-pre.7
archive : ksp-general-0.3.13-pre.007.zip
```
`Cargo.toml` passe de la version de fichier `551` à `552`.
## Supervisor multi-source
`RawTransactionIngestWorker::start_with_runtime_resources` ne vérifie plus `validate_single_source_activation`. Après validation du réseau, le runtime délègue à `RawTransactionIngestRuntimeResources::run_live_sources`.
Le supervisor privé :
```text
crée un stop channel source-owned
crée un JoinSet borné par la collection 1..32
spawn chaque source configurée exactement une fois
clone uniquement les handles nécessaires
rejoint toutes les tasks avant de rendre son résultat
```
La vieille erreur `runtime_resources.multi_source_activation_pending` disparaît du code production.
## Terminalité conservatrice
La politique reste fail-closed :
```text
stop Worker
-> fanout stop vers toutes les sources
-> join complet
-> Ok seulement si les sources ferment sans erreur
source Err
-> stop immédiat des autres sources
-> join complet
-> faute source terminale
source Ok avant stop Worker
-> fermeture inattendue d'une source configurée
-> stop des autres sources
-> faute terminale
JoinError
-> faute terminale sûre
```
Le Worker ne suppose pas qu'une autre source couvre les mêmes filtres, le même provider universe ou la même complétude.
## Inventaire privé et processing frontier
`RawTransactionIngestSourceInventory` et `RawTransactionIngestSourceInventoryPublisher` restent privés au module runtime resources. L'inventaire est initialisé depuis les `source_key` déjà validées et possède exactement une entrée latest-value par source du run.
Chaque source conserve son propre publisher local. Un wrapper privé reporte ses projections dans l'inventaire puis publie une projection globale conservative :
```text
hydration_pending : somme saturante temporaire des sources
oldest_pending_slot : minimum global
processing_frontier_slot : None si une source n'a pas encore de frontier
sinon minimum de toutes les frontiers
source_state : Failed > Reconnecting > Closing > Active > Closed/None
reconnect/replay/gap : agrégation saturante, jamais wrap arithmétique
```
Le choix du minimum n'est pas une preuve de coverage blockchain. La frontier reste run-local et source-neutral.
## Propriété d'ownership
Les sources physiques continuent d'appartenir à leurs façades Transport existantes. Le Worker ne crée ni client reqwest, ni socket alternatif, ni SDK provider. Le supervisor ajoute uniquement l'ownership des tasks source et de leur lifecycle commun.
Une source qui termine avant le stop est explicitement marquée `Closed` ou `Failed` dans l'inventaire avant le résultat du supervisor. Les identités `source_key` restent privées et ne sont jamais projetées dans `RawTransactionIngestSnapshot` ou le crate root.
## Preuves ajoutées
Unit tests déterministes :
```text
inventaire 2 sources : pending/frontier/state/counters agrégés conservativement
stop supervisor : trois tasks source actives reçoivent le stop et sont toutes jointes
source failure : la source sœur reçoit le stop et est jointe avant retour de faute
```
Canaris externes :
```text
dependency_boundary : run_live_sources, JoinSet, inventaire privé, vieille barrière absente
hardening : stop/fault/join fail-closed, aucune policy primary/standby, aucun canal unbounded
public_api : inventaire/live-source/source_key non exportés
```
Les canaris historiques `0.3.12` qui cherchaient `run_single_live_source` sont mis à jour vers le supervisor multi-source au lieu d'être supprimés.
## Frontière de tranche
`pre.007` active la simultanéité, mais ne ferme pas encore la convergence transactionnelle cross-source :
```text
pas de coalescence globale de la même signature entre plusieurs sources
pas d'unique hydration cross-source garantie
pas de fusion de plusieurs provenance/observation seeds vers une acquisition canonique unique
pas encore de record_raw_transaction_observation pour les observations supplémentaires convergées
pas encore de fairness durcie duplicate-storm
pas encore de source_total/source_active/source_failed publics
```
Les coordinators d'hydration des sources reference-bearing restent donc source-local pendant cette tranche. La fermeture de la coalescence globale et des observations multiples appartient à `pre.008`, puis les bornes/fairness renforcées à `pre.009`.
## Documentation
Le README du crate décrit désormais le supervisor 1..32, la terminalité conservative et la frontier agrégée. `USAGE.md` reste version-neutral et montre comment composer plusieurs sources via les `try_push_*` existants sans mentionner le numéro de prerelease.
Le plan et la validation consignent le gate opérateur réellement reçu et distinguent explicitement ce qui reste reporté à `pre.008+`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.007.md
```
## Fichiers supprimés
```text
aucun
```
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.007` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, les versions de fichiers et le diff exhaustif sont exécutés sur l'état exact avant packaging.
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 841 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
Contrôle ciblé du supervisor :
```text
ancienne barrière validate_single_source_activation : absente
runtime_resources.multi_source_activation_pending : absent du code production
inventaire source public : absent
source_key public : absent
unbounded_channel : absent
nouvelle dépendance : aucune
```
## Gate opérateur requis avant pre.008
```bash
cargo fmt --all
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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```

View File

@@ -0,0 +1,85 @@
<!-- file: deltas/0.3.13/pre.008-fix.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.008-fix.001 — correction compilation convergence persistence
## Base requise
```text
livraison précédente : 0.3.13-pre.008
Cargo base : 0.3.13-pre.8
delta base : deltas/0.3.13/pre.008.md
archive delta base : ksp-general-0.3.13-pre.008.zip
```
Le gate opérateur du 10 septembre 2026 sur `pre.008` est vert pour `cargo fmt`, `cargo fmt --check` et les audits Rust/Markdown. `cargo check --workspace` met ensuite en évidence deux défauts de compilation dans `ksp-worker-raw-transaction-ingest-lib` :
```text
unused import : pub(crate) use self::persistence::persist_raw_transaction_ingest_acquisition
E0004 : match non exhaustif sur RawObservationWriteOutcome #[non_exhaustive]
```
Clippy strict reproduit les deux défauts et les tests ne peuvent pas être compilés avant leur correction.
## Objectif du fix
Corriger strictement ces deux défauts sans modifier le contrat de convergence `pre.008` :
```text
re-export crate-root : conservé conformément aux règles KSP
appels internes : via crate::persist_raw_transaction_ingest_acquisition(...)
non-exhaustive enum : wildcard fail-closed vers une erreur runtime stable
```
Le wildcard ne transforme aucune variante future en succès implicite. Toute variante `RawObservationWriteOutcome` inconnue est rejetée comme `persistence.additional_observation_outcome_invalid`.
## Version
```text
livraison : 0.3.13-pre.008-fix.001
workspace.package.version : 0.3.13-pre.8.fix.1
archive : ksp-general-0.3.13-pre.008-fix.001.zip
```
`Cargo.toml` passe de la version de fichier `553` à `554`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
```
`persistence.rs` passe de la version de fichier `2` à `3`.
## Fichiers ajoutés
```text
deltas/0.3.13/pre.008-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
Ce fix ne modifie aucune dépendance, aucune API publique, aucun cache de convergence, aucune clé de coalescence, aucune sémantique d'hydration, aucune observation/provenance et aucune règle Store de première acquisition ou d'observation supplémentaire.
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
## Gate opérateur requis avant pre.009
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```

View File

@@ -0,0 +1,89 @@
<!-- file: deltas/0.3.13/pre.008-fix.002.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.008-fix.002 — correction visibilité hydration globale et constructeur de test
## Base requise
```text
livraison précédente : 0.3.13-pre.008-fix.001
Cargo base : 0.3.13-pre.8.fix.1
delta base : deltas/0.3.13/pre.008-fix.001.md
archive delta base : ksp-general-0.3.13-pre.008-fix.001.zip
```
Le gate opérateur du 10 septembre 2026 confirme que `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown et `cargo check --workspace` passent. `cargo check` signale toutefois quatre warnings dans le Worker, puis Clippy strict les transforme en erreurs :
```text
3 x private_interfaces : trois méthodes pub(crate) run exposent RawTransactionIngestGlobalHydrationRegistry, qui reste privée au module
1 x dead_code : RawTransactionIngestHydrationCoordinator::new n'est plus utilisé en production depuis la registry globale pre.008
```
Malgré l'échec Clippy, `cargo test -p ksp-worker-raw-transaction-ingest-lib` compile et passe ensuite : `94/94` tests unitaires et toutes les suites d'intégration affichées sont vertes.
## Objectif du fix
Corriger strictement ces quatre diagnostics sans élargir l'API interne et sans modifier le contrat de convergence :
```text
YellowstoneSource::run : pub(crate) -> privé au module
HeliusTransactionSource::run : pub(crate) -> privé au module
StandardLogsSource::run : pub(crate) -> privé au module
HydrationCoordinator::new : compilé uniquement sous #[cfg(test)]
GlobalHydrationRegistry : reste privée au module
```
Les trois méthodes `run` concernées ne sont appelées que par `RawTransactionIngestLiveSource::run` dans le même module. La registry globale ne doit donc pas être rendue `pub(crate)` uniquement pour satisfaire la visibilité d'une signature.
Le constructeur `RawTransactionIngestHydrationCoordinator::new` reste disponible pour les tests unitaires historiques mais n'entre plus dans le binaire production ; le runtime `pre.008` utilise `with_global_registry` avec la registry partagée du supervisor.
## Version
```text
livraison : 0.3.13-pre.008-fix.002
workspace.package.version : 0.3.13-pre.8.fix.2
archive : ksp-general-0.3.13-pre.008-fix.002.zip
```
`Cargo.toml` passe de la version de fichier `554` à `555`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
```
`runtime_resources.rs` passe de la version de fichier `22` à `23`.
## Fichiers ajoutés
```text
deltas/0.3.13/pre.008-fix.002.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
Ce fix ne modifie aucune dépendance, aucune API publique, aucune clé de convergence, aucune borne de registry/cache, aucune sémantique d'hydration, aucune observation/provenance, aucun comportement Store et aucune politique multi-source.
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
## Gate opérateur requis avant pre.009
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```

198
deltas/0.3.13/pre.008.md Normal file
View File

@@ -0,0 +1,198 @@
<!-- file: deltas/0.3.13/pre.008.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.008 — convergence cross-source et observations multiples
## Base requise
```text
livraison précédente : 0.3.13-pre.007
Cargo base : 0.3.13-pre.7
delta base : deltas/0.3.13/pre.007.md
archive delta base : ksp-general-0.3.13-pre.007.zip
```
Le gate opérateur communiqué le 10 septembre 2026 est vert pour toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites `ksp-worker-raw-transaction-ingest-lib` (`90` unit, `4` cross-layer, `14` dependency-boundary, `21` hardening, `15` public-api, `4` release-completeness, `0` doc-test). Le log reçu ne contient pas les sorties séparées `cargo test -p ksp-onchain-transport-lib` ni les trois commandes `cargo tree`; elles ne sont donc pas déclarées PASS ici.
## Objectif
Fermer la convergence des sources live simultanées sans dupliquer l'identité `RawTransaction` :
```text
plusieurs sources reference-bearing
-> une hydration globale par (network, signature, commitment)
-> acquisitions source-distinctes
-> canonicalisation RAW commune
-> convergence par (network, signature) + content hash
-> une entity RAW
-> plusieurs observations déterministes
```
## Version
```text
livraison : 0.3.13-pre.008
workspace.package.version : 0.3.13-pre.8
archive : ksp-general-0.3.13-pre.008.zip
```
`Cargo.toml` passe de la version de fichier `552` à `553`.
## Hydration globale cross-source
`RawTransactionIngestRuntimeResources::run_live_sources` crée un `RawTransactionIngestGlobalHydrationRegistry` privé partagé par Yellowstone, Standard Logs et Helius Transaction. Standard Block et HTTP Block Polling restent RAW-direct et n'entrent pas dans ce registre.
La clé reste exactement :
```text
(network, signature, commitment)
```
Le premier demandeur devient leader et exécute le `getTransaction observed` existant. Les suivants s'abonnent au même résultat via un `watch` privé. La registry est bornée par `admission_queue_capacity`, publie soit le résultat observed soit un `ErrorCode` sûr, puis retire la clé.
Les signaux source restent distincts : le même résultat d'hydration est finalisé séparément avec le contexte/provenance de chaque signal.
## Convergence persistence
Le runtime possède un `RawTransactionIngestPersistenceConvergence` privé et run-local. Sa capacité productive est :
```text
max(admission_queue_capacity, persistence_concurrency)
```
Chaque identité `(network, signature)` possède un verrou async local et mémorise le `RawContentHash` canonique après une première persistence réussie non purgée.
```text
première acquisition
-> persist_raw_transaction_acquisition
-> Store atomique entity + observation
même identity + même content hash
-> record_raw_transaction_observation uniquement
même identity + hash divergent
-> content conflict terminal
```
Le cache évince uniquement une entrée inactive lorsqu'il atteint sa borne. Le runtime dimensionne cette borne pour couvrir toutes les tâches persistence simultanées valides.
## Observations multiples
L'`observation_key` reste dérivée de la référence canonique et du `source_key` privé. Deux sources distinctes observant le même RAW produisent donc une seule référence/entity mais des observations déterministes distinctes.
Un replay de la même observation retourne `AlreadyPresent` sans réécriture de l'entité. Une observation supplémentaire `NotRecorded` est incohérente pour une entité déjà durable et devient une faute runtime sûre.
## Preuves ajoutées
Tests unitaires déterministes :
```text
deux source_key -> même référence/hash canonique, observation_key distinctes
registry globale -> un leader, un follower, publication partagée puis retrait
première identity -> une persistence atomique
seconde observation distincte -> record_observation seul
replay même observation -> AlreadyPresent sans nouvelle persistence entity
```
Canaris externes :
```text
dependency_boundary : Store/Transport facades uniquement, pas de backend/client direct
hardening : caches bornés, hash conflict-checked, convergence privée, aucun canal unbounded
public_api : registry/cache/hydration key/source_key non exposés
release_completeness : présence obligatoire des trois canaris pre.008
```
## Frontière de tranche
`pre.008` ne ferme pas encore :
```text
fairness adversariale sous duplicate storm prolongé
starvation entre sources
matrice complète de disagreement provider/source
stress des bornes globales
nouveaux compteurs/health publics multi-source
```
Ces points restent `pre.009` et `pre.010` selon le plan.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.008.md
```
## Fichiers supprimés
```text
aucun
```
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.008` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, le contrôle exhaustif du diff, des headers et du ZIP sont exécutés avant livraison.
Résultats statiques finaux :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 842 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
Contrôle ciblé :
```text
17 fichiers existants modifiés
1 fichier ajouté
0 suppression
17/17 headers existants : +1
nouvelle dépendance : aucune
backend Store physique direct : absent
client HTTP/gRPC direct : absent
unbounded_channel : absent
convergence/registry publiques : absentes
```
## Gate opérateur requis avant pre.009
```bash
cargo fmt --all
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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```

View File

@@ -0,0 +1,137 @@
<!-- file: deltas/0.3.13/pre.009-fix.001.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.009-fix.001 — alignement des canaris de quotas et correction du gate cargo tree
## Base requise
```text
livraison précédente : 0.3.13-pre.009
Cargo base : 0.3.13-pre.9
delta base : deltas/0.3.13/pre.009.md
archive delta base : ksp-general-0.3.13-pre.009.zip
```
Le gate opérateur du 10 septembre 2026 confirme :
```text
cargo fmt + --check : PASS
audits Rust : clean / export completeness 0
Markdown : clean, 340 tables / 845 files
cargo check --workspace : PASS
Clippy strict workspace/all-targets/all-features : PASS
Transport unit : 389/389 PASS
Transport public API : 52/52 PASS
Transport release completeness : 44/44 PASS
Transport doc-tests : 4/4 PASS
Transport smokes live opt-in : 5 ignored comme prévu
Worker unit : 99/99 PASS
Worker cross-layer : 4/4 PASS
Worker dependency-boundary : 13 PASS / 3 FAIL
```
La suite Worker s'arrête sur ces trois canaris `dependency_boundary`; `hardening`, `public_api`, `release_completeness` et les doc-tests Worker ne sont donc pas déclarés PASS pour ce gate.
Les trois échecs exigent encore la chaîne historique :
```text
max_pending_signals: settings.admission_queue_capacity()
```
Cette assertion n'est plus correcte depuis `pre.009`. Les sources reference-bearing reçoivent désormais des quotas `hydration_pending_limit` et `hydration_in_flight_limit` calculés à partir des budgets globaux, tandis que les preuves `pre.009` verrouillent la partition exacte des capacités configurées.
Une revue préventive trouve également le même canari obsolète dans `tests/hardening.rs`, avec l'ancienne hypothèse `max_in_flight: settings.persistence_concurrency()`. Il est corrigé dans le même fix avant qu'il ne devienne le prochain échec.
## Objectif du fix
Mettre à jour les preuves sans modifier la logique runtime :
```text
anciens canaris source-specific : vérifient with_global_registry + quotas source locaux
canari pre.009 dependency : vérifie aussi le lien budgets globaux -> settings
hardening historique : vérifie les champs bornés + quotas, sans ancienne égalité locale
runtime : inchangé
```
Les anciens canaris ne sont pas affaiblis : la relation directe entre capacités Worker et budgets globaux est désormais vérifiée par le canari `pre.009`, et les canaris historiques continuent à vérifier que chaque source utilise le coordinator borné commun.
## Correction du workflow cargo tree
Le gate opérateur avant `pre.010` ne doit pas imposer `cargo tree` : `pre.009` ne modifie aucune dépendance ni feature et `pre.010` n'est pas la fermeture technique de `0.3.13`.
La règle courante est explicitée dans le prompt `0.3.13` :
```text
cargo tree requis si le graphe dépendances/features change
ou à la fermeture technique de 0.3.13
pas à chaque prerelease sans changement de graphe
```
Le plan, la validation et le delta `pre.009` sont alignés sur cette règle pour le gate avant `pre.010`.
## Version
```text
livraison : 0.3.13-pre.009-fix.001
workspace.package.version : 0.3.13-pre.9.fix.1
archive : ksp-general-0.3.13-pre.009-fix.001.zip
```
`Cargo.toml` passe de la version de fichier `556` à `557`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
prompts/032-V0_3_13_START_PROMPT.md
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
deltas/0.3.13/pre.009.md
```
Versions de fichiers :
```text
dependency_boundary.rs : 25 -> 26
hardening.rs : 19 -> 20
032 start prompt : 1 -> 2
plan 034 : 9 -> 10
validation 030 : 9 -> 10
pre.009.md : 1 -> 2
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.009-fix.001.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
Ce fix ne modifie aucune source production, aucun quota calculé, aucune borne globale, aucun sémaphore, aucune registry, aucune logique de disagreement, aucune dépendance, aucune feature et aucune API publique.
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
## Gate opérateur requis avant pre.010
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
Aucun `cargo tree` n'est requis pour ce gate. Le test Transport n'a pas besoin d'être répété par ce fix, qui ne modifie ni Transport, ni dépendance, ni feature.

View File

@@ -0,0 +1,148 @@
<!-- file: deltas/0.3.13/pre.009-fix.002.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.009-fix.002 — alignement du canari de disagreement canonique
## Base requise
```text
livraison précédente : 0.3.13-pre.009-fix.001
Cargo base : 0.3.13-pre.9.fix.1
delta base : deltas/0.3.13/pre.009-fix.001.md
archive delta base : ksp-general-0.3.13-pre.009-fix.001.zip
```
Le gate opérateur du 10 septembre 2026 confirme :
```text
cargo fmt + --check : PASS
audits Rust : clean / export completeness 0
Markdown : clean, 340 tables / 846 files
cargo check --workspace : PASS
Clippy strict workspace/all-targets/all-features : PASS
Transport unit : 389/389 PASS
Transport public API : 52/52 PASS
Transport release completeness : 44/44 PASS
Transport doc-tests : 4/4 PASS
Transport smokes live opt-in : 5 ignored comme prévu
Worker unit : 99/99 PASS
Worker cross-layer : 4/4 PASS
Worker dependency-boundary : 16/16 PASS
Worker hardening : 22 PASS / 1 FAIL
```
La suite Worker s'arrête sur le canari :
```text
v0_3_13_pre_008_cross_source_convergence_is_bounded_conflict_checked_and_private
```
Il exige encore l'ancienne chaîne d'implémentation :
```text
known_hash_value != content_hash
```
Cette assertion est obsolète depuis `pre.009`, où l'état canonique de convergence compare désormais ensemble `slot`, `block_time`, `format_id`, `format_version` et `content_hash` via `RawTransactionIngestCanonicalState`.
`public_api`, `release_completeness` et les doc-tests Worker ne sont donc pas déclarés PASS pour ce gate, car l'exécution s'arrête sur `hardening`.
## Objectif du fix
Corriger uniquement le canari historique `pre.008` afin qu'il vérifie le conflit canonique actuel sans revenir à une comparaison hash-only :
```text
ancienne preuve : known_hash_value != content_hash
nouvelle preuve : known_state_value != &canonical_state
```
Le canari continue à exiger la borne du cache, l'éviction uniquement des entrées non partagées, le passage par `record_observation` et le fail-closed d'une observation additionnelle impossible.
Aucune source production n'est modifiée.
## Immutabilité des deltas
Les deltas déjà livrés sont immuables. Ce fix n'altère aucun fichier `deltas/` existant, y compris :
```text
deltas/0.3.13/pre.009.md
deltas/0.3.13/pre.009-fix.001.md
```
La correction est tracée uniquement par le nouveau fichier `deltas/0.3.13/pre.009-fix.002.md`.
La modification de `pre.009.md` qui avait été incluse dans `pre.009-fix.001` reste une exception historique déjà acceptée par l'opérateur ; elle n'est ni répétée ni réécrite dans ce fix.
## Version
```text
livraison : 0.3.13-pre.009-fix.002
workspace.package.version : 0.3.13-pre.9.fix.2
archive : ksp-general-0.3.13-pre.009-fix.002.zip
```
`Cargo.toml` passe de la version de fichier `557` à `558`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
```
`hardening.rs` passe de la version de fichier `20` à `21`.
## Fichiers ajoutés
```text
deltas/0.3.13/pre.009-fix.002.md
```
## Fichiers supprimés
```text
aucun
```
## Validations exécutées dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`.
Sont exécutés avant packaging :
```text
scripts/audit_rust_workspace_rules.py
audit Markdown complet
scan des définitions normatives
diff exact contre la base pre.009-fix.001
contrôle d'immutabilité byte-for-byte de tous les deltas existants
contrôle du manifest et du contenu extrait de l'archive
```
## Décisions
- le runtime `pre.009` reste inchangé ;
- le disagreement canonique reste fondé sur l'état canonique complet, pas sur le seul hash ;
- aucun delta historique n'est modifié ;
- aucune dépendance ni feature ne change ;
- aucun `cargo tree` n'est requis pour ce fix.
## Questions ouvertes
```text
aucune
```
## Gate opérateur requis avant pre.010
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
Le test Transport n'a pas besoin d'être répété pour ce fix, qui ne modifie ni Transport, ni source production, ni dépendance, ni feature.

221
deltas/0.3.13/pre.009.md Normal file
View File

@@ -0,0 +1,221 @@
<!-- file: deltas/0.3.13/pre.009.md -->
<!-- version: 2 -->
# Delta 0.3.13-pre.009 — disagreements, duplicate storms, bornes globales et fairness
## Base requise
```text
livraison précédente : 0.3.13-pre.008-fix.002
Cargo base : 0.3.13-pre.8.fix.2
delta base : deltas/0.3.13/pre.008-fix.002.md
archive delta base : ksp-general-0.3.13-pre.008-fix.002.zip
```
Les gates opérateur communiqués le 10 septembre 2026 autorisent cette tranche. Le gate Worker est vert pour `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites `ksp-worker-raw-transaction-ingest-lib` : `94` unit, `4` cross-layer, `15` dependency-boundary, `22` hardening, `16` public-api, `4` release-completeness et `0` doc-test.
Le gate étendu complète cette qualification avec `cargo test -p ksp-onchain-transport-lib` : `389` unit, `52` public-api, `44` release-completeness et `4` doc-tests passent. Les cinq smokes réseau opt-in affichés restent ignorés comme prévu. Les commandes `cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal`, `cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features` et `cargo tree --duplicates` sont exécutées sans erreur ; `cargo tree --duplicates` liste les doublons existants du workspace sans introduire de promesse de graphe sans doublon.
## Objectif
Fermer les risques adversariaux qui restent après la convergence cross-source de `pre.008` :
```text
disagreement entre sources
storms de références dupliquées
borne globale des pending hydration signals
borne globale des tâches/hydrations HTTP
cleanup d'un leader global annulé
fairness minimale entre sources reference-bearing
absence de starvation structurelle
```
La tranche ne modifie pas encore la projection publique de health/snapshots multi-source ; ce point reste réservé à `pre.010`.
## Version
```text
livraison : 0.3.13-pre.009
workspace.package.version : 0.3.13-pre.9
archive : ksp-general-0.3.13-pre.009.zip
```
`Cargo.toml` passe de la version de fichier `555` à `556`.
## Budgets globaux et fairness minimale
Yellowstone, Standard Logs et Helius Transaction sont les sources reference-bearing qui nécessitent une hydration `getTransaction`. Avant tout spawn, `run_live_sources` vérifie :
```text
hydration_source_count <= admission_queue_capacity
hydration_source_count <= persistence_concurrency
```
Si une de ces relations est fausse, le démarrage échoue fail-closed avec un `runtime_invalid` stable. Aucune capacité configurée n'est agrandie silencieusement.
Lorsque la composition est valide, les deux budgets sont partitionnés statiquement par quotient/reste suivant l'ordre déjà validé des sources :
```text
somme hydration pending quotas = admission_queue_capacity
somme hydration in-flight quotas = persistence_concurrency
quota de chaque source reference-bearing >= 1
```
Une source ne peut donc pas consommer la part réservée aux autres sources reference-bearing. Ce mécanisme n'introduit ni scheduler pondéré, ni priorité provider, ni primary/standby.
Standard Block et HTTP Block Polling restent RAW-direct et continuent à utiliser l'admission centrale bornée sans passer par ces quotas d'hydration.
## Borne globale d'hydration
Le `RawTransactionIngestGlobalHydrationRegistry` reste privé et borné à `admission_queue_capacity` clés distinctes. Un duplicate storm sur une clé déjà présente rejoint le résultat global existant et ne crée ni nouvelle entrée ni second leader HTTP.
Un `tokio::sync::Semaphore` global, dimensionné à `persistence_concurrency`, protège en plus le nombre de leaders HTTP effectivement ouverts. Les coordinators locaux conservent simultanément leurs quotas `max_pending_signals` et `max_in_flight`.
Une clé distincte au-delà de la borne globale produit `source.global_hydration_pending_saturated` au lieu d'une croissance mémoire non bornée.
## Cleanup d'un leader global
Chaque leader de la registry possède un `RawTransactionIngestHydrationLeaderGuard` privé. Si la tâche est annulée ou droppée avant la publication normale du résultat, le guard publie une faute source sûre aux followers et retire la clé du registre.
Après cleanup, la même clé peut redevenir leader lors d'une acquisition ultérieure. Aucun leader orphelin ne doit survivre à un abort/shutdown.
## Disagreement canonique
Le cache run-local de convergence persistence ne compare plus uniquement `RawContentHash`. Pour une même identité durable `(network, signature)`, il mémorise maintenant un état canonique borné :
```text
slot
block_time
format_id
format_version
content_hash
```
Une divergence sur l'une de ces dimensions produit immédiatement le content conflict terminal existant avant toute observation additionnelle. Aucun byte de transaction n'est recopié dans le cache ; le hash provient de la canonicalisation Common RAW et le Store conserve son contrôle durable final sur le contenu exact.
La politique reste conservatrice : aucune majorité de sources, aucun first-provider-wins, aucune préférence provider/tier et aucun overwrite de conflit.
## Fairness et duplicate storms
La fairness minimale est prouvée sur le canal d'admission commun borné à capacité `1`. Après qu'une source en storm a rejoint la file d'attente, une seconde source déjà prête rejoint la même file et progresse avant la répétition suivante de la storm.
Cette preuve ne revendique pas une QoS pondérée. Elle verrouille uniquement l'absence de starvation indéfinie d'une source déjà prête sous le contrat FIFO du canal borné commun, tandis que les quotas statiques empêchent la starvation structurelle des sources reference-bearing en amont.
## Preuves ajoutées
Tests unitaires déterministes :
```text
budgets 8/5 répartis sur 3 sources -> [3,3,2] pending et [2,2,1] in-flight
composition sous-provisionnée -> refus avant spawn
32 duplicats d'une même clé -> un seul leader global
clé distincte au-delà de max_pending -> saturation sûre
sémaphore capacité 1 -> une seule permit simultanée
leader guard droppé -> Failed publié + clé réouvrable
queue admission capacité 1 -> seconde source prête progresse sous storm
même signature + slot divergent -> content conflict
même signature + block_time divergent -> content conflict
même signature + contenu/hash divergent -> content conflict
aucune observation additionnelle après disagreement
```
Canaris externes :
```text
dependency_boundary : bornes/fairness restent dans Worker + façades existantes
hardening : quotas, sémaphore, cleanup, disagreement et absence de provider preference
public_api : registry, leader guard, quotas et canonical state restent privés
release_completeness : présence obligatoire des trois canaris pre.009
```
## Frontière de tranche
`pre.009` ne ferme pas encore :
```text
nouveaux compteurs/health publics multi-source
projection détaillée par source
politique de dégradation non terminale
failover provider
scheduler/QoS pondéré
source primary/standby
majority vote
```
Les snapshots/health appartiennent à `pre.010`.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.009.md
```
## Fichiers supprimés
```text
aucun
```
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.009` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, le contrôle exhaustif du diff, des headers et du ZIP sont exécutés avant livraison.
## Résultats statiques finaux
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 845 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
Contrôle ciblé :
```text
14 fichiers existants modifiés
1 fichier ajouté
0 suppression
14/14 headers existants : +1
nouvelle dépendance : aucune
backend Store physique direct : absent
client HTTP/gRPC direct : absent
unbounded_channel : absent
registry/quotas/canonical state publics : absents
```
## Gate opérateur requis avant pre.010
```bash
cargo fmt --all
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 -p ksp-onchain-transport-lib
cargo test -p ksp-worker-raw-transaction-ingest-lib
```
Aucun `cargo tree` n'est requis pour ce gate : `pre.009` ne modifie ni dépendance ni feature. Les graphes seront réaudités si le graphe change ou à la fermeture technique de `0.3.13`.

48
deltas/0.3.13/pre.010.md Normal file
View File

@@ -0,0 +1,48 @@
<!-- file: deltas/0.3.13/pre.010.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.010 — snapshots/health multi-source
## Objet
Fermer la projection publique source-neutral prévue pour le runtime multi-source sans exposer les identités logiques, providers, endpoints, filtres ou matériaux Transport.
## Runtime
`RawTransactionIngestSnapshot` expose désormais quatre gauges latest-value : `source_total`, `source_active`, `source_reconnecting` et `source_failed`.
`source_total` est initialisé dès le démarrage depuis la collection `RawTransactionIngestRuntimeResources` validée. L'inventaire privé borné agrège ensuite les états locaux et publie uniquement les comptes numériques, l'état source agrégé et les compteurs de continuité déjà existants.
La health commune devient conservative pendant `Running` : une source failed donne `Unhealthy`; un reconnect ou une composition dont toutes les sources attendues ne sont pas encore actives donne `Degraded`; toutes les sources attendues actives permettent `Healthy`. `Faulted` reste `Unhealthy` et `Stopping`/`Stopped` conservent la dernière health qualifiée.
L'activity reste liée au travail concret, aux pending, à la persistence, au reconnect ou au closing. Une source simplement ouverte n'est pas artificiellement classée `Active`.
## Preuves ajoutées
Les tests couvrent les comptes d'inventaire, la health `Degraded -> Healthy -> Unhealthy`, les quatre getters publics, la redaction du snapshot, l'absence de backend/network client direct et la présence des canaris dans `release_completeness`.
## Frontière
Aucun changement de convergence, fairness, quota, Store, Transport, dépendance ou feature. Aucun failover, provider health public, snapshot par source ou coverage inference. Les races et shutdown concurrents restent `pre.011`.
## Gate reçu avant cette tranche
Le gate opérateur `0.3.13-pre.009-fix.002` est vert pour toutes les commandes exécutées : fmt, audits, workspace check, Clippy strict, Transport `389 + 52 + 44 + 4` avec `5` smokes live opt-in ignorés, puis Worker `99 + 4 + 16 + 23 + 17 + 4`, sans échec. Aucun `cargo tree` n'était requis ni exécuté.
## Validation locale d'assemblage
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun résultat Cargo post-modification n'est déclaré PASS localement. Les audits statiques et contrôles de packaging effectivement exécutés sont les seules preuves locales consignées pour cette livraison.
## Gate opérateur avant pre.011
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
Aucun `cargo tree` n'est requis ; aucune dépendance ni feature n'a changé. Le Transport n'est pas modifié par cette tranche snapshot/health.

62
deltas/0.3.13/pre.011.md Normal file
View File

@@ -0,0 +1,62 @@
<!-- file: deltas/0.3.13/pre.011.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.011 — races/shutdown hardening
## Objet
Fermer les races stop/fault et l'ownership des tâches source/hydration/persistence avant la tranche de completeness/security cross-layer, sans modifier Transport, le graphe de dépendances ou les politiques de convergence/fairness déjà acquises.
## Shutdown et ownership
Le supervisor conserve l'ordre terminal explicite : signal de stop source, passage Worker à `Stopping`, drain borné, abort+join si la deadline expire, puis publication du terminal. L'abort du wrapper multi-source détruit son `JoinSet` privé et annule donc aussi les tâches source imbriquées avant retour.
Une faute source déjà observée n'est pas masquée par un stop concurrent. Le `drain_timeout` reste prioritaire lorsqu'une récupération bornée dépasse réellement sa deadline. Une persistence encore bloquée à cette échéance est abortée et jointe ; elle ne peut pas publier de succès tardif après le snapshot terminal.
## Hydration partagée
`RawTransactionIngestGlobalHydrationRegistry::publish_and_remove` notifie désormais les followers sous le mutex de registry avant de retirer la clé. Une nouvelle génération de leader ne peut donc pas s'intercaler entre le retrait de la clé et la notification des followers existants.
Le leader guard conserve son cleanup fail-closed : si un leader est aborté avant publication normale, les followers reçoivent une faute source sûre et la clé redevient ensuite disponible.
## Inventaire et compteurs
L'agrégation multi-source remplace les additions saturantes par `checked_add` pour `hydration_pending`, `source_reconnect_total`, `source_replay_attempt_total` et `source_continuity_gap_total`. Un overflow devient `worker_raw_transaction_ingest.counter_exhausted`.
Une mise à jour d'inventaire avec index absent, `source_key` incohérente ou projection absente échoue explicitement par `runtime_invalid` au lieu d'être ignorée. Lorsqu'une source remonte `counter_exhausted`, le supervisor préserve ce code terminal et ne le remappe pas en `source_failed`.
## Preuves ajoutées
Les tests couvrent l'identité/index d'inventaire invalides, les overflows d'agrégats, l'abort d'un leader hydration, l'abort du supervisor externe et de ses enfants imbriqués, la race stop/source fault, la conservation de `counter_exhausted`, le drain timeout sans completion tardive, l'ordre terminal et l'atomicité notification-avant-retrait.
Les canaris externes verrouillent l'absence de queue non bornée, backend Store direct, client Transport secondaire ou nouvelle surface publique d'identité source.
## Frontière
Aucun changement de Transport, Store API, dépendance, feature, convergence, quotas/fairness, snapshot public ou politique de failover. La completeness/security exhaustive reste `pre.012`; le gate technique/live complet et les graphes restent `pre.013`.
## Gate reçu avant cette tranche
Le gate opérateur `0.3.13-pre.010` est vert pour toutes les commandes exécutées : fmt, audits, Markdown `340/848`, workspace check, Clippy strict, puis Worker `100` unit + `4` cross-layer + `17` dependency-boundary + `24` hardening + `18` public-api + `4` release-completeness et `0` doc-test.
Le Transport n'était pas modifié par `pre.010` et n'a pas été rejoué. Aucun `cargo tree` n'était requis ni exécuté.
## Validation locale d'assemblage
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun résultat Cargo post-modification n'est déclaré PASS localement. Les audits statiques et contrôles de packaging effectivement exécutés sont les seules preuves locales consignées pour cette livraison.
Les audits statiques finaux d'assemblage sont clean : règles Rust générales, export completeness `0`, règles KSP, Markdown `340/849`, et inventaire normatif `489/489` sans doublon.
## Gate opérateur avant pre.012
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib
```
Aucun `cargo tree` n'est requis ; aucune dépendance ni feature n'a changé. Le Transport n'est pas modifié par cette tranche de hardening shutdown/races.

75
deltas/0.3.13/pre.012.md Normal file
View File

@@ -0,0 +1,75 @@
<!-- file: deltas/0.3.13/pre.012.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.012 — completeness/security cross-layer
## Objet
Fermer la matrice de preuves cross-layer avant le gate technique/live `pre.013`, sans modifier le comportement runtime, l'API publique, Transport, Common RAW, Store, les dépendances ou les features.
## Gate opérateur d'entrée
Le gate `0.3.13-pre.011` communiqué le 10 septembre 2026 est vert :
```text
fmt : PASS
audits Rust : clean / export completeness 0
Markdown : clean, 340 tables / 849 files
cargo check --workspace : PASS
clippy strict : PASS
Worker unit : 106/106 PASS
cross_layer_completeness : 4/4 PASS
dependency_boundary : 18/18 PASS
hardening : 25/25 PASS
public_api : 19/19 PASS
release_completeness : 4/4 PASS
Worker doc-tests : 0/0 PASS
```
## Modifications
La suite `cross_layer_completeness` ajoute quatre preuves obligatoires :
```text
cinq familles live -> pipeline Worker/Common RAW/Store unique
Legacy/V0/V1 -> Transport/Worker/Common RAW/Store
non-régression Yellowstone -> identity/V1/reconnect/gap/hydration
security/redaction -> Transport/Worker/Common RAW/Store API/Store/PostgreSQL
```
Les suites `dependency_boundary`, `hardening`, `public_api` et `release_completeness` verrouillent explicitement la présence de cette matrice.
## Frontières
```text
aucun code production modifié
aucune API publique ajoutée
aucune dépendance/feature modifiée
aucun changement Transport/Common RAW/Store
aucun delta historique modifié
```
## Validation locale d'assemblage
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucune commande Cargo post-modification n'est déclarée PASS localement.
Les audits statiques et les contrôles archive sont consignés uniquement après leur exécution effective.
## Gate opérateur vers pre.013
`pre.013` est le gate technique/live de fermeture ; exécuter :
```bash
cargo fmt --all
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 tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
cargo tree --duplicates
```
Les smokes live accessibles sont qualifiés dans `pre.013`; un smoke inaccessible reste `NON EXÉCUTÉ`.

64
deltas/0.3.13/pre.013.md Normal file
View File

@@ -0,0 +1,64 @@
<!-- file: deltas/0.3.13/pre.013.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.013 — gate technique/live
## Objet
Matérialiser la tranche de gate technique/live prévue par le plan, sans ajouter de scope fonctionnel : enregistrer le gate déterministe workspace complet déjà communiqué et séparer strictement cette preuve des smokes live opt-in encore à exécuter.
## Gate déterministe reçu
Le gate `0.3.13-pre.012` du 10 septembre 2026 est vert pour les commandes exécutées :
```text
fmt : PASS
audits Rust : clean / export completeness 0
Markdown : clean, 340 tables / 850 files
cargo check --workspace : PASS
clippy workspace/all-targets/all-features -D warnings : PASS
cargo test --workspace --all-targets --all-features : 1 850 PASS / 0 échec / 15 ignored
Worker : 106 unit + 8 cross-layer + 19 dependency-boundary + 26 hardening + 20 public-api + 5 release-completeness, 0 échec
cargo tree Worker normal/features : exécutés sans erreur
cargo tree --duplicates : exécuté sans erreur ; inventaire multi-version affiché
```
Les tests `ignored` ne sont pas requalifiés en preuves live.
## Modifications
```text
workspace.package.version -> 0.3.13-pre.13
validation 030 : enregistrement du gate déterministe et qualification live
aucun Rust production/test modifié
aucune dépendance/feature modifiée
aucun delta historique modifié
```
## Smokes keyless requis pour fermer la partie live accessible
```bash
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
cargo test -p ksp-onchain-transport-lib --test websocket_devnet_smoke -- --ignored --nocapture
```
Ces smokes restent séparés du gate déterministe. Les preuves Yellowstone/Helius/blockSubscribe dédié/HTTP polling Worker/multi-source Store end-to-end restent `NON EXÉCUTÉ` tant qu'un gate réel ne fournit pas les credentials, provider capabilities ou ressources nécessaires.
## Frontières
```text
aucun nouveau scope fonctionnel
aucune API publique modifiée
aucun comportement runtime modifié
aucun changement Transport/Store/Common RAW
aucun changement de dépendance ou feature
aucun delta historique modifié
```
## Validation locale d'assemblage
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun PASS Cargo post-`pre.013` n'est revendiqué localement. Les audits statiques et contrôles archive sont déclarés uniquement après exécution effective.
## Gate opérateur
Après application du delta, exécuter les deux smokes keyless ci-dessus. En cas d'échec produit reproductible, rester sur `pre.013` et produire un fix. S'ils passent, les autres smokes non provisionnés restent explicitement `NON EXÉCUTÉ` et la release peut passer à `pre.014` pour la réconciliation documentaire.

78
deltas/0.3.13/pre.014.md Normal file
View File

@@ -0,0 +1,78 @@
<!-- file: deltas/0.3.13/pre.014.md -->
<!-- version: 1 -->
# Delta `0.3.13-pre.014`
## Nature
Réconciliation documentaire de la verticale live multi-source `RawTransaction` après fermeture technique/live de `pre.013`.
Aucun code Rust, test, dépendance, feature ou contrat public n'est modifié.
## Gate d'entrée confirmé
Le gate déterministe `pre.012` reste vert et les deux smokes live keyless exécutés sur `0.3.13-pre.013` passent explicitement :
```text
Solana HTTP Devnet : 1/1 PASS
Solana WebSocket Devnet : 1/1 PASS
```
Le contrôle post-smoke communiqué passe également fmt, audits Rust/Markdown, `cargo check --workspace` et Clippy strict.
## Réconciliation durable
Les documents suivants sont mis en cohérence avec l'état réellement livré :
```text
docs/architecture/004-COMPONENT_INVENTORY.md
docs/architecture/005-DEPENDENCY_GRAPH.md
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
Les corrections principales sont :
```text
Worker RAW documenté comme composition de 1..32 sources logiques sur un même réseau
cinq familles live : Yellowstone / Standard Logs / Standard Block / Helius Transaction / HTTP Block Polling
registry globale d'hydration pour Yellowstone / Standard Logs / Helius
Standard Block et HTTP Block Polling RAW-direct Legacy/V0/V1
blockSubscribe actuel avec maxSupportedTransactionVersion = 1
convergence canonique + observations multiples + disagreement explicite
backpressure/fairness et bornes globales source-neutral
health multi-source conservative et shutdown/races bornés
reports EARLY / coverage-equivalence / repair historique maintenus hors 0.3.13
```
`README.md`, `crates/ksp-worker-raw-transaction-ingest-lib/README.md` et `USAGE.md` ont été audités et sont déjà cohérents ; ils restent byte-identiques. `USAGE.md` reste version-neutral.
## Frontières
Cette tranche ne modifie pas :
```text
CHANGELOG.md
ROADMAP.md
prompt 0.3.14
code/test Rust
Cargo dependencies/features
deltas historiques
```
La préparation publication reste réservée à `pre.015`.
## Gate opérateur avant `pre.015`
```bash
cargo fmt --all
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
```
Aucun `cargo tree`, smoke live ou rerun exhaustif des tests n'est requis pour cette tranche strictement documentaire hors bump mécanique de prerelease.

224
deltas/0.3.13/pre.015.md Normal file
View File

@@ -0,0 +1,224 @@
<!-- file: deltas/0.3.13/pre.015.md -->
<!-- version: 2 -->
# Delta `0.3.13-pre.015` — préparation de publication
## 1. Base requise
```text
0.3.13-pre.014
workspace.package.version = 0.3.13-pre.14
```
Le gate opérateur communiqué le **10 septembre 2026 à 23:15** sur `pre.014` est vert pour toutes les commandes exécutées :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean, 340 tables / 852 files
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
```
Conformément à `pre.014`, aucun `cargo tree`, smoke live ou rerun exhaustif des tests n'était requis pour cette tranche strictement documentaire hors bump mécanique.
## 2. Objectif
`pre.015` est la dernière prerelease de préparation de publication de `0.3.13`.
Conformément à `VER-LIFECYCLE-003`, `VER-LIFECYCLE-012` et `PROMPT_STRUCTURE.md`, elle modifie fonctionnellement uniquement :
```text
prompt de démarrage 0.3.14
CHANGELOG.md
ROADMAP.md
```
Les seuls fichiers mécaniques supplémentaires sont :
```text
Cargo.toml racine
delta pre.015
```
Aucun README, USAGE, plan, validation, architecture, code, test, Config, schema, dépendance, feature ou manifest de crate n'est rouvert.
## 3. Version workspace
La prerelease non-fix synchronise Cargo :
```text
0.3.13-pre.14
->
0.3.13-pre.15
```
Le header de `Cargo.toml` passe de `563` à `564`.
## 4. Prompt suivant
Le nouveau prompt :
```text
prompts/033-V0_3_14_START_PROMPT.md
```
ouvre `0.3.14` exclusivement depuis le futur stable :
```text
v0.3.13
```
Sa mission est bornée au gap repair/hardening multi-source du run actif :
```text
replay natif réellement adressable
preuve de coverage par source redondante
scan HTTP de slots/blocs du run
hydration getTransaction de réparation
repaired vs unresolved gap explicites
health Healthy/Degraded/Unhealthy/Faulted fondée sur coverage prouvée
backpressure/fairness et shutdown pendant repair
smokes provider réellement accessibles
EARLY uniquement si pre.001 apporte une preuve actuelle suffisante
```
Le prompt interdit explicitement de transformer le Worker en campagne historique, de créer un edge Worker -> Job Backfill ou de coder le repair avant le `pre.001` d'audit/sizing. Il réserve :
```text
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
```
## 5. CHANGELOG
`CHANGELOG.md` reçoit l'entrée stable `0.3.13` synthétisant :
```text
composition 1..32 sources d'un même réseau
cinq familles live productives
hydration globale Yellowstone / Standard Logs / Helius
RAW-direct Standard Block / HTTP Block Polling Legacy/V0/V1
coalescence cross-source + observations multiples
content conflict explicite sans first-provider-wins
fairness/backpressure/health source-neutral
shutdown/races hardening
gate déterministe 1 850 PASS / 0 échec / 15 ignored
smokes keyless HTTP Devnet + WebSocket Devnet PASS
preuves provider/resource-gated restantes NON EXÉCUTÉES
handoff 0.3.14 et prompt 033
```
## 6. ROADMAP
L'entrée `0.3.13` passe de `[ ]` à `[X]` et décrit l'état réellement livré. `0.3.14` reste ouverte pour le gap repair/hardening multi-source.
Le TODO Helius `0.3.13` passe également à `[X]` en documentant le résultat réel : la voie Helius Worker réutilise les profils Config et `KSP_SECRET_HELIUS_API_KEY` déjà existants ainsi que `transactionSubscribe` Transport-owned ; aucun nouveau profil, SDK, tier codé ou second client n'a été nécessaire.
## 7. Fichiers ajoutés
```text
prompts/033-V0_3_14_START_PROMPT.md
deltas/0.3.13/pre.015.md
```
## 8. Fichiers modifiés
```text
Cargo.toml
CHANGELOG.md
ROADMAP.md
```
## 9. Fichiers supprimés
```text
aucun
```
## 10. Surfaces explicitement inchangées
```text
README.md
RULES.md
docs/**
crates/**
config/**
tests/**
prompts/001-* à prompts/032-*
deltas/0.3.13/pre.001.md à pre.014.md et leurs fixes
```
Aucun delta historique n'est modifié.
## 11. Vérification complète de l'archive de base avant modification
L'archive opérateur `v0.3.13-pre.014` a été contrôlée avant assemblage :
```text
unzip -t : PASS
2 009 entrées ZIP
0 chemin absolu / traversal / entrée hors racine attendue
0 target/node_modules/dist/.git/.idea/__pycache__ embarqué
0 lockfile embarqué
rust-toolchain.toml absent
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean, 340 tables / 852 files
5/5 tests unitaires du validateur Markdown : PASS
489 définitions de règles normatives
489 identifiants normatifs uniques
0 doublon d'identifiant normatif
headers/newline des surfaces critiques : PASS
scan high-confidence de secrets privés : aucun résultat
```
Le toolchain Cargo/Rustc n'est pas disponible dans l'environnement d'assemblage. Aucun PASS Cargo supplémentaire n'est revendiqué localement ; les PASS Cargo ci-dessus proviennent exclusivement du gate opérateur communiqué.
## 12. Validations exécutées sur l'état `pre.015`
Sur l'état exact de `pre.015` avant packaging :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean, 340 tables / 854 files
489 définitions de règles / 489 identifiants uniques / 0 doublon
prompt 033 : 16 sections normatives présentes
diff exact pre.014 -> pre.015 : 3 fichiers existants modifiés + 2 ajoutés + 0 supprimé
headers des 3 fichiers existants : +1 exactement
Cargo.toml : hors header, seule workspace.package.version change
aucun README/USAGE/plan/validation/architecture/code/test/config/schema/dependency/feature modifié
```
Les contrôles `unzip -t`, archive safety et inventaire minimal du ZIP sont exécutés après packaging avant livraison.
Les commandes Cargo post-`pre.015` restent à exécuter par l'opérateur avant `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
cargo check --workspace
```
Clippy strict peut être rejoué ; aucun code/test/dépendance/feature n'étant modifié, il ne remplace pas le gate minimal ci-dessus.
## 13. Décisions prises
- `0.3.13` est fonctionnellement/documentairement fermé et prêt pour le gate de publication de `pre.015` ;
- `0.3.14` ne commence qu'après `0.3.13-rel.001` validée et le tag stable `v0.3.13` ;
- le prompt `0.3.14` conserve la séparation Worker/Backfill et impose un `pre.001` avant toute logique de repair ;
- les preuves live non exécutées de `0.3.13` restent explicitement non revendiquées ;
- aucun correctif d'un couloir technique/documentaire antérieur n'est absorbé dans cette préparation de publication.
## 14. Questions ouvertes
```text
aucune question bloquante pour la préparation de publication 0.3.13
```

190
deltas/0.3.13/rel.001.md Normal file
View File

@@ -0,0 +1,190 @@
<!-- file: deltas/0.3.13/rel.001.md -->
<!-- version: 2 -->
# Delta `0.3.13-rel.001` — publication stable
## Base requise
Base directe attendue :
```text
0.3.13-pre.015
workspace.package.version = 0.3.13-pre.15
```
Les couloirs de fermeture précédents sont fermés :
- `pre.013` : gate technique déterministe complet, graphes Cargo et qualification explicite des smokes live ;
- smokes keyless post-`pre.013` : Solana HTTP Devnet `1/1 PASS` et WebSocket Devnet `1/1 PASS` ;
- `pre.014` : réconciliation documentaire finale de l'architecture, du plan et de la validation ;
- `pre.015` : préparation minimale de publication avec `CHANGELOG.md`, `ROADMAP.md` et `prompts/033-V0_3_14_START_PROMPT.md`.
Le gate opérateur reçu avant `pre.015`, exécuté le **10 septembre 2026 à 23:15** sur `0.3.13-pre.014`, est propre pour rustfmt, audits Rust/Markdown, `cargo check --workspace` et Clippy strict.
L'environnement d'assemblage de `pre.015` ne possède pas Cargo/Rustc. Le gate Cargo de l'état exact `0.3.13-pre.015` reste donc un **prérequis d'application** de ce delta et ne doit pas être déclaré PASS sans exécution opérateur réelle :
```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 révèle un défaut, ne pas appliquer `rel.001` : ouvrir la prerelease/fix appropriée selon la responsabilité concernée.
## Objet
Publier mécaniquement la version stable :
```text
0.3.13
```
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
564 -> 565
```
La version workspace devient :
```text
0.3.13-pre.15 -> 0.3.13
```
Aucune autre ligne du `Cargo.toml` racine n'est modifiée.
## Fichiers ajoutés
```text
deltas/0.3.13/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/033-V0_3_14_START_PROMPT.md
crates/**
docs/**
config/**
```
La surface préparée en `pre.015` reste byte-identique hors mécanique Cargo et ajout du présent delta.
## Surface stable publiée
`0.3.13` stabilise la convergence live multi-source de `ksp-worker-raw-transaction-ingest-lib` :
- composition caller-owned de `1` à `32` sources logiques d'un même réseau, avec identités dupliquées rejetées avant spawn ;
- cinq familles live productives : Yellowstone, Standard Logs, Standard Block, Helius Transaction et HTTP Block Polling ;
- Standard Logs et Helius Transaction convergent avec Yellowstone vers l'hydration globale `getTransaction observed` ;
- Standard Block et HTTP Block Polling utilisent le RAW-direct uniquement pour Legacy/V0/V1 qualifiés, avec `maxSupportedTransactionVersion = 1` ;
- coalescence cross-source de `(network, signature, commitment)` avant hydration et sérialisation bornée de l'acquisition canonique `(network, signature)` ;
- observations/provenances distinctes conservées pour un contenu canonique identique ; divergence convertie en `content_conflict` terminal, sans majorité, overwrite ou `first-provider-wins` ;
- backpressure, quotas et fairness source-neutral bornés entre sources reference-bearing, hydration et persistence ;
- snapshots/health agrégés sans exposition du matériau provider ; reconnexion transitoire `Degraded`, source `Failed` `Unhealthy`, retour à `Healthy` lorsque toutes les sources attendues redeviennent actives ;
- une faute source reste terminale en `0.3.13` faute de preuve d'équivalence de coverage ; le repair/failover non terminal appartient à `0.3.14` ;
- shutdown stop/fault/drain/abort+join durci contre leaders d'hydration abandonnés, Store lent, counters et publications tardives ;
- aucune dépendance Worker vers Config, Job Backfill ou backend Store physique, aucun client réseau/SDK provider parallèle et aucun second actor Transport.
Le gate technique déterministe de la release a exécuté `cargo test --workspace --all-targets --all-features` avec :
```text
1 850 PASS
0 échec
15 ignored opt-in/operator-only
```
Les smokes keyless Solana HTTP Devnet et WebSocket Devnet passent `1/1` chacun. Les preuves Yellowstone provider-gated, Helius `transactionSubscribe` live, `blockSubscribe` provider dédié, HTTP Block Polling Worker end-to-end et Worker multi-source -> Store end-to-end restent explicitement `NON EXÉCUTÉ` faute de gate provisionné ; aucune fixture ou compilation d'un test ignored ne les requalifie en PASS.
Le Job Backfill historique paramétré et le Worker Ingest continu restent deux producteurs indépendants du même Store. `0.3.13` n'introduit ni campagne historique automatique, ni checkpoint durable Worker, ni preuve de blockchain completeness.
## Suite préparée
La session suivante part exclusivement de la base stable :
```text
v0.3.13
```
et exécute :
```text
prompts/033-V0_3_14_START_PROMPT.md
```
pour ouvrir :
```text
0.3.14 — gap repair / hardening multi-source du Worker RawTransaction
```
`0.3.14-pre.001` doit commencer par lecture, audit interne/externe, brainstorming, sizing et planification avant toute implémentation de repair. Il doit distinguer reconnect, replay attempt, coverage prouvée, repair et unresolved gap ; le Worker ne doit toujours pas devenir un moteur de campagne historique ni dépendre de `ksp-job-backfill-lib`.
## Validations d'assemblage
Sur l'état candidat `rel.001`, avant packaging :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean, 340 tables / 855 files
5/5 tests unitaires du validateur Markdown : PASS
489 définitions de règles / 489 identifiants uniques / 0 doublon
diff exact pre.015 -> rel.001 : Cargo.toml modifié + rel.001.md ajouté, 0 autre changement
Cargo.toml : exactement 2 lignes différentes, header 564 -> 565 et version 0.3.13-pre.15 -> 0.3.13
```
Les contrôles `unzip -t`, archive safety et inventaire minimal du ZIP sont exécutés après packaging avant livraison.
Aucun PASS Cargo post-bump stable n'est revendiqué localement faute de toolchain.
## Gate demandé après application
La tranche est purement mécanique. Après application sur un `pre.015` dont le gate préalable est vert :
```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.13
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
cargo test -p ksp-onchain-transport-lib
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
```
Après gate propre :
```text
commit : v0.3.13-rel.001
tag : v0.3.13
```
Aucun tag de prerelease ou `rel.001` n'est requis.

218
deltas/0.3.14/pre.001.md Normal file
View File

@@ -0,0 +1,218 @@
<!-- file: deltas/0.3.14/pre.001.md -->
<!-- version: 5 -->
# Delta `0.3.14-pre.001` — audit / brainstorming / sizing gap repair
## Base requise
```text
v0.3.13
workspace.package.version = 0.3.13
deltas/0.3.13/rel.001.md présent
```
Archive opérateur auditée :
```text
khadhroony-solana-project-v0.3.13.zip
SHA-256 253fa7ef5e0a6b2721da66fc3ea8dc7695d991d3a00bcc7d34396d9a32b65245
```
L'archive ne contient pas `.git`; l'identité interne stable est contrôlée mais l'objet du tag Git ne peut pas être comparé localement.
## Objet
Exécuter le gate `pre.001` imposé par `prompts/033-V0_3_14_START_PROMPT.md` avant tout codage de repair :
```text
lecture complète des règles et du handoff 0.3.13
audit archive/base stable
audit Worker/Transport/frontier/replay/continuity
audit externe courant Solana/Yellowstone/providers
matrice des cinq familles live
modèle gap/range/coverage run-local
sémantique repaired/unresolved
health candidate
threat model
smokes accessibles
Cargo/features cible
sizing et décision maintien/rescission
plan 035 + validation 031
```
Aucun moteur de repair, failover/degraded runtime, modification Transport/Config ou adapter EARLY n'est implémenté.
## Décisions
```text
reconnect != replay_attempt != replay_covered != repaired
replay accepté ne prouve pas la coverage
transaction-only source ne prouve pas l'absence d'événements manquants
repair final qualifié au niveau slot/range
skipped seulement après discovery slots réussie
getBlock null sur slot produit reste unresolved
getTransaction null n'est pas une preuve d'inexistence
Common RAW/admission/ksp-store-lib restent le chemin unique
retry/reconnect restent Transport-owned
required/redundant est une relation de coverage dynamique, pas un flag provider
processing frontier != continuity frontier
pool HTTP d'hydration != capability de scan : getTransaction seul est garanti pour Yellowstone/Logs/Helius
les façades WS doivent exposer une observation latest-value de leur snapshot avant le repair ; aucun nouveau socket/actor
range gap inclusif pour ne pas perdre la fin d'un slot déjà partiellement observé ; aucun slot dérivé d'un timestamp
TargetCoverage = réunion conservative des scopes configurés ; source perdue non respawnée par Worker
FullLedgerTransactions et ExactSourceScope sont distingués ; aucune équivalence cross-family implicite
getBlocksWithLimit ne ferme une plage que si la fenêtre cible est explicitement prouvée complète
scan full-block interdit s'il élargirait silencieusement un run uniquement filtré
EARLY hors scope 0.3.14
aucune nouvelle dépendance
Config inchangé en pre.001 ; modification future seulement sur besoin démontré
0.3.14 maintenue sans rescission sous le scope des cinq familles existantes
```
Forecast recalibré : `pre.002 -> pre.016`, avec une tranche WS continuity dédiée, replay Yellowstone et coverage redondante séparés, puis HTTP/hydration/reconciliation/health/fairness/observability/races/gates/docs/publication. Cette extension remplace le forecast initial `pre.002 -> pre.014` afin de respecter les slices 1520 minutes.
Bornes cibles à matérialiser dans les tranches techniques :
```text
MAX_OPEN_REPAIR_GAPS = 64
MAX_REPAIR_RANGE_SLOTS = 4096
MAX_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
MAX_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_REPAIR_ACTIVE_GAPS = 1
```
## Audit externe — points structurants
Le proto Yellowstone utilisé expose `from_slot` et `SubscribeReplayInfo/first_available`. `yellowstone-grpc-proto 12.7.0` est courant au moment de l'audit.
Le changelog upstream du 22 juillet 2026 documente un bug corrigé où un replay `blocks` pouvait accepter `from_slot`, ne rejouer aucun block, puis reprendre au head live avec un gap. La preuve KSP ne peut donc pas être « request accepted ».
PublicNode expose actuellement Solana Mainnet/Testnet RPC/WS/Yellowstone et archive data, mais sa profondeur de replay/first_available n'est pas suffisamment documentée pour être revendiquée.
OrbitFlare expose Yellowstone et Devnet gRPC sur ses tiers courants ; un exemple récent utilise `from_slot`, mais la profondeur/first_available de l'endpoint KSP reste à prouver par smoke.
Helius documente LaserStream gRPC avec replay jusqu'à 24 h, mais cette surface est distincte du `transactionSubscribe` WSS stable et n'est pas ajoutée à `0.3.14`.
QuickNode documente un `fromSlot` provider-specific jusqu'à 3000 slots ; cette comparaison confirme qu'une profondeur de replay ne peut pas être généralisée entre providers.
## Fichiers ajoutés
```text
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
deltas/0.3.14/pre.001.md
```
## Fichiers modifiés
```text
Cargo.toml
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
```text
header : 565 -> 566
workspace.package.version : 0.3.13 -> 0.3.14-pre.1
```
Aucune dépendance, feature ou autre ligne fonctionnelle du manifest racine n'est changée.
## Surfaces explicitement inchangées
```text
crates/**
config/**
.env.example
README.md
RULES.md
ROADMAP.md
CHANGELOG.md
docs/architecture/**
prompts/**
deltas/0.3.13/**
```
## Validations base exécutées
Localement avant modification :
```text
unzip -t : PASS
archive safety : PASS
workspace/crate inventory : 21/21
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 855 files)
489 définitions de règles / 489 IDs uniques / 0 doublon
```
Le journal opérateur fourni pour la stable contient :
```text
cargo fmt/check : terminé sans erreur visible
cargo check --workspace : terminé sans erreur visible
cargo clippy --workspace --all-targets --all-features -- -D warnings : terminé sans erreur visible
cargo test --workspace --all-targets --all-features : 1 850 PASS / 0 failed / 15 ignored
cargo tree Worker direct/features : produit
cargo tree --duplicates : produit
```
Cette preuve concerne `0.3.13`, pas le bump `0.3.14-pre.1`.
## Validations post-modification
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 858 files)
rule definitions : 489 / 489 uniques / 0 doublon
comparaison stable -> worktree : 3 ajouts / 1 modification / 0 suppression
Cargo fmt/check : NON EXÉCUTÉ LOCAL — cargo absent du sandbox
```
Le `scripts/__pycache__` recréé par les audits Python est supprimé avant packaging et ne fait pas partie de la livraison.
Archive de livraison attendue :
```text
ksp-general-0.3.14-pre.001.zip
```
## Archive déchange
Conformément à `VER-ARCHIVE-001`, `VER-ARCHIVE-004` et `VER-ARCHIVE-005`, la livraison est une archive `general` minimale :
```text
ksp-general-0.3.14-pre.001.zip
```
Contenu attendu, exactement quatre fichiers :
```text
Cargo.toml
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
deltas/0.3.14/pre.001.md
```
Aucun fichier inchangé de la stable, cache, lockfile, secret, sortie de compilation ou artefact daudit nest inclus.
## Gate opérateur après application
```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.14
cargo check --workspace
```
Si ce gate est propre, `pre.002` peut matérialiser uniquement les contrats gap/range/coverage prévus par le plan `035`.

View File

@@ -0,0 +1,203 @@
<!-- file: deltas/0.3.14/pre.002-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.002-fix.001` — correction frontières replay et dead-code continuity
## Base requise
```text
0.3.14-pre.002
workspace.package.version = 0.3.14-pre.2
deltas/0.3.14/pre.002.md présent
```
## Objectif
Corriger strictement la tranche `pre.002` après le gate opérateur, sans étendre son périmètre fonctionnel :
```text
supprimer la mutation Worker du cursor Yellowstone from_slot
préserver la propriété Transport-owned replay
supprimer le dead_code de RawTransactionIngestGapRange::new sans suppression de validation ni lint bypass
conserver pre.002 sans repair I/O
```
## Défauts observés
Le gate opérateur sur `0.3.14-pre.2` a produit :
```text
cargo check --workspace : PASS avec 1 warning dead_code
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : FAIL
```
Clippy :
```text
associated function `new` is never used
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs:102
-D dead-code implied by -D warnings
```
Test d'intégration :
```text
v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on_proven_retention_gap : FAIL
Worker took ownership of replay/repair responsibility: set_from_slot(
```
Les 114 unit tests du crate passaient avant l'échec du test de frontière.
## Cause
`pre.002` construisait le fingerprint de coverage Yellowstone en clonant la requête puis en supprimant :
```text
commitment
from_slot
ping
```
La suppression de `from_slot` appelait `YellowstoneSubscribeRequest::set_from_slot(None)` depuis `runtime_resources.rs`. Même sans I/O, cette mutation viole la frontière durable établie en `0.3.12-pre.008` : le Worker observe le replay Yellowstone mais ne possède ni ne modifie son cursor ; la responsabilité `from_slot` reste dans Transport.
Le constructeur privé `RawTransactionIngestGapRange::new` était par ailleurs appelé uniquement depuis les unit tests de `pre.002`, donc considéré mort dans la compilation normale de la bibliothèque.
## Correction
### Yellowstone coverage fingerprint
Le Worker continue de neutraliser uniquement les éléments communs qu'il peut traiter sans absorber la responsabilité replay :
```text
commitment -> retiré du fingerprint opaque, car porté séparément par TargetCoverage
ping -> retiré du fingerprint opaque, car non métier
from_slot -> conservé dans l'identité opaque Transport
```
Le Worker n'appelle plus `set_from_slot`.
Conserver `from_slot` dans l'identité rend la comparaison de deux scopes Yellowstone volontairement plus conservative : deux requêtes identiques métier mais portant des cursors replay différents peuvent rester distinctes. Cette différence ne peut pas broaden la coverage ni produire une fausse preuve de complétude. Une éventuelle identité Transport spécifiquement dédiée au scope métier relève d'une tranche Transport ultérieure, pas de ce fix.
Cette correction remplace donc la décision trop agressive de `pre.002` qui annonçait l'exclusion du cursor replay du fingerprint Worker.
### Gap range validation
`RawTransactionIngestGapLedger::validate_invariants()` réutilise maintenant `RawTransactionIngestGapRange::new(start_slot, end_slot)` pour revalider chaque plage retenue.
Cela :
```text
rend le constructeur vivant dans le code de production
conserve le rejet reversed
conserve le contrôle checked du span inclusif
ajoute au recheck du ledger la borne MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS
n'introduit aucun allow/expect dead_code
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.002-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le fix modifie du code Rust ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 567 -> 568
workspace.package.version : 0.3.14-pre.2 -> 0.3.14-pre.2.fix.1
```
Versions de fichiers :
```text
continuity.rs : 1 -> 2
runtime_resources.rs : 27 -> 28
```
## Frontières préservées
```text
aucun repair I/O
aucune nouvelle tâche
aucune nouvelle socket
aucun retry Worker
aucune nouvelle dépendance
aucune nouvelle feature
aucun accès Config depuis Worker
aucun accès Job Backfill depuis Worker
aucun backend Store physique depuis Worker
aucune mutation Worker de Yellowstone from_slot
```
## Validations exécutées
Dans le sandbox de préparation :
```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/0.3.14
scan statique de la frontière set_from_slot dans runtime_resources.rs
scan du constructeur RawTransactionIngestGapRange::new dans le chemin production
comparaison exacte pre.002 -> pre.002-fix.001
contrôle contenu archive delta
unzip -t archive delta
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté opérateur :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
Le test de frontière existant qui a détecté la régression n'est ni modifié ni assoupli.
## Décisions prises
```text
fix strict de pre.002, sans nouvelle responsabilité fonctionnelle
Transport reste propriétaire de from_slot/replay
le Worker privilégie une identité Yellowstone conservative plutôt qu'une normalisation interdite
le ledger revalide ses ranges via le constructeur canonique
aucun lint bypass n'est accepté pour dead_code
```
## Questions ouvertes
```text
aucune pour ce fix
```
## Gate opérateur après application
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

View File

@@ -0,0 +1,220 @@
<!-- file: deltas/0.3.14/pre.002-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.002-fix.002` — correction façade crate-root continuity
## Base requise
```text
0.3.14-pre.002-fix.001
workspace.package.version = 0.3.14-pre.2.fix.1
deltas/0.3.14/pre.002-fix.001.md présent
```
## Objectif
Corriger strictement la tranche `pre.002` après le second gate opérateur, sans étendre son périmètre fonctionnel :
```text
préserver le canari historique pre.007 interdisant le vocabulaire repair dans la crate-root
conserver les contrats continuity/gap de pre.002 entièrement privés
ne réexporter au crate-root que les éléments pub(crate) réellement partagés entre modules
conserver pre.002 sans repair I/O
```
## Défaut observé
Le gate opérateur sur `0.3.14-pre.2.fix.1` a produit :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : FAIL
```
Les `114` unit tests passaient, ainsi que les suites `cross_layer_completeness`, `dependency_boundary` et `hardening` exécutées avant l'échec.
Échec :
```text
v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only : FAIL
pre.007 public root overclaims continuity/replay: repair
```
Le test historique vérifie directement `src/lib.rs` et interdit les tokens suivants :
```text
ReplayInfo
from_slot
repair
backfill
```
## Cause
`pre.002` avait correctement gardé ses nouveaux contrats en `pub(crate)`, mais avait réexporté au crate-root plusieurs bornes internes nommées `*_REPAIR_*` ainsi que `RawTransactionIngestRepairCapabilityDescriptor`.
Ces éléments n'étaient pas publics hors crate, mais leur présence textuelle dans `src/lib.rs` violait malgré tout le contrat historique `pre.007` : la façade crate-root ne doit pas revendiquer une responsabilité de replay/repair.
Cinq bornes étaient en outre utilisées uniquement dans `continuity.rs` et son module de unit tests ; elles n'avaient donc pas besoin d'être `pub(crate)` ni réexportées.
## Correction
### Bornes de continuity
Les cinq bornes restent inchangées en valeur et en sémantique, mais deviennent strictement privées au module `continuity` :
```text
MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS = 64
MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS = 1
MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS = 4096
```
Leurs réexports `pub(crate)` sont supprimés de `src/lib.rs`.
Les unit tests rattachés au module parent y accèdent maintenant via `super::...`, conformément à la règle de visibilité des éléments strictement privés.
### Capability descriptor partagé
Le seul descriptor réellement partagé entre `continuity.rs` et `runtime_resources.rs` reste `pub(crate)` et réexporté au crate-root, mais sous un nom source-neutre :
```text
RawTransactionIngestRepairCapabilityDescriptor
-> RawTransactionIngestContinuityCapabilityDescriptor
```
Sa structure, ses validations et ses données ne changent pas.
Le helper privé `repair_capability_descriptor` de `runtime_resources.rs` n'est pas une façade crate-root et conserve son rôle actuel ; aucun repair I/O n'est ajouté.
### Canari de non-régression
Le test historique `tests/public_api.rs` n'est ni modifié ni assoupli.
Le canari `v0_3_14_pre_002_continuity_contracts_are_private_bounded_and_io_free` est renforcé afin d'exiger lui aussi :
```text
src/lib.rs ne contient pas "repair"
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.002-fix.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le fix modifie du code Rust ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 568 -> 569
workspace.package.version : 0.3.14-pre.2.fix.1 -> 0.3.14-pre.2.fix.2
```
Versions de fichiers :
```text
src/lib.rs : 27 -> 28
src/continuity.rs : 2 -> 3
src/runtime_resources.rs : 28 -> 29
unit_tests/continuity.rs : 1 -> 2
tests/hardening.rs : 25 -> 26
```
## Frontières préservées
```text
aucun repair I/O
aucune nouvelle tâche
aucune nouvelle socket
aucun retry Worker
aucune nouvelle dépendance
aucune nouvelle feature
aucun accès Config depuis Worker
aucun accès Job Backfill depuis Worker
aucun backend Store physique depuis Worker
aucune mutation Worker de Yellowstone from_slot
aucun nouveau symbole public
crate-root toujours exempte de ReplayInfo/from_slot/repair/backfill
```
## Validations exécutées
Dans le sandbox de préparation :
```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
scan exact des tokens ReplayInfo/from_slot/repair/backfill dans src/lib.rs
scan des références vers l'ancien RawTransactionIngestRepairCapabilityDescriptor
scan des références des bornes privées de continuity
comparaison exacte pre.002-fix.001 -> pre.002-fix.002
contrôle contenu archive delta
unzip -t archive delta
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté opérateur :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
Le test historique `v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only` n'est pas modifié.
## Décisions prises
```text
fix strict de pre.002, sans nouvelle responsabilité fonctionnelle
les bornes non partagées restent privées à continuity.rs
seuls les éléments réellement partagés sont pub(crate) et réexportés au crate-root
le descriptor partagé porte une identité continuity source-neutre
aucun contournement du canari historique n'est accepté
```
## Questions ouvertes
```text
aucune pour ce fix
```
## Gate opérateur après application
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

257
deltas/0.3.14/pre.002.md Normal file
View File

@@ -0,0 +1,257 @@
<!-- file: deltas/0.3.14/pre.002.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.002` — contrats run-local gap / range / coverage
## Base requise
```text
0.3.14-pre.001
workspace.package.version = 0.3.14-pre.1
deltas/0.3.14/pre.001.md présent
```
Gate opérateur communiqué sur `0.3.14-pre.1` :
```text
cargo fmt --all -- --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (306 tables / 181 files sur le périmètre demandé)
cargo check --workspace : PASS
```
## Objectif
Matérialiser uniquement la tranche `pre.002` du plan `035` :
```text
contrats run-local de gap et plage inclusive
TargetCoverage conservative par commitment et scope
FullLedgerTransactions / ExactSourceScope / KnownReferences
inventaire privé des capabilities de repair déjà composées
ledger run-local borné et invariants de coalescence
bornes de future discovery / fetch / active repair
raccord pur aux RuntimeResources avant spawn des sources
```
Cette tranche ne réalise aucun repair, replay, scan HTTP, hydration de gap ou I/O Store supplémentaire.
## Fichiers ajoutés
```text
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
deltas/0.3.14/pre.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
```
## Fichiers supprimés
```text
aucun
```
## Contrats introduits
Bornes run-local :
```text
MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS = 64
MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS = 4096
MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS = 1
```
Le scope de coverage distingue explicitement :
```text
FullLedgerTransactions
ExactSourceScope(family, semantic_fingerprint)
KnownReferences(fingerprint)
```
`KnownReferences` est admis comme notion de capability mais rejeté comme `TargetCoverage` configurée : connaître des signatures à hydrater ne prouve jamais qu'aucune transaction n'a été perdue.
Les plages de gap sont inclusives, bornées, validées avec arithmétique checked et coalescibles seulement lorsqu'elles se chevauchent ou sont adjacentes sans dépasser la borne maximale.
Le ledger impose notamment :
```text
réseau unique du run
GapId non nul, unique et monotone
maximum 64 gaps ouverts
maximum 1 gap en Repairing
pas de ranges ouverts coalescibles du même source/scope/commitment/reason
aucun wrap implicite des compteurs ou bornes
```
## Capabilities source
L'inventaire est dérivé des `RuntimeResources` déjà validées, sans I/O :
```text
HTTP Block Polling All -> FullLedgerTransactions
Standard Block All -> FullLedgerTransactions
Standard Block filtered -> ExactSourceScope
Standard Logs -> ExactSourceScope
Helius transactionSubscribe -> ExactSourceScope
Yellowstone -> ExactSourceScope provider-neutral
```
La capability de scan HTTP n'est pas inférée de la simple présence d'un `HttpTransportPool`. Elle est déclarée seulement lorsque le rôle fourni expose réellement, sur le réseau attendu :
```text
getBlock
getSlot
et au moins getBlocks ou getBlocksWithLimit
```
La disponibilité de `getTransaction` est détectée séparément pour la future hydration de références.
Pour Yellowstone, le fingerprint de coverage retire du calcul les éléments de transport/continuité qui ne définissent pas le scope métier :
```text
commitment
from_slot
ping
```
Le scope reste volontairement conservatif : aucune équivalence cross-family ou provider n'est inventée.
## Intégration runtime
Avant le spawn des sources live, `RawTransactionIngestRuntimeResources` :
```text
construit exactement un descriptor par source
valide réseau / source keys / TargetCoverage / bornes
construit un ledger vide run-local
conserve ces contrats pendant la durée du supervisor
revalide les invariants à la fermeture
```
Aucune nouvelle tâche, socket, retry, requête HTTP/gRPC/WS, lecture Config ou opération Store n'est ajoutée par cette tranche.
## Tests ajoutés / renforcés
Les tests couvrent :
```text
valeurs exactes des cinq bornes
plages inclusives / reversed / maximum / overflow
chevauchement et adjacency
merge borné
TargetCoverage exact dédupliquée
absence de broadening entre commitments
subsomption FullLedger seulement au même commitment
rejet de KnownReferences comme target
limites open/active gaps
monotonicité GapId
module production continuity privé
absence d'I/O et de nouvelles frontières dans continuity.rs
inventaire de modules de production mis à jour
```
La surface publique du crate reste inchangée ; les nouveaux contrats sont `pub(crate)` et réexportés uniquement par la façade crate-root pour usage interne conforme aux règles KSP.
## Dépendances / features
```text
aucune nouvelle dépendance
aucune nouvelle feature
aucun SDK provider
aucun accès Config depuis le Worker
aucun accès Job Backfill depuis le Worker
aucun backend Store physique depuis le Worker
```
## Version Cargo
```text
header Cargo.toml : 566 -> 567
workspace.package.version : 0.3.14-pre.1 -> 0.3.14-pre.2
```
## Validations exécutées
Dans le sandbox de préparation :
```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/0.3.14
scan statique frontières / I/O continuity
comparaison exacte avec la base pre.001
contrôle contenu archive delta
unzip -t archive delta
```
Résultats :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (306 tables / 182 files)
scan continuity I/O / frontières : PASS
scan surface publique continuity : PASS
comparaison pre.001 -> pre.002 : 3 ajouts / 5 modifications / 0 suppression
```
Le contrôle ZIP est exécuté après construction de l'archive de livraison ; toute archive qui échoue ce contrôle est rejetée et n'est pas livrée.
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust ; les gates suivants ne peuvent donc pas constituer une preuve locale :
```text
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
Ils doivent être exécutés côté opérateur après application du delta.
## Décisions prises
```text
pre.002 reste sans repair I/O
TargetCoverage est conservative et commitment-sensitive
FullLedger ne broaden jamais un commitment différent
KnownReferences ne prouve jamais la complétude
HTTP repair capability est détectée par méthodes réellement routables
Yellowstone coverage identity exclut cursor replay et identité provider/endpoint
les contrats restent privés au Worker
aucune nouvelle dépendance ou frontière architecturale
```
## Questions ouvertes
```text
aucune pour pre.002
```
La tranche suivante reste `pre.003` : observabilité latest-value de continuité WebSocket, sans encore déclencher le repair.
## Gate opérateur après application
```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.14
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

333
deltas/0.3.14/pre.003.md Normal file
View File

@@ -0,0 +1,333 @@
<!-- file: deltas/0.3.14/pre.003.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.003` — observabilité latest-value de continuité WebSocket
## Base requise
```text
0.3.14-pre.002-fix.002
workspace.package.version = 0.3.14-pre.2.fix.2
deltas/0.3.14/pre.002-fix.002.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.002-fix.002` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
114 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 27 PASS
public_api : 20 PASS
release_completeness : 5 PASS
```
## Objectif
Implémenter strictement la tranche `pre.003` du plan `035` :
```text
ajouter dans Transport une source latest-value sûre pour les snapshots WebSocket typés
la déléguer depuis les façades Standard et Helius existantes
observer les incidents reconnect/overflow dans les sources Standard Logs, Standard Block et Helius du Worker
ancrer un incident sur le dernier slot réellement observé par cette source
borner l'incident avec le premier slot post-incident réellement reçu
conserver une terminalité conservative tant que les tranches de replay/repair ultérieures ne sont pas présentes
ne créer aucun nouveau socket, actor, retry ou repair HTTP
```
Cette tranche ne remplace pas encore un gap WebSocket par une réparation. Elle rend l'incident explicitement observable et bornable en réutilisant exclusivement les snapshots déjà publiés par l'actor Transport existant.
## Transport — source latest-value WebSocket
`WsSession` possède déjà un `tokio::sync::watch::Receiver<WsSessionSnapshot>` interne utilisé pour `snapshot()` et `state()`.
`pre.003` ajoute `WsSessionSnapshotSource`, un observateur cloneable qui encapsule uniquement un clone de ce receiver et expose :
```text
current() -> dernier WsSessionSnapshot sûr
wait_for_change() -> prochaine valeur latest-value, ou None si l'actor disparaît
```
Le type ne contient ni URL, ni credentials, ni socket, ni payload de notification. Son `Debug` reste fondé sur la projection sûre existante.
`WsSession::snapshot_source()` clone le receiver existant. Les façades :
```text
SolanaStandardWsSession
HeliusLaserStreamWsSession
```
délèguent cette méthode au même `WsSession` physique. Aucun second runtime WebSocket n'est créé.
## Worker — ancre d'incident WebSocket
Le Worker introduit un contrat crate-private `RawTransactionIngestWebSocketIncidentAnchor`.
Il conserve uniquement :
```text
start_slot
end_slot optionnel
compteurs de continuity gap / overflow observés au moment de l'incident
motifs reconnect et/ou overflow
```
La borne de début respecte le plan `035` :
```text
start_slot = dernier slot réellement observé par la source avant l'incident
```
Aucun timestamp, frontier dérivé d'une autre source ou `last_slot + 1` n'est utilisé comme preuve de début.
La borne de fin est :
```text
end_slot = premier slot réellement reçu par la même source après l'incident
```
Le range reste inclusif. Les validations réutilisent les bornes run-local introduites en `pre.002` et rejettent notamment inversion, dépassement de plage et compteur régressif.
Si un reconnect/overflow est observé avant qu'un slot source n'ait été vu, le Worker ne fabrique pas d'historique : l'incident est `source.websocket_incident_unbounded`.
## Worker — observation des snapshots
`RawTransactionIngestProcessingFrontierReporter` observe maintenant les `WsSessionSnapshot` Transport et maintient :
```text
continuity gap total WebSocket
notification overflow total
ancre d'incident WebSocket éventuelle
projection de lifecycle source
```
Une source redevenue `Active` reste projetée `Reconnecting` tant qu'un incident ouvert n'a pas reçu sa borne post-incident. Cela évite de publier transitoirement une continuité saine alors que le gap n'est pas encore borné.
Aucune mutation de la politique de reconnect Transport n'est effectuée par le Worker.
## Standard Logs et Helius transaction
Les deux sources réutilisent maintenant `session.snapshot_source()` en parallèle de la réception métier et des tâches d'hydration existantes.
Lorsqu'un incident est détecté :
```text
le premier signal post-incident borne le range de manière inclusive
ce signal est quand même admis dans le coordinator d'hydration existant
aucune nouvelle notification n'est admise après cette borne dans pre.003
les signaux/tâches déjà admis sont drainés
la source termine ensuite par source.continuity_gap_proven
```
Ce séquencement évite de perdre artificiellement le premier événement post-incident tout en restant fail-closed tant que les tranches de repair ne sont pas implémentées.
## Standard Block
La source Standard Block observe le même snapshot latest-value Transport.
Le premier bloc post-incident :
```text
borne l'incident
projette et envoie toutes ses ingresses via l'admission centrale existante
marque ensuite le slot settled
termine enfin par source.continuity_gap_proven
```
Un bloc sans transaction qualifiée peut quand même fermer la borne de slot et est settled avant la terminalité conservative.
## Yellowstone
Le chemin Yellowstone existant reste inchangé fonctionnellement dans cette tranche :
```text
aucune mutation Worker de from_slot
aucun ownership Worker de SubscribeReplayInfo
aucune nouvelle tentative de replay
aucune modification du contrat Transport Yellowstone
```
La preuve de replay native et la distinction `replay_attempt` / couverture effective restent réservées à `pre.004`.
## Tests ajoutés/étendus
Transport couvre :
```text
source latest-value courante
publication d'un changement
fermeture de la source lorsque l'actor publisher disparaît
accessibilité crate-root de WsSessionSnapshotSource
présence des délégations Standard/Helius
réutilisation du watch actor existant sans second runtime
```
Worker couvre :
```text
ancre inclusive et bornée
extension monotone d'un incident reconnect/overflow
rejet d'un incident sans motif
rejet de range inversé ou surdimensionné
incident sans slot précédent -> erreur explicite
compteurs WebSocket régressifs -> erreur explicite
reconnect et overflow successifs -> même ancre la plus ancienne
absence de socket/retry/from_slot possédé par le Worker
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.003.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
crates/ksp-onchain-transport-lib/src/ws_session.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009`, cette nouvelle prerelease synchronise la version workspace :
```text
header Cargo.toml : 569 -> 570
workspace.package.version : 0.3.14-pre.2.fix.2 -> 0.3.14-pre.3
```
Versions des fichiers modifiés :
```text
ksp-onchain-transport-lib/src/lib.rs : 47 -> 48
ksp-onchain-transport-lib/src/ws_protocol_session.rs : 6 -> 7
ksp-onchain-transport-lib/src/ws_session.rs : 14 -> 15
ksp-onchain-transport-lib/tests/public_api.rs : 52 -> 53
ksp-onchain-transport-lib/tests/release_completeness.rs : 44 -> 45
ksp-onchain-transport-lib/unit_tests/ws_session.rs : 11 -> 12
ksp-worker-raw-transaction-ingest-lib/src/continuity.rs : 3 -> 4
ksp-worker-raw-transaction-ingest-lib/src/lib.rs : 28 -> 29
ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs : 29 -> 30
ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs : 26 -> 27
ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs : 21 -> 22
ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs : 2 -> 3
ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs : 23 -> 24
```
## Frontières préservées
```text
aucun repair HTTP
aucun nouveau socket WebSocket
aucun second actor WebSocket
aucun retry Worker
aucune nouvelle dépendance
aucune nouvelle feature
aucun changement Config
aucun changement Store
aucun changement Job Backfill
aucun backend Store physique depuis Worker
aucune mutation Worker de Yellowstone from_slot
aucun SubscribeReplayInfo possédé par Worker
aucun élargissement de la façade publique Worker
src/lib.rs Worker reste exempt de ReplayInfo/from_slot/repair/backfill
```
## Validations exécutées
Dans le sandbox de préparation :
```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
scan exact ReplayInfo/from_slot/repair/backfill dans Worker src/lib.rs
scan de possession WebSocket directe dans Worker
scan set_from_slot dans les sources de production Worker
comparaison exacte 0.3.14-pre.002-fix.002 -> 0.3.14-pre.003
contrôle des versions de fichier modifiées
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
réapplication de l'archive sur la base puis comparaison byte-exacte
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent donc explicitement à exécuter côté opérateur :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
l'observabilité WS réutilise le watch latest-value déjà possédé par Transport
le Worker observe mais ne pilote pas la reconnexion WebSocket
un gap WS commence au dernier slot source réellement observé, inclusivement
le premier slot source post-incident est la borne de fin inclusive
aucune histoire antérieure n'est inventée sans ancre source
le premier événement post-incident est admis avant la terminalité conservative
pre.003 reste fail-closed et ne prétend pas encore réparer le gap
```
## Questions ouvertes
```text
aucune pour pre.003
```
## Tranche suivante
`pre.004` reste dédiée à la preuve de replay natif Yellowstone : qualification de `SubscribeReplayInfo`, preuve de couverture effective et conservation stricte de l'ownership `from_slot` dans Transport.
## Gate opérateur après application
```bash
cargo fmt --all
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 -p ksp-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

367
deltas/0.3.14/pre.004.md Normal file
View File

@@ -0,0 +1,367 @@
<!-- file: deltas/0.3.14/pre.004.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.004` — preuve conservative de replay natif Yellowstone
## Base requise
```text
0.3.14-pre.003
workspace.package.version = 0.3.14-pre.3
deltas/0.3.14/pre.003.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.003` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-onchain-transport-lib --all-targets --all-features : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Transport comprend notamment :
```text
390 unit tests : PASS
public_api : 53 PASS
release_completeness : 45 PASS
smokes live : ignorés par défaut conformément au contrat existant
```
Le gate Worker comprend notamment :
```text
119 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 28 PASS
public_api : 20 PASS
release_completeness : 5 PASS
```
## Objectif
Implémenter strictement la tranche `pre.004` du plan `035` :
```text
conserver l'ownership Yellowstone from_slot / SubscribeReplayInfo dans Transport
distinguer replay attempt de replay delivery
distinguer toute delivery ponctuelle d'une preuve de coverage
conserver first_available comme preuve de limite de rétention uniquement
rendre observable une reprise acceptée dont la couverture reste non prouvée
rester fail-closed côté Worker tant que les tranches de réconciliation ne sont pas présentes
ne créer aucun replay Worker, aucun repair HTTP et aucune nouvelle source
```
## Transport — distinction attempt / delivery / coverage non prouvée
`YellowstoneGrpcSubscribeSnapshot` conserve `replay_attempt_count` et ajoute deux compteurs sûrs :
```text
replay_delivery_count
replay_coverage_unproven_count
```
La sémantique est volontairement conservative.
`replay_delivery_count` n'augmente que lorsqu'une reconnexion replay-bearing redélivre exactement le slot de reprise effectivement demandé par Transport. Cette observation prouve uniquement qu'un matériau slot-bearing a été livré au bord demandé. Elle ne prouve pas que tous les updates correspondant aux filtres ont été rejoués sur l'intervalle.
`replay_coverage_unproven_count` augmente lorsqu'une reconnexion replay-bearing a réussi et que :
```text
le premier update slot-bearing post-reconnect atteint ou dépasse la borne demandée
ou
un nouveau reconnect commence avant tout matériau slot-bearing replayé
```
Même une redélivrance exacte de la borne fait donc progresser `replay_delivery_count` **et** `replay_coverage_unproven_count` : la première prouve une delivery ponctuelle, la seconde conserve explicitement l'absence de preuve de couverture complète.
Cette valeur signifie explicitement **coverage non prouvée**. Elle ne prouve pas qu'un événement filtré existait, qu'il a été perdu, ni que le provider a nécessairement violé son contrat.
Aucun compteur générique `replay_covered` / `replay_coverage_proven` n'est ajouté. Le replay natif Transport ne possède pas aujourd'hui une preuve générique suffisante pour faire cette affirmation.
## Transport — tracker de replay
Le `ContinuityTracker` existant conserve maintenant une seule borne privée :
```text
pending_replay_from_slot
```
Lorsqu'un reconnect physique Yellowstone réussit avec un `effective_from_slot`, Transport ouvre cette preuve pending.
Sur le premier update slot-bearing :
```text
slot == requested -> delivery conservatrice prouvée + coverage globale toujours non prouvée
slot > requested -> aucune delivery de borne prouvée + coverage globale non prouvée
slot < requested -> aucune conclusion positive ; la preuve reste pending
```
Si le stream se coupe de nouveau avant résolution de la borne pending, la coverage de cette tentative est classée non prouvée avant d'ouvrir la tentative suivante.
Les `Ping`/`Pong` ne ferment jamais cette preuve parce qu'ils ne portent aucun slot.
## Transport — rétention `SubscribeReplayInfo`
Le contrat antérieur reste inchangé :
```text
first_available > requested
```
incrémente `continuity_gap_count`, clamp le `from_slot` effectif dans Transport et signifie uniquement que l'endpoint ne peut pas rejouer avant `first_available`.
Le clamp ne devient pas une preuve de perte d'un événement filtré et ne devient pas une preuve de coverage complète.
## Cas replay accepté mais couverture non prouvée
Une fixture dédiée couvre le cas adversarial prévu au plan :
```text
avant coupure : slot 800 observé
reconnect : from_slot = 800 accepté
premier update après reconnect : slot 805
```
Résultat attendu :
```text
reconnect_count = 1
replay_attempt_count = 1
replay_delivery_count = 0
replay_coverage_unproven_count = 1
continuity_gap_count = 0
```
Le résultat ne prétend ni qu'un update manquait réellement entre 800 et 805, ni que le replay est complet. Il démontre seulement que KSP ne possède pas la preuve conservative de delivery requise pour assimiler la reprise à une couverture saine.
La fixture de replay redélivrant `slot 500` conserve au contraire :
```text
replay_attempt_count = 1
replay_delivery_count = 1
replay_coverage_unproven_count = 1
```
Cette delivery ne devient toujours pas un `replay_covered`.
## Worker — consommation des preuves Transport
`RawTransactionIngestProcessingFrontierReporter` observe désormais de manière monotone :
```text
replay_attempt_total
replay_delivery_total
replay_coverage_unproven_total
continuity_gap_total
```
Le Worker ne calcule aucun `from_slot`, n'appelle jamais `SubscribeReplayInfo` et ne construit aucune requête replay.
La phase `replay_attempt` peut être observée avant toute delivery et ne ferme aucune obligation de coverage. Une delivery de borne fait ensuite progresser la preuve de delivery mais conserve simultanément `coverage_unproven`.
Une progression de `replay_coverage_unproven_total` sans gap de rétention produit actuellement :
```text
source.replay_coverage_unproven
```
et conserve la terminalité fail-closed de la source. Cette terminalité est volontairement temporaire jusqu'aux tranches de coverage redondante et de réconciliation du plan `035` ; elle évite qu'un replay accepté mais non prouvé soit assimilé silencieusement à une continuité saine.
Si `continuity_gap_total` augmente simultanément, la preuve de rétention plus forte conserve la priorité et produit le contrat existant :
```text
source.continuity_gap_proven
```
Les nouveaux compteurs privés sont soumis au même rejet de régression que les compteurs reconnect/replay existants.
## Surface publique
Transport étend uniquement le snapshot Yellowstone déjà public avec :
```text
YellowstoneGrpcSubscribeSnapshot::replay_delivery_count()
YellowstoneGrpcSubscribeSnapshot::replay_coverage_unproven_count()
```
Aucun type provider, payload, endpoint, credential ou proto upstream n'est exposé.
La façade publique Worker n'est pas étendue. Les preuves supplémentaires restent privées au runtime Worker dans cette tranche.
## Documentation d'utilisation
`ksp-onchain-transport-lib/USAGE.md` explicite désormais que :
```text
attempt != delivery != coverage
une delivery ponctuelle n'est pas une preuve de replay_covered
coverage_unproven est une absence explicite de preuve, pas une preuve de perte
```
Le document reste version-neutral conformément aux règles workspace.
## Tests ajoutés/étendus
Transport couvre :
```text
replay avec redélivrance exacte de la borne -> delivery = 1
rétention first_available -> gap = 1 tout en conservant delivery distincte
reconnects exhaustés -> attempts sans fausse delivery
replay accepté puis progression au-delà de la borne -> coverage_unproven = 1
absence de compteur générique replay_covered/replay_coverage_proven
accessibilité crate-root des deux getters snapshot
```
Worker couvre :
```text
attempt seul != delivery
delivery de borne != coverage
coverage_unproven devient fail-closed
régression delivery/coverage_unproven rejetée
absence persistante de set_from_slot / SubscribeReplayInfo / YellowstoneReplayInfo dans Worker
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.004.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/grpc_stream.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009`, cette nouvelle prerelease synchronise la version workspace :
```text
header Cargo.toml : 570 -> 571
workspace.package.version : 0.3.14-pre.3 -> 0.3.14-pre.4
```
Versions des fichiers modifiés :
```text
ksp-onchain-transport-lib/USAGE.md : 25 -> 26
ksp-onchain-transport-lib/src/grpc_stream.rs : 4 -> 5
ksp-onchain-transport-lib/tests/public_api.rs : 53 -> 54
ksp-onchain-transport-lib/tests/release_completeness.rs : 45 -> 46
ksp-onchain-transport-lib/unit_tests/grpc_stream.rs : 5 -> 6
ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs : 30 -> 31
ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs : 27 -> 28
ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs : 22 -> 23
ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs : 24 -> 25
```
## Frontières préservées
```text
from_slot reste Transport-owned
SubscribeReplayInfo reste Transport-owned
aucun set_from_slot dans Worker
aucun replay loop Worker
aucun retry réseau Worker
aucun repair HTTP
aucune nouvelle source
aucun nouveau provider
aucune nouvelle dépendance
aucune nouvelle feature
aucun changement Config
aucun changement Store
aucun changement Job Backfill
aucun backend Store physique depuis Worker
aucun replay_covered générique inventé par Transport
aucun élargissement de la façade publique Worker
```
## Validations exécutées
Dans le sandbox de préparation :
```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
scan exact de possession replay dans Worker
scan des nouveaux compteurs attempt/delivery/coverage_unproven
comparaison exacte 0.3.14-pre.003 -> 0.3.14-pre.004
contrôle des versions de fichier modifiées
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
réapplication de l'archive sur la base puis comparaison byte-exacte
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent donc explicitement à exécuter côté opérateur :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
replay attempt ne prouve aucune delivery
delivery au slot demandé ne prouve aucune coverage complète
absence de delivery conservative avant progression/reconnect suivant devient coverage_unproven
first_available reste une preuve de rétention et non une preuve d'événement perdu
aucun replay_covered n'est revendiqué par Transport générique
le Worker reste fail-closed sur coverage_unproven jusqu'aux tranches de réconciliation
```
## Questions ouvertes
```text
aucune pour pre.004
```
## Tranche suivante
`pre.005` reste dédiée à la coverage redondante : relations exact/superset conservatrices, epochs/ranges de coverage et refus explicite des équivalences cross-family opportunistes.
## Gate opérateur après application
```bash
cargo fmt --all
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 -p ksp-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

320
deltas/0.3.14/pre.005.md Normal file
View File

@@ -0,0 +1,320 @@
<!-- file: deltas/0.3.14/pre.005.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.005` — preuve conservative de coverage redondante
## Base requise
```text
0.3.14-pre.004
workspace.package.version = 0.3.14-pre.4
deltas/0.3.14/pre.004.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.004` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-onchain-transport-lib --all-targets --all-features : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Transport comprend notamment :
```text
391 unit tests : PASS
public_api : 53 PASS
release_completeness : 46 PASS
smokes live : ignorés par défaut conformément au contrat existant
```
Le gate Worker comprend notamment :
```text
120 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 29 PASS
public_api : 20 PASS
release_completeness : 6 PASS
```
## Objectif
Implémenter strictement la tranche `pre.005` du plan `035` :
```text
matérialiser les relations conservative Exact / Superset
matérialiser des epochs et ranges de coverage run-local bornés
n'autoriser une preuve redondante que depuis une source distincte
exiger un commitment strictement identique
exiger que l'epoch couvre intégralement la plage du gap
interdire toute équivalence opportuniste entre familles exactes différentes
conserver KnownReferences impropre à TargetCoverage
ne modifier encore aucune décision runtime de supervisor
ne déclencher aucun repair HTTP et aucun replay supplémentaire
```
## Relations de coverage
`RawTransactionIngestCoverageScope` possède maintenant une relation privée et directionnelle :
```text
FullLedgerTransactions -> FullLedgerTransactions : Exact
FullLedgerTransactions -> ExactSourceScope(...) : Superset
ExactSourceScope(family, fingerprint)
-> ExactSourceScope(même family, même fingerprint) : Exact
autres combinaisons : aucune relation prouvée
```
Cette relation est volontairement conservative.
Deux scopes exacts qui partagent le même fingerprint mais pas le même `family_code` ne sont jamais considérés équivalents. Ainsi, par exemple, aucune correspondance `standard_logs <-> helius_transaction` ou `standard_block <-> yellowstone` n'est inventée à partir d'une similarité de sélecteur.
La relation est également indépendante de l'identité provider/endpoint : cette indépendance vient uniquement du scope sémantique déjà normalisé, tandis que l'obligation de redondance exige une `source_key` différente.
## Commitments
La redondance requiert un commitment strictement identique :
```text
Confirmed couvre seulement Confirmed
Finalized couvre seulement Finalized
```
Aucune hiérarchie `Confirmed <-> Finalized` n'est inférée dans cette tranche.
## TargetCoverage
La construction de `RawTransactionIngestTargetCoverage` réutilise désormais la relation conservative plutôt qu'une comparaison ad hoc :
```text
un FullLedgerTransactions au même commitment subsume les requirements exacts
un requirement exact identique est dédupliqué
un scope exact cross-family reste distinct
un commitment différent reste distinct
KnownReferences reste interdit comme target scope
```
Aucune broadening cross-family n'est introduite.
## Epochs et ranges de coverage
La tranche matérialise des contrats privés :
```text
RawTransactionIngestCoverageRange
RawTransactionIngestCoverageEpoch
RawTransactionIngestCoverageEpochLedger
RawTransactionIngestCoverageRelation
```
Un `CoverageRange` est inclusif et rejette uniquement une plage inversée. Contrairement au `GapRange`, il n'est pas limité artificiellement à `MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS`, car un epoch de continuité saine peut être plus long qu'une seule obligation de repair.
Un epoch conserve :
```text
epoch_id
source_key
commitment
scope
range inclusif
```
Le ledger est run-local et borné à :
```text
MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS = 256
```
Il vérifie :
```text
IDs non nuls et uniques
next_epoch_id strictement monotone
borne de cardinalité
range valide
scope admissible comme configured target scope
source_key présente dans l'inventaire exact des capabilities
commitment et scope strictement identiques à la capability de cette source
```
Le ledger est initialisé vide avec les autres continuity contracts. Aucune coverage n'est pré-déclarée à partir de la simple configuration d'une source.
## Preuve de redondance
Un epoch peut prouver une obligation uniquement si toutes les conditions suivantes sont vraies :
```text
source de preuve != source cible
commitment de preuve == commitment requis
range de preuve contient intégralement le GapRange inclusif
scope de preuve relation Exact ou Superset avec le scope requis
```
Un epoch partiel, une source identique, un commitment différent, un filtre différent ou une famille différente ne ferment donc aucune obligation.
`FullLedgerTransactions` peut fournir une preuve `Superset` d'un scope exact au même commitment, parce qu'il représente toutes les transactions de tous les blocs produits. L'inverse est interdit.
## Activation runtime
Cette tranche ne change pas encore la politique de source-loss/reconciliation :
```text
aucun gap n'est fermé automatiquement
aucune source n'est maintenue en vie grâce à cette preuve
aucune terminalité fail-closed de pre.003/pre.004 n'est encore remplacée
```
`pre.008` restera responsable de l'intégration de ces preuves dans le supervisor et le gap ledger. `pre.005` fournit uniquement le modèle conservative nécessaire.
## Tests ajoutés
Les unit tests Worker couvrent :
```text
Exact pour même famille + même fingerprint
Superset uniquement FullLedger -> exact
absence de relation exact -> FullLedger
absence de relation pour filtre exact différent
absence de relation cross-family malgré fingerprint identique
source distincte obligatoire
commitment strictement identique
range complet obligatoire
ledger borné à 256 epochs
next_epoch_id monotone
capability source/scope/commitment obligatoire
```
Les canaris `hardening` et `release_completeness` garantissent en plus :
```text
présence des contrats Exact/Superset/epoch
absence d'équivalence cross-family codée en dur
absence de hiérarchie opportuniste de commitment
absence d'exposition publique des nouveaux types de preuve
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.005.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009`, cette nouvelle prerelease synchronise la version workspace :
```text
header Cargo.toml : 571 -> 572
workspace.package.version : 0.3.14-pre.4 -> 0.3.14-pre.5
```
Versions des fichiers modifiés :
```text
ksp-worker-raw-transaction-ingest-lib/src/continuity.rs : 4 -> 5
ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs : 28 -> 29
ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs : 23 -> 24
ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs : 3 -> 4
```
## Frontières préservées
```text
aucun changement Transport
aucun changement Config
aucun changement Store
aucun changement Job Backfill
aucune nouvelle dépendance
aucune nouvelle feature
aucun repair HTTP
aucun replay Worker
aucun nouveau socket/actor/retry
aucun set_from_slot dans Worker
aucune nouvelle surface publique Worker
aucune équivalence cross-family opportuniste
aucune hiérarchie implicite de commitment
```
## Validations exécutées dans le sandbox de préparation
```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
scan des frontières et de la surface crate-root Worker
comparaison exacte 0.3.14-pre.004 -> 0.3.14-pre.005
contrôle des versions de fichiers modifiés
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
réapplication de l'archive sur la base puis comparaison byte-exacte
```
## Validations non exécutées dans le sandbox de préparation
Le sandbox ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté opérateur :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
coverage relationnelle directionnelle, jamais symétrisée implicitement
FullLedger est un superset d'un exact scope au même commitment
un exact scope n'est équivalent qu'à même family + même fingerprint
une redondance doit provenir d'une source_key distincte
un epoch doit contenir toute la plage du gap
aucune coverage initiale n'est créée depuis la configuration seule
pre.005 ne modifie pas encore la terminalité runtime
```
## Questions ouvertes
```text
aucune pour pre.005
```
## Tranche suivante
`pre.006` reste dédiée au scan HTTP de réparation : détection des capabilities run-wide réellement disponibles, fenêtres bornées, découverte `getBlocks`/`getBlocksWithLimit` conservative et récupération `getBlock observed`, sans broadening de scope.
## Gate opérateur après application
```bash
cargo fmt --all
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 -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

274
deltas/0.3.14/pre.006.md Normal file
View File

@@ -0,0 +1,274 @@
<!-- file: deltas/0.3.14/pre.006.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.006` — discovery HTTP bornée pour continuité
## Base requise
```text
0.3.14-pre.005
workspace.package.version = 0.3.14-pre.5
deltas/0.3.14/pre.005.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.005` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
124 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 30 PASS
public_api : 20 PASS
release_completeness : 7 PASS
```
## Objectif
Implémenter strictement la tranche `pre.006` du plan `035` :
```text
détecter les primitives HTTP de scan réellement disponibles sur le rôle same-network
borner chaque fenêtre de discovery à 512 slots inclusifs
préférer getBlocks(start,end) lorsqu'il est réellement supporté
utiliser getBlocksWithLimit uniquement avec une preuve de progression issue de sa propre réponse
ne jamais utiliser getSlot seul comme preuve de queue skipped
réutiliser getBlock observed pour les slots explicitement produits
conserver getBlock = null comme obstacle, jamais comme slot skipped
ne pas élargir un scope filtré vers FullLedgerTransactions
ne pas modifier encore la politique de supervisor ou de health
ne pas introduire de logique caller-driven de backfill
```
## Capability HTTP run-wide
`runtime_resources.rs` matérialise maintenant un inventaire privé :
```text
RawTransactionIngestHttpScanCapabilities
get_block
get_blocks
get_blocks_with_limit
get_slot
```
Le choix de primitive est conservative :
```text
getBlock + getBlocks
-> ClosedRange
getBlock + getBlocksWithLimit + getSlot
-> WithLimitBoundary
autres combinaisons
-> scan indisponible
```
`ClosedRange` est toujours préféré lorsque les deux stratégies sont disponibles.
La détection reste fondée sur le snapshot Transport du rôle exact et du cluster attendu. La seule présence d'un pool HTTP ne crée donc aucune capability implicite.
## Fenêtres bornées
La borne canonique de `continuity.rs` reste :
```text
MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
```
Un alias crate-wide de même valeur est ajouté uniquement pour que `runtime_resources.rs` réutilise cette borne sans la dupliquer :
```text
MAX_RAW_TRANSACTION_INGEST_CONTINUITY_DISCOVERY_WINDOW_SLOTS
```
Une fenêtre :
```text
est inclusive
rejette end < start
rejette les overflows
rejette plus de 512 slots
```
La source HTTP polling productive réutilise désormais cette primitive. Sa valeur `max_discovered_blocks_per_cycle` reste une borne supérieure ; une fenêtre productive ne dépasse jamais le minimum entre cette valeur et `512` slots.
## Stratégie `ClosedRange`
Lorsque `getBlocks` est supporté, le Worker appelle :
```text
getBlocks(start_slot, end_slot, commitment)
```
Une réponse réussie constitue une preuve fermée de la fenêtre RPC demandée. Les slots absents de la liste sont alors qualifiables comme `skipped/non-produced` uniquement à l'intérieur de cette fenêtre.
Tout slot retourné au-delà de `end_slot` est rejeté comme violation d'invariant.
## Stratégie `WithLimitBoundary`
Lorsque seul le chemin `getBlocksWithLimit` qualifié est disponible :
```text
getSlot(commitment)
getBlocksWithLimit(start_slot, window_slot_count, commitment)
```
`getSlot` indique uniquement la position du cluster observée par ce rôle. Il ne ferme jamais à lui seul la queue de la fenêtre.
La preuve avance seulement jusqu'au dernier slot produit effectivement énuméré par la réponse `getBlocksWithLimit` :
```text
last_discovered < end_slot
-> preuve partielle jusqu'à last_discovered
last_discovered >= end_slot
-> fenêtre cible entièrement prouvée
aucun slot découvert
-> aucune preuve de tail, même si getSlot >= end_slot
```
Ainsi, une queue constituée uniquement de slots skipped n'est pas inventée à partir de deux réponses pouvant provenir de routes HTTP différentes. La fenêtre restera en attente jusqu'à ce qu'une primitive plus forte ou un bloc ultérieur fournisse une borne observable.
## `getBlock observed`
Chaque slot explicitement produit continue à passer par :
```text
HttpTransportPool::get_block_observed
```
Les règles existantes restent inchangées :
```text
Some(block)
-> projection Common RAW puis admission centrale
None
-> la progression se bloque sur ce slot
le slot n'est pas reclassé skipped
```
Aucun second pipeline de persistence n'est ajouté.
## Scope et intégration
Cette tranche utilise immédiatement la primitive bornée uniquement dans `RawTransactionIngestHttpBlockPollingSource`, dont le scope est déjà `FullLedgerTransactions`.
Pour Yellowstone, Standard Logs et Helius Transaction, la détection run-wide de capability HTTP demeure disponible dans les continuity descriptors, mais aucun scan full-block n'est déclenché ici. Leur activation dépendra de la preuve de scope et de la réconciliation des tranches suivantes.
`Standard Block` conserve l'absence de pool HTTP attaché.
La politique de terminalité des incidents `pre.003/pre.004` n'est pas encore remplacée. `pre.008` reste responsable de l'intégration supervisor/gap-ledger.
## Tests ajoutés
Les unit tests Worker couvrent :
```text
préférence ClosedRange sur fallback
capability fallback complète obligatoire
capability détectée par rôle sans I/O
fenêtre inclusive 1..512
rejet 513 slots, range inversé et overflow
ClosedRange vide comme preuve de slots skipped dans la fenêtre exacte
rejet d'un résultat ClosedRange au-delà de end_slot
fallback dépassant end_slot et filtrage de la cible
fallback partiel ne prouvant que jusqu'au dernier slot énuméré
getSlot seul + réponse vide ne prouvant aucune queue
absence de getSlot dans le fallback rejetée
```
Les canaris `hardening` et `release_completeness` garantissent en plus :
```text
présence des deux stratégies HTTP
usage borné de getBlocks/getBlocksWithLimit
réutilisation de getBlock observed
absence de dépendance Job Backfill
absence de surface publique de discovery
absence de responsabilité repair lower-case dans la crate-root historique
```
## Hors périmètre inchangé
```text
aucun moteur de reconciliation supervisor
aucun changement health public
aucune hydration repair getTransaction dans cette tranche
aucune source/provider supplémentaire
aucun EARLY/shred adapter
aucun backfill historique caller-driven
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.006.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009` :
```text
header Cargo.toml : 572 -> 573
workspace.package.version : 0.3.14-pre.5 -> 0.3.14-pre.6
```
Versions des fichiers modifiés :
```text
continuity.rs : 5 -> 6
lib.rs : 29 -> 30
runtime_resources.rs : 31 -> 32
unit_tests/runtime_resources.rs : 25 -> 26
tests/hardening.rs : 29 -> 30
tests/release_completeness.rs : 24 -> 25
```
## Validation exécutée dans l'environnement de préparation
```text
python3 scripts/audit_rust_workspace_rules.py : PASS
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS
```
Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.007`.
## Prochaine tranche
`pre.007` : hydration des références connues avec `getTransaction observed`, coalescence globale existante et conservation de `Missing` comme obligation non résolue ; aucun `getTransaction = null` ne devra devenir une preuve d'absence.

Some files were not shown because too many files have changed in this diff Show More